1.矩形纹理
对于二维纹理图像来说,另一个有用的选项是纹理目标:GL_TEXTURE_RECTANGLE.
纹理特点:不能进行MIP贴图,意味着我们只能够加载glTexImage2D的第0层。 纹理坐标不是标准化的。这就意味着纹理坐标实际上是对像素寻址,而不是从0到1的范围覆盖图像的。
注意纹理坐标不能重复,并且不能支持压缩。
例如:纹理坐标(5,19)实际上是图像中从左起6个像素以及从上面起第20个像素。
加载矩形纹理
gltWriteTGA函数将屏幕图像保存为一个Targa文件。
我们必须使用GL_NEAREST或者GL_LINEAR过滤器模式。
//前后的对比,一个是加载2D纹理,一个是加载矩形纹理
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 读入纹理位
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
//用于检查MIP贴图纹理过滤器
if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
bool LoadTGATextureRect(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 读入纹理位
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
//对纹理属性进行设置
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, magFilter);
//设置像素的存储格式
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//加载2D图像到矩形纹理
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
return true;
}
注意上面加载纹理的过程,基本上也就这样:
读入纹理位
设置纹理参数
设置像素存储模式
加载纹理
检验mip贴图纹理过滤器(根据纹理情况而定)
下面我们在主代码中看一看矩形纹理是怎么设置的:
首先是在SetUpRC当中对矩形纹理的设置。
//对矩形纹理的设置
int x = 500;
int y = 155;
int width = 300;
int height = 155;
logoBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
//注意纹理坐标和实际坐标的初始点完全不一致
// 左上角的坐标设置
logoBatch.MultiTexCoord2f(0, 0.0f, height);
logoBatch.Vertex3f(x, y, 0.0);
// 左下角的坐标设置
logoBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
logoBatch.Vertex3f(x, y - height, 0.0f);
// 右下角的坐标设置
logoBatch.MultiTexCoord2f(0, width, 0.0f);
logoBatch.Vertex3f(x + width, y - height, 0.0f);
// 右上角的坐标设置
logoBatch.MultiTexCoord2f(0, width, height);
logoBatch.Vertex3f(x + width, y, 0.0f);
logoBatch.End();
// 生成纹理贴图
glGenTextures(4, uiTextures);
// Load the Marble
glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
// Load Mars
glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR, GL_CLAMP_TO_EDGE);
// Load Moon
glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR, GL_CLAMP_TO_EDGE);
// 绑定纹理
glBindTexture(GL_TEXTURE_RECTANGLE, uiTextures[3]);
//并且使用写好的加载纹理的函数
LoadTGATextureRect("OpenGL-Logo.tga", GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE);
//使用对应的渲染器进行渲染
rectReplaceShader = gltLoadShaderPairWithAttributes("RectReplace.vp", "RectReplace.fp",
2, GLT_ATTRIBUTE_VERTEX, "vVertex", GLT_ATTRIBUTE_TEXTURE0, "vTexCoord");
//得到统一值
locRectMVP = glGetUniformLocation(rectReplaceShader, "mvpMatrix");
locRectTexture = glGetUniformLocation(rectReplaceShader, "rectangleImage");
就像上面所示的,主要是设置好矩形纹理的坐标、加载纹理以及对渲染器进行设置。最后得到对渲染器当中统一值的控制。
// 创建一个矩阵,不用每一帧都计算的。
// 这个程序的中心目的是,在屏幕右下角显示OPENGL的标志
//在屏幕空间进行2D绘制的时候,普遍的一个做法是创建一个和屏幕大小
//匹配的正投影矩阵。我们选择让坐标系和屏幕上的纹理匹配。但是
//是将原点设置到左下角。(纹理坐标的原点)
M3DMatrix44f mScreenSpace;
m3dMakeOrthographicMatrix(mScreenSpace, 0.0f, 800.0f, 0.0f, 600.0f, -1.0f, 1.0f);//创建一个正交矩阵
// 注意:打开混合模式,关闭深度测试。
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
//对rectReplaceShader当中的统一值进行设置。
glUseProgram(rectReplaceShader);
glUniform1i(locRectTexture, 0);
glUniformMatrix4fv(locRectMVP, 1, GL_FALSE, mScreenSpace);
glBindTexture(GL_TEXTURE_RECTANGLE, uiTextures[3]);
logoBatch.Draw();
在实际渲染当中的代码。
#version 140
// 输入每个顶点的坐标以及纹理坐标
in vec4 vVertex;
in vec2 vTexCoord;
//模型投影矩阵
uniform mat4 mvpMatrix;
// 传递给片段着色器程序
smooth out vec2 vVaryingTexCoord;
void main(void)
{
// 仅仅传递一下
vVaryingTexCoord = vTexCoord;
// 集合变换
gl_Position = mvpMatrix * vVertex;
}
顶点着色器代码如上。
#version 140
out vec4 vFragColor;
//统一值的矩形取样纹理
uniform sampler2DRect rectangleImage;
//由顶点着色器程序传递的纹理坐标
smooth in vec2 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(rectangleImage, vVaryingTexCoord);
}
片段渲染器代码。
2.立方体贴图
立方体贴图是作为一个单独的纹理对象看待的,但是它由组成立方体的6个面的6个正方形(必须是正方形!!!)的2D图像组成的。立方体贴图的应用范围包括:3D光线贴图、反射和高精度环境贴图。
实质上一个立方体贴图是投影到一个对象上的,就像这个立方体贴图是包围这个对象一样。
(1)加载立方体贴图
立方体贴图有以下六个枚举值,这些值可以传递到glTexImage2D:
GL_TEXTURE_CUBE_MAP_POSITIVE_X
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
GL_TEXTURE_CUBE_MAP_POSITIVE_Y
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
GL_TEXTURE_CUBE_MAP_POSITIVE_Z
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
这些常量展示了包围被贴图物体立方体表面的场景坐标方向。
在实际使用的时候,在全局变量当中如下声明:
// Six sides of a cube map
const char *szCubeFaces[6] = { "pos_x.tga", "neg_x.tga", "pos_y.tga", "neg_y.tga", "pos_z.tga", "neg_z.tga" };
GLenum cube[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };
立方体贴图最普遍的用法就是创建一个反应它周围镜像的对象。一个立方体贴图被应用到一个球体上,创建了镜面表面的外观,并且同样的立方体贴图也应用到天空盒上面,这个天空盒创建了反射后的背景。
天空盒使用GLTools当中的gltMakeCube进行绘制,这个函数所做的只是用组成一个指定半径的立方体的三角形来填充GLBatch容器。
gltMakeCube(cubeBatch, 20);
//选择一个在每个方向到原点距离都为20个单位长度的立方体。
这个函数将2D纹理坐标分配给GLT_ATTRIBUTE_TEXTURE0属性槽当中。这样在立方体的每个表面上都会应用一个2D图像。不过这样做不能满足对于立方体贴图的要求,我们需要的是代表一个向量的3D纹理,沿着这个向量在在这个立方体贴图上进行纹理单元采样。GLBatch只支持2D纹理,所以它在盒子的外面是无法使用的。
解决办法就是:编写一个自定义的顶点着色器,用它为我们计算纹理坐标。
实际上,这样只是简单第指定立方体的每个角在顶点空间中也是一个从立方体的中心指向这个位置的向量。我们要做的就是对这个向量进行标准化。而我们已经有了一个现成的立方体贴图坐标。
顶点着色器
#version 130
// 输入每个点的位置
in vec4 vVertex;
uniform mat4 mvpMatrix; // Transformation matrix
// 传递给片段程序的纹理坐标
varying vec3 vVaryingTexCoord;
void main(void)
{
//规范化向量,传递纹理坐标
vVaryingTexCoord = normalize(vVertex.xyz);
// Don't forget to transform the geometry!
gl_Position = mvpMatrix * vVertex;
}
片段着色器
#version 130
out vec4 vFragColor;
uniform samplerCube cubeMap;
varying vec3 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(cubeMap, vVaryingTexCoord);
}
我们在立方体贴图上使用mip贴图的时候,沿着两个面结合的边缘常常会出现裂缝。OPENGL内部会调整自己的过滤规则,启用GL_TEXTURE_CUBE_MAP_SEAMLESS时帮助消除这些缝隙。
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
(2)创建反射
首先必须使用表面法线和指向顶点的向量在着色器中创建一个视觉坐标系中的反射向量。另外,为了获得一个真实的反射,还要考虑相机的方向。从GLFrame类中提取照相机的旋转矩阵并且进行转置。然后将其作为统一值,与另外一个变换矩阵一起提供给着色器。(用来对前述的反射向量进行旋转,这个反射香辣实际上就是立方体纹理坐标)一起提供给着色器。
顶点着色器
#version 130
// Incoming per vertex... position and normal
in vec4 vVertex;
in vec3 vNormal;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
uniform mat4 mInverseCamera;
// 传递给片段程序的纹理坐标
smooth out vec3 vVaryingTexCoord;
void main(void)
{
// 视觉空间中的法线
vec3 vEyeNormal = normalMatrix * vNormal;
// 视觉空间中的顶点位置
vec4 vVert4 = mvMatrix * vVertex;
vec3 vEyeVertex = normalize(vVert4.xyz / vVert4.w);
//反射向量获取
vec4 vCoords = vec4(reflect(vEyeVertex, vEyeNormal), 1.0);
// 翻转照相机进行旋转
vCoords = mInverseCamera * vCoords;
vVaryingTexCoord.xyz = normalize(vCoords.xyz);
// 几何图形进行变换
gl_Position = mvpMatrix * vVertex;
}
片段程序使用插值立方体贴图纹理坐标对立方体贴图进行采样并且将其应用到片段上。
片段程序:
#version 130
out vec4 vFragColor;
uniform samplerCube cubeMap;
smooth in vec3 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(cubeMap, vVaryingTexCoord.stp);
}