美文网首页
OpenGL案例04

OpenGL案例04

作者: 卡布奇诺_95d2 | 来源:发表于2020-08-20 23:33 被阅读0次

    案例04:本案例是在案例01:绘制甜甜圈自转+小球公转+移动的基础上,增加纹理和镜像显示,效果图如下:

    球体世界效果图
    由于之前案例01已经实现将甜甜圈、静态小球、公转小球绘制完成,本案例将在此基础上进行修改。
    待修改的内容有:
    • 将甜甜圈转换成大球
    • 将大球、小球、地板都增加纹理
    • 实现镜像显示

    SetupRC函数

    1. 将构建甜甜圈转成构建球体,这里直接使用系统函数。
    gltMakeSphere(torusBatch, 0.4f, 40, 80);
    
    1. 增加地板的纹理坐标与顶点坐标的关联。
    GLfloat texSize = 10.0f;
    floorBatch.Begin(GL_TRIANGLE_FAN, 4,1);
    floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    floorBatch.Vertex3f(-20.f, -0.41f, 20.0f);
        
    floorBatch.MultiTexCoord2f(0, texSize, 0.0f);
    floorBatch.Vertex3f(20.0f, -0.41f, 20.f);
        
    floorBatch.MultiTexCoord2f(0, texSize, texSize);
    floorBatch.Vertex3f(20.0f, -0.41f, -20.0f);
        
    floorBatch.MultiTexCoord2f(0, 0.0f, texSize);
    floorBatch.Vertex3f(-20.0f, -0.41f, -20.0f);
    floorBatch.End();
    

    注意:
    地板的绘制使用的是三角形批次类,因此地板需要4个顶点即:2个三角形组成。
    此时的纹理坐标超过1.0,这是因为后续加载纹理的时候采用了GL_REPEAT环绕方式,在OpenGL中纹理坐标超过1.0且环绕方式为GL_REPEAT时,表示在超过1.0的⽅向上对纹理进行重复。

    1. 加载纹理
      加载纹理的步骤:
      3.1 生成纹理对象
      3.2 绑定纹理
      3.3 读取纹素
      3.4 设置纹理参数
      3.5 载入纹理

    由于此处涉及3个纹理的加载,所以将3.2-3.5步骤封装成LoadTGATexture函数,加载不同纹理的时候分别调用这个函数即可。

    //生成纹理对象
    glGenTextures(3, uiTextures);
        
    //将TGA文件(地板)加载为2D纹理。
    //GL_LINEAR_MIPMAP_LINEAR:在Mip层之间执行线性插补,并执行线性过滤,又称三线性Mip贴图
    //GL_LINEAR:在Mip基层上执行线性过滤
    //GL_REPEAT:OpenGL在纹理坐标超过1.0的⽅向上对纹理进行重复
    glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
    LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
        
    //将TGA文件(大球)加载为2D纹理。
    //GL_LINEAR_MIPMAP_LINEAR:在Mip层之间执行线性插补,并执行线性过滤,又称三线性Mip贴图
    //GL_LINEAR:在Mip基层上执行线性过滤
    //GL_CLAMP_TO_EDGE:纹理坐标会被约束到0和1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果
    glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
    LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
        
    //将TGA文件(小球)加载为2D纹理。
    glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
    LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
    

    RenderScene函数

    在RenderScene函数中,分三部分进行绘制。

    • 绘制镜像中的球体(大球+小球)
    • 绘制地板
    • 绘制正常观察到的球体(大球+小球)

    绘制球体

    由于镜像中球体与正常观察的球体只是一个镜像显示,但是绘制方式都是一样的,所以可以将绘制球体抽成一个函数,这样,在绘制镜像和绘制正常现象时,只需要调用该函数就能完成球体绘制。

    1. 绘制自转大球
    glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
    modelViewMatrix.Translate(0.0f, 0.2f, 0.0f);
    modelViewMatrix.PushMatrix();
    modelViewMatrix.Rotate(yRot, 0, 1, 0);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vWhite, 0);
        torusBatch.Draw();
    modelViewMatrix.PopMatrix();
    
    1. 绘制静态小球
    glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
    for(int i = 0; i<NUM_SPHERES; i++){
        modelViewMatrix.PushMatrix();
        modelViewMatrix.MultMatrix(spheres[i]);
        shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vWhite, 0);
        sphereBatch.Draw();
        modelViewMatrix.PopMatrix();
    }
    
    1. 绘制公转小球
    glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
    modelViewMatrix.PushMatrix();
    modelViewMatrix.Rotate(yRot*-2.0f, 0, 1, 0);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vWhite, 0);
    sphereBatch.Draw();
    modelViewMatrix.PopMatrix();
    

    镜像球体绘制

    1. 一切需要进行变换时,先将模型视图矩阵压栈。
    modelViewMatrix.PushMatrix();
    
    1. 翻转y轴,通过缩放因子为-1的方式。
    modelViewMatrix.Scale(1.0f, -1.0f, 1.0f);
    
    1. 增加y轴方向的平移,为了镜面的效果更加逼真。在实际生活中照镜子,镜子里面与镜子外面的物体之间也有间隔。
    modelViewMatrix.Translate(0, 0.8, 0);
    
    1. 在绘制-y轴镜像球体之前,需要指定修改系统默认的正面,默认情况下,逆时针为正面,此时修改为顺时针为正面。这样做的好处就是可以使用同一套绘制球体的方法。
    glFrontFace(GL_CW);
    
    1. 绘制球体,此时绘制的是-y轴的那部分球体。
    drawSomething(yRot);
    

    6.绘制完成之后需要将正面恢复为逆时针方向,不然后续无法绘制正常球体。

    glFrontFace(GL_CCW);
    
    1. 模型视图矩阵堆栈栈顶出栈,对应于开始镜像绘制的入栈,将模型视图矩阵恢复原样。
    modelViewMatrix.PopMatrix();
    

    镜面地板绘制

    绘制镜面地板的时候不能将镜像的球体遮挡,地板需要呈半透明显示,为了达到这种效果,必须开启颜色混合,将地板颜色与镜像球体进行颜色混合。
    注意:地板需要设置一个半透明的基本色,用于颜色混合,如果不设置,则就算开启颜色混合,地板也不会与镜面球体发生颜色混合。

    1. 开启颜色混合
    glEnable(GL_BLEND);
    
    1. 指定颜色混合方程式,这里采用这种固定写法即可。因为在实际使用中,也无法完全确定A颜色与B颜色混合之后一定呈现哪种颜色。
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    1. 重新绑定地板纹理
    glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
    
    1. 采用纹理调整着色器(将一个基本色乘以一个取自纹理的单元nTextureUnit的纹理)
    /*
    参数1:GLT_SHADER_TEXTURE_MODULATE
    参数2:模型视图投影矩阵
    参数3:颜色
    参数4:纹理单元(第0层的纹理单元)
    */
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor, 0);
    
    1. 使用批次类绘制地板
    floorBatch.Draw();
    
    1. 关闭颜色混合
    glDisable(GL_BLEND);
    

    非镜像球体绘制

    当前系统逆时针绘制为正面已经在绘制完镜像的时候恢复了,可以调用绘制球体的函数直接绘制非镜像球体。

    modelViewMatrix.PushMatrix();
    drawSomething(yRot);
    modelViewMatrix.PopMatrix();
    

    至此,项目完成整个球体世界的显示。

    总结:

    • 翻转y轴的方式可通过将缩放因子设置为-1实现。
    • 绘制球体的时候可以通过修改正背面来实现镜像球体和非镜像球体。
    • 绘制地板的时候需要设置一个半透明的基本色,否则镜像的部分将被遮住。
    • 绘制地板的时候需要开启颜色混合,否则镜像部分也无法显示。

    Demo地址

    相关文章

      网友评论

          本文标题:OpenGL案例04

          本文链接:https://www.haomeiwen.com/subject/hiisjktx.html