渲染世界的OPENGL紋理進階-多重紋理
多重紋理
OPENGL允許我們將獨立的紋理物件繫結到一些可用的紋理單元上,從而提供了將兩個或更多紋理同時應用到幾何圖形上的能力。我們可以對實現進行查詢:
GLint iUnits;
glGetIntegerv(GL_MAX_TEXXTURE_UNITS, &iUnits);
預設情況下,第一個紋理單元為活動的紋理單元。所有紋理繫結操作都會影響當前活動的紋理單元。我們可以通過呼叫以紋理單元識別符號為變數的glActiveTexture來改變當前紋理單元。
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureID) ;
//要切換到第二個紋理單元並且將它繫結到指定的紋理上。
(1)多重紋理座標
通過對紋理座標進行插值,我們可以吧紋理應用到幾何圖形上面。我們還可以計算紋理座標,就像在前面示例當中為天空盒做的一切一樣。或者可以為每個紋理提供獨立的一組紋理座標;畢竟這只不過是在我們批次中增加一組屬性而已。
預設情況下,GLBatch類不會以一個屬性陣列的形式提供任何紋理座標。不過在呼叫以nTextureUnits為引數的Begin函式時,我們最多可以指定4組紋理座標。
void GLBatch::Begin(GLenum primitive, GLuint nVerts, GLuint nTextureUnits=0 );
有兩個函式可以提供紋理座標。第一個函式是CopyTexCoordData2f,速度最快的,因為會一次複製整個一組紋理座標。
void GLBatch::CopyTexCoordData2f(M3DVector2f *vTexCoords, GLuint uiTextureLayer);
第二個函式則是使用較慢的每次一個頂點的介面,與立即模式類似。
我們可以通過兩種方式指定一個二維紋理座標:
void GLBatch::MultiTexCoord2f(GLuint texture, GLclampf s, GLclamf t);
void GLBacth::MultiTexCoordfv(GLuint texture , M3DVector2f vTexCoord);
(2)多重紋理示例
紋理座標可以以幾乎無限多的方式進行組合,有種類繁多的技術採用在一個著色器中一次使用兩個或多個紋理方式。
這個示例是多重紋理的組合演示,針對這個示例,我將會對這個專案的完整程式碼進行詳細分析。
在上一章的反射周圍環境的球體,再加上晦暗紋理貼圖。
我們將這個二維紋理繫結到紋理單元GL_TEXTURE1上,然後用取自晦暗紋理的紋理顏色乘以立方體貼圖紋理的顏色。在晦暗紋理較深的地方,反射會變暗。而在晦暗紋理比較淺或者接近白色的地方,對反射紋理幾乎沒有影響。
#version 130
// 輸入每個頂點的座標以及法線座標
in vec4 vVertex;
in vec3 vNormal;
//為晦暗紋理將要使用的二維紋理座標新增屬性
in vec2 vTexCoords;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
uniform mat4 mInverseCamera;
smooth out vec3 vVaryingTexCoord;
//在新增完二維紋理座標新增屬性之後,我們需要對其進行插值,
//設定一組座標,這組座標能夠在頂點之間平滑的進行插值。
smooth out vec2 vTarnishCoords;
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);
//最後將這些屬性分配給插值變數即可。
vTarnishCoords = vTexCoords.st;
// 幾何變換
gl_Position = mvpMatrix * vVertex;
}
對於反射的頂點著色器程式碼。
注意,上面的頂點著色器當中重要的步驟如下:
//為晦暗紋理將要使用的二維紋理座標新增屬性
in vec2 vTexCoords;
//在新增完二維紋理座標新增屬性之後,我們需要對其進行插值,
//設定一組座標,這組座標能夠在頂點之間平滑的進行插值。
smooth out vec2 vTarnishCoords;
//最後將這些屬性分配給插值變數即可。
vTarnishCoords = vTexCoords.st;
然後是管理反射的片段著色器:
#version 130
out vec4 vFragColor;
//注意這裡有兩個取樣器,即sampleCube型別的cubeMap和sampler2D型別的tarnishMap。
//這兩個紋理使用各自的紋理座標進行取樣,而得到的過濾顏色值只是簡單地相乘。
//就會得到最終的片段顏色。
uniform samplerCube cubeMap;
uniform sampler2D tarnishMap;
smooth in vec3 vVaryingTexCoord;
smooth in vec2 vTarnishCoords;
void main(void)
{
//注意其計算方法
//對於cube就使用紋理座標的三個座標一起使用
vFragColor = texture(cubeMap, vVaryingTexCoord.stp);
//而tarnish是2D紋理。
vFragColor *= texture(tarnishMap, vTarnishCoords);
}
對於天空盒還有兩個渲染器,下面我們列出天空盒渲染:
首先是頂點座標的輸入:
#version 130
// 輸入每一個點的座標
in vec4 vVertex;
uniform mat4 mvpMatrix; // 模型投影矩陣
// 需要傳遞給片段著色器的紋理座標
varying vec3 vVaryingTexCoord;
void main(void)
{
// 紋理座標轉換
vVaryingTexCoord = normalize(vVertex.xyz);
// 對幾何操作的設定。
gl_Position = mvpMatrix * vVertex;
}
然後是片段著色器的程式碼:
#version 130
out vec4 vFragColor;
//注意一個取樣為cubeMap.
uniform samplerCube cubeMap;
//然後是一個渲染紋理座標。
varying vec3 vVaryingTexCoord;
void main(void)
{
//僅僅是一個texture函式,來輸出vFragColor
vFragColor = texture(cubeMap, vVaryingTexCoord);
}
最後是原始碼,其中的程式碼如下:
#pragma comment(lib,"GLTools.lib")
#include <GLTools.h> // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <Stopwatch.h>
#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch sphereBatch;
GLBatch cubeBatch;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLuint cubeTexture;
GLuint tarnishTexture;
GLint reflectionShader;
GLint skyBoxShader;
GLint locMVPReflect, locMVReflect, locNormalReflect, locInvertedCamera;
GLint locCubeMap, locTarnishMap;
GLint locMVPSkyBox;
// 六個面在主程式目錄下的名字
const char *szCubeFaces[6] = { "pos_x.tga", "neg_x.tga", "pos_y.tga", "neg_y.tga", "pos_z.tga", "neg_z.tga" };
//然後是六個面的列舉值,對應於上面的char陣列
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 };
//////////////////////////////////////////////////////////////////
// 初始化渲染環境
void SetupRC()
{
GLbyte *pBytes;
GLint iWidth, iHeight, iComponents;
GLenum eFormat;
int i;
// 去掉背面
glCullFace(GL_BACK);
//對正面背面的認定標準進行設定,這裡設定逆時針為正面
glFrontFace(GL_CCW);
//開啟深度測試
glEnable(GL_DEPTH_TEST);
//畫素儲存模式
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// 生成晦暗影象的影象單元
glGenTextures(1, &tarnishTexture);
//對這個影象單元進行屬性設定,繫結影象
glBindTexture(GL_TEXTURE_2D, tarnishTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//得到對應影象的資訊
pBytes = gltReadTGABits("tarnish.tga", &iWidth, &iHeight, &iComponents, &eFormat);
//設定2D影象
glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
free(pBytes);
glGenerateMipmap(GL_TEXTURE_2D);//生成mipmap
// 載入立方體貼圖
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);
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);
// 載入立方體貼圖
for (i = 0; i < 6; i++)
{
//載入紋理貼圖
//得到資料夾下所有的影象的資訊
//並且使用glTexImage2D對6個面進行繫結
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);//生成mip貼圖
//設定好攝像機的距離
viewFrame.MoveForward(-4.0f);
//建立一個球
gltMakeSphere(sphereBatch, 1.0f, 52, 26);
//建立一個cub作為渲染天空盒
gltMakeCube(cubeBatch, 20.0f);
//對反射著色器進行設定,傳入三個屬性,傳給頂點著色器
reflectionShader = gltLoadShaderPairWithAttributes("Reflection.vp", "Reflection.fp", 3,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal",
GLT_ATTRIBUTE_TEXTURE0, "vTexCoords");
//得到相關統一值,方便賦值
locMVPReflect = glGetUniformLocation(reflectionShader, "mvpMatrix");
locMVReflect = glGetUniformLocation(reflectionShader, "mvMatrix");
locNormalReflect = glGetUniformLocation(reflectionShader, "normalMatrix");
locInvertedCamera = glGetUniformLocation(reflectionShader, "mInverseCamera");
locCubeMap = glGetUniformLocation(reflectionShader, "cubeMap");
locTarnishMap = glGetUniformLocation(reflectionShader, "tarnishMap");
//對於sky天空盒渲染,對其進行渲染,傳入兩個輸入
skyBoxShader = gltLoadShaderPairWithAttributes("SkyBox.vp", "SkyBox.fp", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");
//得到相關統一值,方便賦值
locMVPSkyBox = glGetUniformLocation(skyBoxShader, "mvpMatrix");
//操作客戶端部分非常簡單,現在已知tarnishiTexture是包含
//晦暗紋理的紋理物件,而cubeTexture是包含立方體貼圖的紋理物件
//名稱,下面的程式碼會將這兩個紋理進行繫結。其中每個紋理都
//繫結到自己的紋理單元上。
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tarnishTexture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);
}
void ShutdownRC(void)
{
//當渲染環境結束之後執行,來刪除立方體貼圖。
glDeleteTextures(1, &cubeTexture);
}
// 呼叫此函式來渲染
void RenderScene(void)
{
// 清除對應緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
M3DMatrix44f mCamera;
M3DMatrix44f mCameraRotOnly;
M3DMatrix44f mInverseCamera;
//對以上矩陣變數進行賦值,得到與camera移動相關的矩陣
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);
glUniform1i(locCubeMap, 0);
glUniform1i(locTarnishMap, 1);
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();
}
// 利用箭頭按鍵控制攝像機的移動
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;
// S設定視口
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 MultiTexture");
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;
}