OpenGL入门第八课--补充个案例

1,029 阅读5分钟

       前面几节都是纯理论居多看起来有点枯燥,这节来补充一个案例。

类似CF隧道场景的绘制

先来看一下效果,这样方面大家理解我们下面的代码到底在写些什么。

                                                   

怎么样是不是很像之前玩的cf里的隧道地图,还能通过前后建控制视角往前后移动。效果也看到了后面闲话少说直接上代码吧

int main(int argc, char *argv[]){    
gltSetWorkingDirectory(argv[0]);//设置当前工作目录,针对MAC OS X    
glutInit(&argc, argv);//初始化GLUT库    
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);//将显示模式初始化双缓冲、RGB颜色模式    
glutInitWindowSize(800, 600);//初始化窗口大小    
glutCreateWindow("CF");//创建窗口并设置窗口标题为CF  
glutReshapeFunc(ChangeSize);//注册回调函数ChangeSize 初次创建窗口以及窗口大小改变是调用    
glutSpecialFunc(SpecialKeys);//注册回调函数SpecialKeys 键盘特殊键位(比如上下左右)按下时调用 
glutDisplayFunc(RenderScene);//注册回调函数RenderScene,窗口内容绘制、窗口大小改变、窗口重绘等都会回调该函数    
//初始化驱动程序并坚持有没有出错    
GLenum err = glewInit();    
if (GLEW_OK != err) {        
   fprintf(stderr, "GLEW Error: %s\n", 
    glewGetErrorString(err));
    return 1;
}   
SetupRC();//调用SetupRC函数    
glutMainLoop();// 启动循环    
ShutdownRC();//删除纹理    
return 0;

main函数作为程序的入口主要完成初始化、注册回调函数、启动运行循环等操作。然后main函数里面调用了SetupRC这个自定义函数,在这个函数里我们处理一些参数的初始化,一般是处理一些只需要初始化一次的参数,当然也能处理渲染环境中任何需要的初始化,看个人喜好和代码风格。比如这个项目中我们设置并初始化纹理对象。

void SetupRC(){   
     //1.黑色的背景    
     glClearColor(0.0f, 0.0f, 0.0f,1.0f);
     //2.初始化shaderManager    
     shaderManager.InitializeStockShaders();    
     GLbyte *pBytes;    
     GLint iWidth, iHeight, iComponents;    
     GLenum eFormat;   
     GLint iLoop;    
    //3.生成纹理标记    
    glGenTextures(TEXTURE_COUNT, textures);    
    //4. 循环设置纹理数组的纹理参数    
    for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++){
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
        pBytes = gltReadTGABits(szTextureFiles[iLoop],&iWidth, &iHeight,&iComponents, &eFormat);//加载纹理、设置过滤器和包装模式
        //GL_TEXTURE_MAG_FILTER(放大过滤器,GL_NEAREST(最邻近过滤)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        //GL_TEXTURE_MIN_FILTER(缩小过滤器),GL_NEAREST(最邻近过滤)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        //GL_TEXTURE_WRAP_S(s轴环绕),GL_CLAMP_TO_EDGE(环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或一列进行采样)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        //GL_TEXTURE_WRAP_T(t轴环绕),GL_CLAMP_TO_EDGE(环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或一列进行采样)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
        glGenerateMipmap(GL_TEXTURE_2D);//为纹理对象生成一组完整的mipmap
        //释放原始纹理数据,不在需要纹理原始数据了
        free(pBytes);    
    }    
    //5. 设置几何图形顶点/纹理坐标(上.下.左.右)    
    GLfloat z;    
    floorBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);    //Z表示深度,隧道的深度  
    for(z = 60.0f; z >= 0.0f; z -=10.0f){        
        floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        floorBatch.Vertex3f(-10.0f, -10.0f, z);
        floorBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        floorBatch.Vertex3f(10.0f, -10.0f, z);
        floorBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        floorBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
        floorBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        floorBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
    }   
    floorBatch.End(); 
    ceilingBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f) {
        ceilingBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        ceilingBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
        ceilingBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        ceilingBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
        ceilingBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        ceilingBatch.Vertex3f(-10.0f, 10.0f, z);
        ceilingBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        ceilingBatch.Vertex3f(10.0f, 10.0f, z);
    }
    ceilingBatch.End();
    leftWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f){
        leftWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        leftWallBatch.Vertex3f(-10.0f, -10.0f, z);
        leftWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        leftWallBatch.Vertex3f(-10.0f, 10.0f, z);
        leftWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        leftWallBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
        leftWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        leftWallBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
    }
    leftWallBatch.End();
    rightWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f){
        rightWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        rightWallBatch.Vertex3f(10.0f, -10.0f, z);
        rightWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        rightWallBatch.Vertex3f(10.0f, 10.0f, z);
        rightWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        rightWallBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
        rightWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        rightWallBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
    }
    rightWallBatch.End();
}

     在这个函数里先设置了清屏颜色,初始化固定管线,然后设置了纹理相关的参数(顶点坐标和纹理坐标)。关于顶点坐标和纹理坐标,这里只说一个面,如下图红色字体和黄色字符分别表示出了底部的顶点坐标和纹理坐标。其他面也都大同小异。


     这里要注意的是顶点坐标,要把他想象成立体的所以越往屏幕里面z值越小,而不是y值变小。

     处理完这些后接着就是实现main函数里注册的各个回调函数了 ,我们首先来看ChangeSize函数。

void ChangeSize(int w, int h)
{
    if(h == 0)
    h = 1;
    //2.将视口设置大小 
    glViewport(0, 0, w, h);
    GLfloat fAspect = (GLfloat)w/(GLfloat)h;
    //3.生成透视投影    
    viewFrustum.SetPerspective(80.0f,fAspect,1.0,120.0);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

     这个函数是窗口大小改变的回调函数,在这里可以依据窗口的宽高,设置视口大小。同时生产透视投影,初始化投影矩阵. 做完这一步就可以开始我们的绘制了,下面看最后一个函数RenderScene,用来绘制窗口内容,具体如下:

void RenderScene(void)
{
    //1.用当前清除色,清除窗口
    glClear(GL_COLOR_BUFFER_BIT);
    //2.模型视图压栈
    modelViewMatrix.PushMatrix();  
      //Z轴平移viewZ 距离
    modelViewMatrix.Translate(0.0f, 0.0f, viewZ);
    //3.纹理替换矩阵着色器
    /* 
        参数1:GLT_SHADER_TEXTURE_REPLACE(着色器标签)
        参数2:模型视图投影矩阵
        参数3:纹理层
    */
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
    //4.绑定纹理
    /*
        参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
        参数2:需要绑定的纹理
    */
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]);
    floorBatch.Draw();
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]);
    ceilingBatch.Draw();
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]);
    leftWallBatch.Draw();
    rightWallBatch.Draw();
    modelViewMatrix.PopMatrix();
    //6.缓存区交换
    glutSwapBuffers();
}

       这里需要注意一下,绘制完毕以后需要交换一下缓存区,因为我们采用的是双缓冲区绘制。写到这一步我们基本都画面已经绘制完毕了,最后再看一下怎么实现前后移动的视觉效果,这里我们通过键盘输入前后按键来实现前后移动的视觉效果,具体代码如下:

//前后移动视口来对方向键作出响应
void SpecialKeys(int key, int x, int y){
    if(key == GLUT_KEY_UP)
        //移动的是深度值,Z
        viewZ += 0.5f;
    if(key == GLUT_KEY_DOWN)
        viewZ -= 0.5f;
       //更新窗口,即可回调到RenderScene函数里
    glutPostRedisplay();
}

       好了,到这里这个案例就已经写完了 这个案例主要是告诉大家纹理的使用,重点是纹理坐标的设置以及相关API的灵活运用。