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

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

纹理应用

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

纹理坐标

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

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

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

image

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

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

image

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

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

image

金字塔案例

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

image

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个顶点数据

金字塔坐标解析:

image

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

顶点坐标
塔顶坐标(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)

image
//绘制金字塔
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等在前几篇文章中都详细介绍过,所以这里就一笔带过。

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

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容