1,
为了实现纹理贴图我们需要做三件事:将一张贴图加载到OpenGL中,提供纹理坐标和顶点(将纹理对应匹配到顶点上),并使用纹理坐标从纹理中进行取样操作取得像素颜色。
由于三角形会被缩放、旋转、平移变换导致最后会以不同的结果投影显示到屏幕上,而且由于camera的不同操作看上去也会很不一样。GPU要做的就是让纹理紧跟三角形图元顶点的移动使其看上去真实(如果纹理看上去明显游离在三角形上产生错位就不真实了)。
为实现这个效果需要为每个顶点提供一系列纹理坐标。在GPU光栅化三角形阶段,会对纹理坐标进行插值计算并覆盖到整个三角形面上,并且在片段着色器中开发者要将这些坐标跟纹理进行匹配。这个操作叫‘取样‘,取样的结果叫‘纹素‘(纹理中的一个像素)。
2,
纹理坐标并不是在纹理中纹素的坐标,哪样局限性太大,应为这样如果要同一张高宽不同的纹理替代一张纹理我们得更新所有顶点坐标来匹配新的纹理图片。
因此,纹理坐标是定义在‘纹理空间‘的,也就是定义在单位化的[0,1]范围内。也就是说纹理坐标事实上是个分数,纹理的宽高乘以相应的比列分数就可以算出纹素在纹理中的坐标。
通常的约定是使用U和V作为纹理空间中的轴线,U对应于2D坐标系的X轴,V对应于Y轴。
在对纹理坐标进行插值时多数的像素可以获得原图片中对应相同的纹理坐标(因为他们相对于顶点的相对位置相同)。
和纹理映射相关的另一个重要概念是‘过滤‘。我们已经讨论了怎样将纹理坐标(这是个0到1之间的分数!)映射到纹素上,纹理贴图中纹素的坐标总是以整数定义的,但是如果纹理坐标映射到纹素上的坐标为(152.34,745.14)怎么办?不明智的方案是将这个坐标舍去小数变为(152,745)。这种方法虽然可以有效果但是在某些情形下效果会很差。一个更好的办法是选取该坐标周围纹素2x2的4个坐标 ( (152,745), (153,745), (152,744) 和 (153,744) ) 并根据他们的颜色做线性插值。线性插值必须体现出坐标(152.34,745.14)和四个坐标的相对距离来。距离近的点的颜色对最终纹素的颜色值影响大,越远影响越小,这样就比开始的方法简单多了。
选择最终纹素的方法叫做‘过滤‘,最简单的实现小数纹理坐标取整的方法叫做‘最邻近过滤‘,更复杂一点的方法叫做‘线性过滤‘,另外一种可能遇到的最邻近过滤方法叫做‘点过滤‘。OpenGL支持几种不同类型的过滤方式可以选择。通常效果好的过滤方式需要GPU更强大计算能力并且可能对帧率产生影响,选择不同的过滤方式就需要权衡对过滤效果的需求和对目标设备能力的要求了。
3,纹理映射在OpenGL中实现的方式:
OpenGL中的纹理贴图意味着要操作四个概念之间错综复杂的联系:着色器中的纹理对象,纹理单元,取样器对象和取样器一致变量。
--------------------------------------
4,
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
glFrontFace()函数告诉OpenGL三角形的顶点是按照顺时针顺序定义的,也就是当你看向三角形的正面时,会发现缓冲器中的三角形顶点是顺时针顺序排列的。
glCullFace()告诉GPU剔除三角形的背面,也就是物体的内表面不需要渲染,只渲染外表面。
最后是开启面剔除本身(默认是关闭的)
5,
sampler_location = glGetUniformLocation(shader_program, "gsampler");
glUniform1i(sampler_location, 0);
先将要使用的纹理单元的索引放到shader中的取样器一致变量里。
这里要注意的是纹理单元的实际索引是在此处使用的,而不是那个OpenGL枚举GL_TEXTURE0,值是不一样的。
6,
m_image.read(m_fileName);
glGenTextures(1, &m_textureObj);
glBindTexture(m_textureTarget, m_textureObj);
glTexImage2D(m_textureTarget, 0, GL_RGBA, m_image.columns(), m_image.rows(), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_blob.data());
glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
void
GLAPIENTRY glGenTextures (GLsizei
n, GLuint *textures);
产生指定数量的纹理对象,并将他们的引用句柄放到GLuint数组指针(第二个参数)中。这里只需要一个纹理对象。
void
GLAPIENTRY glBindTexture (GLenum
target, GLuint
texture);
它告诉OpenGL在后面所有和纹理相关调用中我们所引用的是texture这个绑定的纹理对象,直到有新的纹理对象被绑定。
第一个参数target指定目标纹理为GL_TEXTURE_1D, GL_TEXTURE_2D等等。
void
GLAPIENTRY glTexImage2D (GLenum
target, GLint
level, GLint
internalformat, GLsizei
width, GLsizei
height, GLint
border, GLenum
format, GLenum
type, const
void *pixels);
这个复杂的函数是用来加载纹理对象的主要部分的,也就是纹理数据本身。
目标纹理总是参数中的第一个;
第二个参数是LOD(Level-Of-Detail),一个纹理对象在不同分辨率下可使用同一张纹理。有个概念叫做‘多级渐远纹理(Mip-mapping)‘。每张mip-map有一个不同的LOD索引,最好分辨率时索引为0,并随着分辨率降低索引值增大。目前我们只有一张mip-map所以索引值暂时传递0;
internalformat参数是OpenGL存储纹理的内部格式,比如你可以使用完整的四通道(红,绿,蓝,透明度)传递纹理。但是如果参数是GL_RED的话,你将只得到纹理的红色通道。这里我们使用GL_RGBA来获取颜色完整的纹理图片;
width, height是纹理的宽高;
border是边界;
最后的三个参数定义了导入的纹理数据的来源,分别是格式、类型和内存地址。
void
GLAPIENTRY glTexParameterf (GLenum
target, GLenum
pname, GLfloat
param);
控制纹理采样
glActiveTexture(GL_TEXTURE0);
void
GLAPIENTRY glBindTexture (GLenum
target, GLuint
texture);
现在3d应用已经越来越复杂,我们可能要在渲染循环中的很多绘制回调中使用很多不同的纹理。在每次绘制回调之前我们需要将我们需要的纹理对象绑定到一个纹理单元上,从而可以在片段着色器中进行采样。这里这个绑定函数使用纹理单元的枚举作为一个参数(GL_TEXTURE0, GL_TEXTURE1等等)。
glActiveTexture激活纹理单元,glBindTexture将纹理对象绑定到上面
7 ,
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexText), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexText), (const
GLvoid*)12);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_textureObj);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
主渲染循环中的代码有所添改,在之前启用的用于顶点位置的顶点属性0之后,这里我们启动一个顶点属性1用于纹理坐标。这个和顶点着色器中的layout变量声明是一一对应的。
然后我们调用glVertexAttribPointer函数来定义顶点缓冲器中纹理坐标的位置。
8,
原文:http://www.cnblogs.com/liuhan333/p/6363142.html