OpenGL/OpenGL ES入门: 纹理应用 - 纹理坐标及案例解析(金字塔)

4,873 阅读8分钟

系列推荐文章:
OpenGL/OpenGL ES入门:图形API以及专业名词解析
OpenGL/OpenGL ES入门:渲染流程以及固定存储着色器
OpenGL/OpenGL ES入门:图像渲染实现以及渲染问题
OpenGL/OpenGL ES入门:基础变换 - 初识向量/矩阵
OpenGL/OpenGL ES入门:纹理初探 - 常用API解析
OpenGL/OpenGL ES入门: 纹理应用 - 纹理坐标及案例解析(金字塔)
OpenGL/OpenGL ES入门: 顶点着色器与片元着色器(OpenGL过渡OpenGL ES)
OpenGL/OpenGL ES入门: GLKit以及API简介
OpenGL/OpenGL ES入门: GLKit使用以及案例

纹理应用

在上一篇文章OpenGL/OpenGL ES 纹理初探 - 常用API解析中,我们讲述了纹理相关常用的API。加载纹理只是在几何图形上应用纹理的第一步。最低限度我们必须同时提供纹理坐标,并设置纹理坐标环绕模式和纹理过滤。最后,我们可以选择对纹理进行Mip贴图,以提高纹理贴图性能和/或视觉质量。

纹理坐标

总体上说,通过为每个顶点指定一个纹理坐标而直接在几何图形上进行纹理贴图的。纹理坐标要么是指定为着色器的一个属性,要么通过算法计算出来。

纹理贴图中的纹理单元是作为一个更加抽象(经常是浮点值)的纹理坐标,而不是作为内存位置(在像素图中则是这样)进行寻址的。典型情况下,纹理坐标是0.0~1.0之间的浮点值指定的。

纹理坐标命名为s、t、r和q(与顶点坐标x、y、z和w类似),支持从一维到三维的纹理坐标,并且可以选择一种对坐标进行缩放的方法。

q坐标对于几何图形坐标w。这是一个缩放因子,作用于其他纹理坐标。也就是说,实际上所使用的纹理坐标是s/q、t/q和r/q。在默认情况下,q设置为1.0。

着重了解2D纹理坐标,在xy轴上,范围为0~1之间。使用纹理坐标获取纹理颜色叫做采用(Sampling)。 纹理坐标起始于(0,0),也就是纹理图片的左下角,终止于(1,1)。即纹理图片的右上角

上图中:三角形上顶点的纹理坐标为(0.5,1)

通过下图,体会一下纹理坐标的映射:

金字塔案例

下面将通过金字塔案例讲解一下纹理的使用,效果图如下:

SetupRC函数

设置背景色,初始化等

void SetupRC()
{
    // 设置背景色
    glClearColor(0.7, 0.7, 0.7, 1.0);

    // 初始化shaderManager
    shaderManager.InitializeStockShaders();

    // 开启深度测试
    glEnabel(GL_DEPTH_TEST);

    // 分配纹理对象 
    /*
    参数1: 纹理对象个数
    参数2: 纹理对象指针
    */
    glGenTextures(1, &textureID);

    // 绑定纹理
    /*
    参数1: 纹理状态2D
    参数2: 纹理对象
    */
    glBindTexture(GL_TEXTURE_2D, textureID);

    // 将TGA文件加载为2D纹理
    /*
    参数1: 纹理文件名称
    参数2和参数3: 需要的缩小和放大过滤器
    参数4: 纹理坐标环绕模式
    */
    LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);

    // 创造金字塔pyramidBatch
    MakePyramid(pyramidBatch);

    // 相机frame MoveForward(平移)
    /*
    参数1:Z,深度(屏幕到图形的Z轴距离)
    */
    cameraFrame.MoveForward(-10);
}

加载纹理文件LoadTGATexture

// 将TGA文件加载为2D纹理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;
    
    //1、读纹理位,读取像素
    /*
    参数1:纹理文件名称
    参数2:文件宽度地址
    参数3:文件高度地址
    参数4:文件组件地址
    参数5:文件格式地址
    返回值:pBits,指向图像数据的指针
    */
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if(pBits == NULL)
        return false;
    
    //2、设置纹理参数
    /*
    参数1:纹理维度
    参数2:为S/T坐标设置模式
    参数3:wrapMode,环绕模式
    */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    /*
    参数1:纹理维度
    参数2:线性过滤
    参数3:wrapMode,过滤模式
    */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    

    //3.载入纹理
    /*
    参数1:纹理维度
    参数2:mip贴图层次
    参数3:纹理单元存储的颜色成分(从读取像素图是获得)
    参数4:加载纹理宽
    参数5:加载纹理高
    参数6:加载纹理的深度
    参数7:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
    参数8:指向纹理图像数据的指针
    */
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
                 eFormat, GL_UNSIGNED_BYTE, pBits);
    
    //使用完毕释放pBits
    free(pBits);
    
    //4.加载Mip,纹理生成所有的Mip层
    //参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    glGenerateMipmap(GL_TEXTURE_2D);
 
    return true;
}

绘制金字塔(获取定点、纹理坐标)

-------- 前情导入 ---------
1、 设置法线
void Normal3f(GLfloat x, GLfloat y, GLfloat z);
Normal3f: 添加一个表面发现(法线坐标与Vertex顶点坐标中的Y轴一致)
表面法线是有方向的向量,代表表面或顶点面对的方向(相反的方向)。在多数的光照模式下必须使用

2、 设置纹理坐标
void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
参数2:s: 对应顶点坐标中的x坐标
参数3:t: 对应顶点坐标中的y
(s,t,r,q对应顶点坐标的x,y,z,w)
pyramidBatch.MultiTexCoord2f(0,s,t);

3、 设置顶点坐标
void Vertex3f(GLfloat x, GLfloat y, GLfloat z);
void Vertex3fv(M3DVector3f vVertex);
向三角形批次类添加顶点数据(x,y,z);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);

4、获取从三点找到一个坐标(三点确定一个面)
void m3dFindNormal(result, point1, point2, point3);
参数1:结果
参数2~4:3个顶点数据

金字塔坐标解析:

在坐标系中绘制一个金字塔,坐标原点位于金字塔中心

顶点坐标
塔顶坐标(0.0, 1.0, 0.0)
vBackLeft(-1.0, -1.0, -1.0)
vBackRight(1.0. -1.0, -1.0)
vFrontRight(1.0, -1.0, 1.0)
vFrontLeft(-1.0, -1.0, 1.0)

看下面图片描述了金字塔底部两个三角形的纹理坐标

纹理坐标
三角形A纹理坐标
vBackLeft(0.0, 0.0, 0.0)
vBackRight(0.0, 1.0, 0.0)
vFrontRight(0.0, 1.0, 1.0)

三角形B纹理坐标
vFrontLeft(0.0, 0.0, 1.0)
vBackLeft(0.0, 0.0, 0.0)
vFrontRight(0.0, 1.0, 1.0)

//绘制金字塔
void MakePyramid(GLBatch& pyramidBatch)
{
    // 通过pyramidBatch组建三角形批次
    /*
      参数1:类型
      参数2:顶点数
      参数3:这个批次中将会应用1个纹理
      注意:如果不写这个参数,默认为0,表示应用1个纹理
     */
    pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
    
    //塔顶
    M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
    M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
    M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
    M3DVector3f vBackLeft = { -1.0f,  -1.0f, -1.0f };
    M3DVector3f vBackRight = { 1.0f,  -1.0f, -1.0f };
    M3DVector3f n;
    
    //金字塔底部
    //底部的四边形 = 三角形A + 三角形B
    //三角形A = (vBackLeft, vBackRight, vFrontRight)
    
    //1.找到三角形A 法线
    m3dFindNormal(n, vBackLeft, vBackRight, vFrontRight);
   
    //vBackLeft
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vBackRight
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    //vFrontRight
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    
    //三角形B =(vFrontLeft,vBackLeft,vFrontRight)
   
    //1.找到三角形B 法线
    m3dFindNormal(n, vFrontLeft, vBackLeft, vFrontRight);
    
    //vFrontLeft
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //vBackLeft
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vFrontRight
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3fv(vFrontRight);

    
    // 金字塔前面
    //三角形:(Apex,vFrontLeft,vFrontRight)
    m3dFindNormal(n, vApex, vFrontLeft, vFrontRight);
   
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    //金字塔左边
    //三角形:(vApex, vBackLeft, vFrontLeft)
    m3dFindNormal(n, vApex, vBackLeft, vFrontLeft);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //金字塔右边
    //三角形:(vApex, vFrontRight, vBackRight)
    m3dFindNormal(n, vApex, vFrontRight, vBackRight);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    //金字塔后边
    //三角形:(vApex, vBackRight, vBackLeft)
    m3dFindNormal(n, vApex, vBackRight, vBackLeft);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //结束批次设置
    pyramidBatch.End();
}

RenderScene函数

void RenderScene(void)
{
    //1.颜色值&光源位置
    static GLfloat vLightPos [] = { 1.0f, 1.0f, 0.0f };
    static GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f };
    
    //2.清理缓存区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //3.当前模型视频压栈
    modelViewMatrix.PushMatrix();
    
    //添加照相机矩阵
    M3DMatrix44f mCamera;
    //从camraFrame中获取一个4*4的矩阵
    cameraFrame.GetCameraMatrix(mCamera);
    //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将照相机矩阵 与 当前模型矩阵相乘 压入栈顶
    modelViewMatrix.MultMatrix(mCamera);
    
    //创建mObjectFrame矩阵
    M3DMatrix44f mObjectFrame;
    //从objectFrame中获取矩阵,objectFrame保存的是特殊键位的变换矩阵
    objectFrame.GetMatrix(mObjectFrame);
    //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将世界变换矩阵 与 当前模型矩阵相乘 压入栈顶
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //4.绑定纹理,因为我们的项目中只有一个纹理。如果有多个纹理。绑定纹理很重要
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    /*5.点光源着色器
     参数1:GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF(着色器标签)
     参数2:模型视图矩阵
     参数3:投影矩阵
     参数4:视点坐标系中的光源位置
     参数5:基本漫反射颜色
     参数6:图形颜色(用纹理就不需要设置颜色。设置为0)
     */
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
                                 transformPipeline.GetModelViewMatrix(),
                                 transformPipeline.GetProjectionMatrix(),
                                 vLightPos, vWhite, 0);
    
    //pyramidBatch 绘制
    pyramidBatch.Draw();
    
    //模型视图出栈,恢复矩阵(push一次就要pop一次)
    modelViewMatrix.PopMatrix();
    
    //6.交换缓存区
    glutSwapBuffers();
}

ShutdownRC函数

void ShutdownRC(void)
{
    // 清理…例如删除纹理对象
    glDeleteTextures(1, &textureID);
}

SpecialKeys函数

void SpecialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_UP)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    
    glutPostRedisplay();
}

ChangeSize函数

void ChangeSize(int w, int h)
{
    //1.设置视口
    glViewport(0, 0, w, h);
    
    //2.创建投影矩阵
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
  
    //viewFrustum.GetProjectionMatrix()  获取viewFrustum投影矩阵
    //并将其加载到投影矩阵堆栈上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //3.设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)
    //初始化GLGeometryTransform 的实例transformPipeline.通过将它的内部指针设置为模型视图矩阵堆栈 和 投影矩阵堆栈实例,来完成初始化
    //当然这个操作也可以在SetupRC 函数中完成,但是在窗口大小改变时或者窗口创建时设置它们并没有坏处。而且这样可以一次性完成矩阵和管线的设置。
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

本篇主要描述了关于纹理方面的代码,后面的几个函数RenderScene、SpecialKeys、ChangeSize等在前几篇文章中都详细介绍过,所以这里就一笔带过。

下面整理了一张流程图,仅供大家参考: