拓幻图形学工程师教学手册(第二讲)|一字一字敲出OpenGL学习教程

拓幻图形学工程师教学手册(第二讲)|一字一字敲出OpenGL学习教程

首页战争策略代号三幻X更新时间:2024-09-22

上一讲已经介绍了基础的OpenGL知识和绘制方面的内容。示例代码都会在我们公司Github找到。Github请搜索Tillusory可以看到。代码都是有注释的,运行环境是Mac OSX的Xcode。win版本的童鞋可以下下来之后放到VS中跑,环境搭建资料很多,有问题的可私信公众号。

4. 纹理映射

4.1 基本概念

上一讲提到绘制太阳系,那么只是学了上一讲,最多就是画几个球,移动位置,做各种旋转运动等等,这怎么是太阳系。太阳,和各大行星至少要有表面图案吧,那么怎么把这些图案覆盖到球体表面呢?

先来了解下纹理,纹理说白了其实就是一张图,将这张图映射到3D模型上去,增加真实感。图形学硬件要求这些图片像素必须是2的整数幂,并非所有硬件的要求,确保安全而已。

并且,在这里怕大家混淆,讲清楚一个概念。众所周知,屏幕上每个点我们称之为像素(Pixel),但是纹理上的每个点我们不称像素,而是叫纹素(Texel)。使用纹素这个术语,而不是像素来表示纹理对象中的显示元素,主要是为了强调纹理对象的应用方式。纹理对象通常是通过纹理图片读取到的,这个数据保存到一个二维数组中,这个数组中的元素称为纹素(texel),纹素包含颜色值和alpha值。这里先提一下alpha值,颜色参数还记得是rgb吗?其实还有第四个参数,a,这个值就是alpha,控制透明度的。其实这一块搞过U3D开发的其实并不陌生,能讲的也很多,深度测试啊,zbuffer啊,先看下资料吧,后边有需要我细讲。

纹素的坐标我们也不用X,Y来表示,我们使用S,T来表示。ST组成纹理坐标,要想获取纹理对象中的纹素,需要使用纹理坐标(texture coordinate)指定。S,T也可以用U,V来表示,纹理坐标有时也成uv坐标。接触过3D模型的不陌生uv图这种东西,每个模型对应一个uv图,对着uv图设计贴图等等。uv坐标其实就是我们这里说的st坐标,也就是纹理坐标。坐标如下图:

其中左下角为(0,0),右上角为(1,1)。S和T为大于等于0小于等于1的数。通过指定纹理坐标,可以映射到纹素。例如一个256x256大小的二维纹理,坐标(0.5,1.0)对应的纹素即是(128,256)。(256x0.5 = 128, 256x1.0 = 256)。纹理映射时只需要为物体的顶点指定纹理坐标即可,其余部分由片元着色器插值完成。

4.2 读取纹理图片

我们如何在示例代码中添加读取纹理图片的代码,这里我给出一个从BMP文件中读取纹理贴图的方法。这个方法各位可以再Github中看到一个叫bmptotexture.cpp的文件中找到。方法名叫BmpToTexture。使用方法如下:

宽高要求是2的整数幂。如果不是BMP文件也可以使用一些工具转成BMP,比如PS,或者别的工具。

4.3 WRAP参数

纹理坐标都是0-1,那么如果超过0-1怎么办。比如刚刚说的纹理坐标(0.5, 1.0)到纹素的映射,恰好为(128,256)。如果纹理坐标超出[0,0]到[1,1]的范围该怎么处理呢?

这时就用到一个叫WRAP的参数,专门处理这种情况。

先看下具体设置wrap参数的代码:

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap );

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap );

其中wrap分为以下两种:

GL_REPEAT:坐标的整数部分被忽略,重复纹理,这是OpenGL纹理默认的处理方式。

GL_CLAMP: 坐标会被截断到[0,1]之间。结果是坐标值大的被截断到纹理的边缘部分,形成了一个拉伸的边缘(stick pattern)。

如下图,左边为GL_CLAMP,右图为GL_REPEAT。

4.4 Filter参数

当使用纹理坐标映射到纹素数组时,正好得到对应纹素的中心位置的情况,很少出现。例如上面的(0.5,1.0)对应纹素(128,256)的情况是比较少的。如果纹理坐标映射到纹素位置(127.34,255.14)该怎么办呢 ?

这是就要用到另一个参数叫filter参数。同样先看下设置这个参数的代码:

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter );

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter );

其中filter分为以下两种:

GL_NEAREST:使用最佳逼近点来获取纹素,也就是最近邻滤波( nearest neighbor filtering)。这种方式容易导致走样误差,明显有像素块的感觉。

GL_LINEAR:使用线性滤波方法(linear filtering),它使用纹素位置(127.34,255.14)附近的一组纹素的加权平均值来确定最终的纹素值。例如使用 ( (127,255), (128,255), (127,254) 和 (128,254) )这四个纹素值的加权平均值。权系数通过与目标点的距离远近反映,距离越近,权系数越大,即对最终的纹素值影响越大。

区别如下图:

4.5 纹理环境(纹理模式)

根据纹理坐标从纹理中取出每个片元对应的颜色、片元颜色,它们如何合成成新的颜色再沿渲染管线向后传递决定于纹理模式和纹理基准内部格式(一般都射为RGBA)。简单来说就是纹理环境或者模式告诉OpenGL当它得到纹素颜色后该怎么做。

看一下设置代码:

glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode );

其中mode分为以下四种:

GL_REPLACE:纹理颜色替代,纹理颜色(连同alpha分量)替换片元颜色成为新的片元颜色。

GL_MODULATE:纹理颜色调制,纹理颜色(连同alpha分量)和片元颜色相乘成为新的片元颜色。

GL_DECAL:纹理颜色贴花,使用该模式需要先使用glTexEnvfv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,envColor)指定一个纹理模式颜色。新的片元颜色(RGB分量)= 纹理颜色alpha * 纹理模式颜色 (1-纹理颜色alpha)*片元颜色。新的片元颜色alpha不变。

GL_BLEND:纹理颜色混合,同上也需要纹理模式颜色。新的片元颜色(RGB分量)为((1.0,1.0,1.0)-纹理颜色)*片元颜色 纹理颜色 * 纹理模式颜色 。新的片元颜色alpha为 原alpha*纹理alpha。

4.6 加载纹理

如下设置并加载纹理:

这个函数将纹素数组从CPU传至GPU并且设置为当前纹理。在处理单一纹理时,你可以用,负责效率非常低。多纹理时可以参见下面会讲到的纹理绑定。

4.7 使用纹理

首先调用glEnable( GL_TEXTURE_2D ),来启用2D纹理,然后绘制图形,并且为每个顶点指定ST坐标。最后调用glDisable( GL_TEXTURE_2D ).

这是个最简单的例子,图形使用OpenGL绘制的。

4.8 纹理对象及绑定

OpenGL中glTexImage2D这个函数每次调用就会涉及到从CPU传递纹素数组给GPU,效率会非常低下。如果你只有一处使用单张纹理,那是没问题的,如果你有多处并且使用多张纹理,可想而知,卡不死你。这个时候你需要绑定纹理对象(Texture Objects)。纹理对象是将你的纹理留存在显卡中,并且反复利用,这比反复加载快多了。

创建一个纹理对象,首先先要生成纹理名称,然后再将纹理对象绑定到纹理数据上。生成纹理名称我们用glGenTextures( 1, &tex0 ),将纹理对象绑定到纹理数据上,我们用glBindTexture( ) 。每次执行glBindTexture时,你就创建了一个纹理对象。

下面例子是多张纹理时如何处理的代码:

第一次先创建两个纹理名称:tex0,tex1.并且将tex0的纹理作为当前纹理进行绑定到纹理对象,然后设置一系列参数。

然后进行tex1的纹理对象创建并绑定,并且设置参数。

之后在使用时,也就是示例代码的Display()函数中这样调用:

最后也可以调用glDeleteTextures()进行释放资源,毕竟纹理资源是有限的。

以上就是纹理对象创建并绑定,到使用的过程。使用纹理对象通常是支持纹理的最快的方式,会出现高性能的结果,这是因为它绑定(重新使用)一个现有纹理对象的速度,总是比使用函数glTexImage2D()来重新加载一个纹理的速度更快。

记得之前画太阳系的project时,有朋友问现在正在克莱姆森读博士的Doc. Lee, 说为啥他的太阳系卡成狗,李博士立马知道问题出在每次重新加载纹理上。请记得,实际工程中,多纹理时千万不要每次重新加载,否则不熟悉图形学的人很难找出卡顿,闪屏等原因,别坑队友啊。

下面我们具体解释下纹理绑定的原理吧。感兴趣的看下。熟悉OpenGL的人一般听过渲染上下文这个概念,也就是Context。渲染上下文包含了即将被送去显示的所有纹理,颜色,光照,或者点的移动等特征信息。OpenGL的绑定其实可以理解成“附上(attach)”或者“Docking”,将OpenGL对象绑定到上下文中去。然后你可以将其指派到上下文的一些特征信息中去。上下文这一块内容,还是比较有研究价值的,比如以后要用到OpenGL ES,多线程渲染,就需要进行共享上下文等等。我之前的项目,因为使用Unity进行渲染,之后要和iOS源生进行交互,但是一个在主线程,一个不在,这个时候不在一个线程是不能够进行上下文统一,进行共享纹理等操作的,这个坑我踩了很久,最后是U3D中华区技术总监在UUG大会上这样和我说的,我只能选择放弃~

如图所示:

另外如何在Shader中使用纹理,我们到着色器时再说。

这一讲先写这么多,上次有粉丝私信说第一讲内容太多,又没源码。这次就讲纹理,并附上github,里面有示例代码和太阳系源码给大家参考。下一讲我们将OpenGL光照。

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved