立方体贴图最普遍的用法是创建一个反映它周围景象的对象。
// CubeMapped.cpp
// OpenGL SuperBible
// Demonstrates applying a cube map to an object (sphere) using
// and using the same map for the skybox.
// Program by Richard S. Wright Jr.
#include // OpenGL toolkit
#include
#include
#include
#include
#include
#include
#include
#ifdef __APPLE__
#include
#else
#define FREEGLUT_STATIC
#include
#endif
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch sphereBatch;
GLBatch cubeBatch;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLuint cubeTexture;
GLint reflectionShader;
GLint skyBoxShader;
GLint locMVPReflect, locMVReflect, locNormalReflect, locInvertedCamera;
GLint locMVPSkyBox;
// 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 };
//////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering
// context.
void SetupRC()
{
GLbyte *pBytes;
GLint iWidth, iHeight, iComponents;
GLenum eFormat;
int i;
// Cull backs of polygons
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glEnable(GL_DEPTH_TEST);
glGenTextures(1, &cubeTexture);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);
// Set up texture maps
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Load Cube Map images
for(i = 0; i < 6; i++)
{
// Load this texture map
pBytes = gltReadTGABits(szCubeFaces[i], &iWidth, &iHeight, &iComponents, &eFormat);
glTexImage2D(cube[i], 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
free(pBytes);
}
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
viewFrame.MoveForward(-4.0f);
gltMakeSphere(sphereBatch, 1.0f, 52, 26);
gltMakeCube(cubeBatch, 20.0f);
reflectionShader = gltLoadShaderPairWithAttributes("Reflection.vp", "Reflection.fp", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");
locMVPReflect = glGetUniformLocation(reflectionShader, "mvpMatrix");
locMVReflect = glGetUniformLocation(reflectionShader, "mvMatrix");
locNormalReflect = glGetUniformLocation(reflectionShader, "normalMatrix");
locInvertedCamera = glGetUniformLocation(reflectionShader, "mInverseCamera");
skyBoxShader = gltLoadShaderPairWithAttributes("SkyBox.vp", "SkyBox.fp", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");
locMVPSkyBox = glGetUniformLocation(skyBoxShader, "mvpMatrix");
}
void ShutdownRC(void)
{
glDeleteTextures(1, &cubeTexture);
}
// Called to draw scene
void RenderScene(void)
{
// Clear the window
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
M3DMatrix44f mCamera;
M3DMatrix44f mCameraRotOnly;
M3DMatrix44f mInverseCamera;
viewFrame.GetCameraMatrix(mCamera, false);
viewFrame.GetCameraMatrix(mCameraRotOnly, true);
m3dInvertMatrix44(mInverseCamera, mCameraRotOnly);
modelViewMatrix.PushMatrix();
// Draw the sphere
modelViewMatrix.MultMatrix(mCamera);
glUseProgram(reflectionShader);
glUniformMatrix4fv(locMVPReflect, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glUniformMatrix4fv(locMVReflect, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniformMatrix3fv(locNormalReflect, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
glUniformMatrix4fv(locInvertedCamera, 1, GL_FALSE, mInverseCamera);
glEnable(GL_CULL_FACE);
sphereBatch.Draw();
glDisable(GL_CULL_FACE);
modelViewMatrix.PopMatrix();
modelViewMatrix.PushMatrix();
modelViewMatrix.MultMatrix(mCameraRotOnly);
glUseProgram(skyBoxShader);
glUniformMatrix4fv(locMVPSkyBox, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
cubeBatch.Draw();
modelViewMatrix.PopMatrix();
// Do the buffer Swap
glutSwapBuffers();
}
// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
if(key == GLUT_KEY_UP)
viewFrame.MoveForward(0.1f);
if(key == GLUT_KEY_DOWN)
viewFrame.MoveForward(-0.1f);
if(key == GLUT_KEY_LEFT)
viewFrame.RotateLocalY(0.1);
if(key == GLUT_KEY_RIGHT)
viewFrame.RotateLocalY(-0.1);
// Refresh the Window
glutPostRedisplay();
}
void ChangeSize(int w, int h)
{
// Prevent a divide by zero
if(h == 0)
h = 1;
// Set Viewport to window dimensions
glViewport(0, 0, w, h);
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 1000.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800,600);
glutCreateWindow("OpenGL Cube Maps");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpecialKeys);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
// Reflection Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#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;
// Texture coordinate to fragment program
smooth out vec3 vVaryingTexCoord;
void main(void)
{
// Normal in Eye Space
vec3 vEyeNormal = normalMatrix * vNormal;
// Vertex position in Eye Space
vec4 vVert4 = mvMatrix * vVertex;
vec3 vEyeVertex = normalize(vVert4.xyz / vVert4.w);
// Get reflected vector
vec4 vCoords = vec4(reflect(vEyeVertex, vEyeNormal), 1.0);
// Rotate by flipped camera
vCoords = mInverseCamera * vCoords;
vVaryingTexCoord.xyz = normalize(vCoords.xyz);
// Don't forget to transform the geometry!
gl_Position = mvpMatrix * vVertex;
}
// Reflection Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
out vec4 vFragColor;
uniform samplerCube cubeMap;
smooth in vec3 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(cubeMap, vVaryingTexCoord.stp);
}
// Skybox Shader
// Vertex Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
// Incoming per vertex... just the position
in vec4 vVertex;
uniform mat4 mvpMatrix; // Transformation matrix
// Texture Coordinate to fragment program
varying vec3 vVaryingTexCoord;
void main(void)
{
// Pass on the texture coordinates
vVaryingTexCoord = normalize(vVertex.xyz);
// Don't forget to transform the geometry!
gl_Position = mvpMatrix * vVertex;
}
// Skybox Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
out vec4 vFragColor;
uniform samplerCube cubeMap;
varying vec3 vVaryingTexCoord;
void main(void)
{
vFragColor = texture(cubeMap, vVaryingTexCoord);
}
立方体贴图作为一个单独的纹理对象看待,但是它由组成的6个面的6个正方形的2D图像组成。立方体贴图的应用范围包括3D光线贴图、反射和高精度环境贴图等。实际上一个立方体贴图是投影到一个对象上的,就像这个立方体贴图是包围着这儿对象一样。
1、加载立方体贴图
立方体贴图新增了一下6个值,这些值可以传递到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
2、创建天空盒
立方体贴图最普遍的用法就是创建一个反映它周围景象的对象。天空盒创建了反射后的背景,即用立方体贴图创建镜面表现的外观。所谓的天空盒只不过是一个带有天空图片的大盒子。另一种观点是将他看成一个贴在大盒子上的天空图片。一个有效的天空盒包含6个图像,这6个图像包括从我们场景中心沿着6个方向轴所看到的场景。
3、创建反射
对天空盒进行渲染非常简单,创建发射也只是更复杂一点点而已。
首先必须使用表面法线和指向顶点的向量在着色器创建一个视觉坐标系的反射向量。另外,为了获得一个真实的反射,还需要考虑照相机的方向。这个反射向量实际上就是立方体贴图纹理坐标。
1、全局变量
GLFrame viewFrame; //创建角色帧
GLFrustum viewFrustum;//投影矩阵容器的创建
GLTriangleBatch sphereBatch; //图元批次声明
GLBatch cubeBatch; //创建图元批次
GLMatrixStack modelViewMatrix;//模型视图矩阵堆桟
GLMatrixStack projectionMatrix;//投影矩阵堆栈
GLGeometryTransform transformPipeline;//管线管理
GLuint cubeTexture;//立方体纹理贴图标识
GLint reflectionShader;//反射着色器标识
GLint skyBoxShader;//天空盒着色器标识//对应从着色器获取到的统一值标识符
GLint locMVPReflect, locMVReflect, locNormalReflect, locInvertedCamera;
GLint locMVPSkyBox;//6个纹理贴图文件名,用一个数组指针定义
const char *szCubeFaces[6] = { "pos_x.tga", "neg_x.tga", "pos_y.tga", "neg_y.tga", "pos_z.tga", "neg_z.tga" };
//定义加载立方体贴图的6个参数
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 };2、函数
1)void SetupRC()
//指针指向读取到纹理数据
GLbyte *pBytes;
//确定贴图文件的图像的宽度、高度和新的缓冲区以及数据格式
GLint iWidth, iHeight, iComponents;
GLenum eFormat;
int i;glCullFace(GL_BACK); //禁用背面光照、阴影和颜色计算及操作,消除不必要的渲染计算
glFrontFace(GL_CCW);//表示窗口坐标上投影多边形的顶点顺序为逆时针方向的表面为正面。
glEnable(GL_DEPTH_TEST);//开启深度测试//生成纹理,指定纹理数量是1和纹理指针
glGenTextures(1, &cubeTexture);
//绑定纹理对象,指定为立方体纹理和纹理指针
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);//设置立方体纹理贴图收缩和扩展的过滤方式
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);//设置s、t和r坐标上的纹理环绕模式为强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或者最后一列进行采样
注意:和真正的3D纹理不同,S、T和R纹理坐标表示的是一个从纹理贴图的中线发出的有符号向量。
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);//把纹理数据改成紧密包装像素数据
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);//循环6次将6个图像加载到一个单独的纹理对象中(前面绑定的cubeTexture)
for(i = 0; i < 6; i++)
{
// Load this texture map
pBytes = gltReadTGABits(szCubeFaces[i], &iWidth, &iHeight, &iComponents, &eFormat);
glTexImage2D(cube[i], 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
free(pBytes);
}//生成Mip层
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);//参考角色帧朝向窗口方向前移4个像素
viewFrame.MoveForward(-4.0f);
//绘制球体和天空盒图元批次
gltMakeSphere(sphereBatch, 1.0f, 52, 26);
gltMakeCube(cubeBatch, 20.0f);//载入“反射”着色器程序,指定了在顶点着色器中的2个属性值,分别是顶点和法向量
reflectionShader = gltLoadShaderPairWithAttributes("Reflection.vp", "Reflection.fp", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");//获得“反射”着色器中的统一值,分别是模型视图投影矩阵、模型视图矩阵、法向量和反转的照相机
locMVPReflect = glGetUniformLocation(reflectionShader, "mvpMatrix");
locMVReflect = glGetUniformLocation(reflectionShader, "mvMatrix");
locNormalReflect = glGetUniformLocation(reflectionShader, "normalMatrix");
locInvertedCamera = glGetUniformLocation(reflectionShader, "mInverseCamera");//载入“天空盒”着色器程序,指定在顶点着色器的2个属性值,分别是顶点和法向量
skyBoxShader = gltLoadShaderPairWithAttributes("SkyBox.vp", "SkyBox.fp", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");//获得“天空盒”着色器中的统一值,为模型视图投影矩阵
locMVPSkyBox = glGetUniformLocation(skyBoxShader, "mvpMatrix");
2)void ShutdownRC(void)
glDeleteTextures(1, &cubeTexture); //删除纹理对象
3)void RenderScene(void)
//清除颜色和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//声明使用的4x4矩阵
M3DMatrix44f mCamera;
M3DMatrix44f mCameraRotOnly;
M3DMatrix44f mInverseCamera;//得到视坐标角色帧的照相机矩阵
viewFrame.GetCameraMatrix(mCamera, false);
//照相机的旋转矩阵
viewFrame.GetCameraMatrix(mCameraRotOnly, true);
//得到照相机旋转矩阵后的转置矩阵,即反射矩阵,用来映射天空盒纹理到球体上
m3dInvertMatrix44(mInverseCamera, mCameraRotOnly);
//模型视图矩阵堆栈压栈单位矩阵
modelViewMatrix.PushMatrix();
//模型视图矩阵栈顶矩阵乘以照相机矩阵替代单位矩阵存放在模型视图矩阵堆栈中
modelViewMatrix.MultMatrix(mCamera);
//使用反射着色器
glUseProgram(reflectionShader);//设置从反射着色器获取到的统一值
glUniformMatrix4fv(locMVPReflect, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glUniformMatrix4fv(locMVReflect, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniformMatrix3fv(locNormalReflect, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
glUniformMatrix4fv(locInvertedCamera, 1, GL_FALSE, mInverseCamera);//开启剔除操作效果;渲染绘制球体图元;再关闭剔除操作效果;
glEnable(GL_CULL_FACE);
sphereBatch.Draw();
glDisable(GL_CULL_FACE);//模型视图矩阵堆栈出栈,避免对后续图元渲染的影响
modelViewMatrix.PopMatrix();//模型视图矩阵堆栈压栈单位矩阵
modelViewMatrix.PushMatrix();
//模型视图矩阵栈顶矩阵乘以照相机旋转矩阵替代单位矩阵存放在模型视图矩阵堆栈中
注意:旋转矩阵是用来对反射向量进行旋转,反射向量实际上就是立方体贴图纹理坐标
modelViewMatrix.MultMatrix(mCameraRotOnly);//使用天空盒着色器
glUseProgram(skyBoxShader);//设置着色器程序的模型视图矩阵统一值
glUniformMatrix4fv(locMVPSkyBox, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());//渲染绘制天空盒图形,即可以看做球体后面的立方体,背景图
cubeBatch.Draw();
modelViewMatrix.PopMatrix();glutSwapBuffers();
Client程序重要就是这两个函数的解析,其他函数可参考之前的解析。接下来重点要解析的是着色器程序(Server)。
1、Reflection着色器程序
Reflection.vp顶点着色器
1)变量
//输入每个顶点的位置坐标和法向量坐标
in vec4 vVertex;
in vec3 vNormal;//统一值声明,1.模型视图投影矩阵2.模型视图矩阵3.法向量矩阵4.反向照相机(矩阵)
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;
}
Reflection.fp片段程序
1)变量
//输出的将要进行光栅化的颜色值
out vec4 vFragColor;
//立方体纹理采样器:将要采样的纹理所绑定的纹理单元
uniform samplerCube cubeMap;
//从顶点着色器传过来的纹理坐标值
smooth in vec3 vVaryingTexCoord;
void main(void)
{
//输出的颜色值是使用纹理坐标进行立方体纹理采样纹理单元得到的颜色
vFragColor = texture(cubeMap, vVaryingTexCoord.stp);
}
2、SkyBox着色器程序
SkyBox.vp顶点程序
1)变量
//输入的顶点坐标值,即client程序图元的顶点坐标
in vec4 vVertex;
//模型视图投影矩阵统一值,可在client程序中读取此值
uniform mat4 mvpMatrix;
//将要传递到片段着色器中的纹理坐标,注意是varying变量
varying vec3 vVaryingTexCoord;
void main(void)
//把顶点坐标归一化,此顶点坐标会传送到片段着色器进行处理
vVaryingTexCoord = normalize(vVertex.xyz);
//几何转换
gl_Position = mvpMatrix * vVertex;
SkyBox.fp片段程序
1)变量
//将要进行光栅化的颜色输出值
out vec4 vFragColor;
//立方体纹理采样器:将要采样的纹理所绑定的纹理单元
uniform samplerCube cubeMap;
//从顶点着色器传递过来的纹理坐标值
varying vec3 vVaryingTexCoord;
2)函数
void main(void)
{
//输出的颜色值是用纹理坐标对纹理单元进行立方体纹理采样得到的颜色值
vFragColor = texture(cubeMap, vVaryingTexCoord);
}
此节学习了立方体纹理和立方体采样的应用,在生成和载入立方体纹理贴图就如在SetupRC中的源码所写,先生成一个纹理对象glGenTextures(1, &cubeTexture),下一步绑定纹理对象glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);注意参数是立方体纹理模式,接下来设置纹理过滤和环绕模式。再循环载入纹理贴图。值得注意的是,此次的立方体贴图的源码和前一节学习的矩阵纹理贴图有一点不同的是,前一节在纹理贴图源码中使用了glUniform1i( locRectTexture,0)函数设置了绑定的纹理单元,而此次的源码并未使用该函数设置纹理,我在此节的理解是立方体纹理贴图载入时,默认设定了绑定的立方体纹理单元。
另外,就是client程序中的旋转转置照相机矩阵的使用和Server程序中以顶点坐标和法向量为参考得到的反射向量的使用,旋转矩阵是用来对反射向量进行旋转,反射向量实际上就是立方体贴图纹理坐标。