該原創文章首發於微信公衆號:字節流動
OpenGL ES 立方體貼圖
OpenGL ES 立方體貼圖本質上還是紋理映射,是一種 3D 紋理映射。立方體貼圖所使的紋理稱爲立方圖紋理,它是由 6 個單獨的 2D 紋理組成,每個 2D 紋理是立方圖的一個面。
立方圖紋理的採樣通過一個 3D 向量(s, t, r)作爲紋理座標,這個 3D 向量只作爲方向向量使用,OpenGL ES 獲取方向向量觸碰到立方圖表面上的紋理像素作爲採樣結果。方向向量觸碰到立方圖表面對應的紋理位置作爲採樣點,要求立方圖的中心必須位於原點。
立方圖各個面的指定方法與 2D 紋理基本相同,且每個面必須爲正方形(寬度和高度必須相同)。
立方圖紋理的使用與 2D 紋理基本一致,首先生成一個紋理,激活相應紋理單元,然後綁定到 GL_TEXTURE_CUBE_MAP
類型紋理。
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
由於立方圖包含 6 個紋理,每個面對應一個紋理,需要調用glTexImage2D
函數 6 次,OpenGL ES 爲立方圖提供了 6 個不同的紋理目標,對應立方圖的 6 個面,且 6 個紋理目標按順序依次增 1。
紋理目標(Texture target) | 方位 |
---|---|
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 | 前 |
將立方圖的 6 個面對應的圖像數據加載到紋理,其中m_pSkyBoxRenderImg
爲圖像數據的數組:
glGenTextures(1, &m_TextureId);
glBindTexture(GL_TEXTURE_CUBE_MAP, m_TextureId);
for (int i = 0; i < sizeof(m_pSkyBoxRenderImg) / sizeof(NativeImage); ++i)
{
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0,
GL_RGBA, m_pSkyBoxRenderImg[i].width, m_pSkyBoxRenderImg[i].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_pSkyBoxRenderImg[i].ppPlane[0]
);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_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);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
類似於普通的 2D 紋理,在使用立方圖紋理繪製物體之前,需要激活相應的紋理單元並綁定到立方圖上。不同的是,對應的片段着色器中,採樣器變成了 samplerCube
,並且紋理座標變成了三維方向向量。
#version 300 es
precision mediump float;
in vec3 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform samplerCube s_SkyBox;
void main()
{
outColor = texture(s_SkyBox, v_texCoord);
}
天空盒的繪製:
// draw SkyBox
glUseProgram(m_ProgramObj);
glBindVertexArray(m_SkyBoxVaoId);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawArrays(GL_TRIANGLES, 0, 36);
接下來我們想在天空盒內繪製一個立方體,並讓立方體的表面反射它周圍環境的屬性。
天空盒內物體反射的原理圖:
其中 I 表示觀察方向向量,通過當前頂點座標減去相機位置(觀察者)座標計算得出;N 表示物體的法線向量,R 爲反射向量,通過使用 GLSL 的內建函數 reflect 計算得出反射向量 R。最後,以反射向量 R 作爲方向向量對立方圖進行索採樣,返回採樣結果(一個對應反射環境的顏色值)。最後的效果看起來就像物體反射了天空盒。
天空盒內繪製物體(反射周圍環境顏色)使用的頂點着色器:
#version 300 es
precision mediump float;
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
uniform mat4 u_MVPMatrix;
uniform mat4 u_ModelMatrix;
out vec3 v_texCoord;
out vec3 v_normal;
void main()
{
gl_Position = u_MVPMatrix * vec4(a_position, 1.0);
v_normal = mat3(transpose(inverse(u_ModelMatrix))) * a_normal;
v_texCoord = vec3(u_ModelMatrix * vec4(a_position, 1.0));
}
天空盒內繪製物體(反射周圍環境顏色)使用的片段着色器:
#version 300 es
precision mediump float;
in vec3 v_texCoord;
in vec3 v_normal;
layout(location = 0) out vec4 outColor;
uniform samplerCube s_SkyBox;
uniform vec3 u_cameraPos;
void main()
{
float ratio = 1.00 / 1.52;
vec3 I = normalize(v_texCoord - u_cameraPos);
//反射
vec3 R = reflect(I, normalize(v_normal));
//折射
//vec3 R = refract(I, normalize(v_normal), ratio);
outColor = texture(s_SkyBox, R);
}
繪製天空盒和盒內立方體:
UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, 1.0, (float) screenW / screenH);
// draw SkyBox
glUseProgram(m_ProgramObj);
glBindVertexArray(m_SkyBoxVaoId);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawArrays(GL_TRIANGLES, 0, 36);
UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, 0.4f, (float) screenW / screenH);
// draw Cube
glUseProgram(m_CubeProgramObj);
glBindVertexArray(m_CubeVaoId);
glUniformMatrix4fv(m_CubeMVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glUniformMatrix4fv(m_CubeModelMatLoc, 1, GL_FALSE, &m_ModelMatrix[0][0]);
glUniform3f(m_ViewPosLoc, 0.0f, 0.0f, 1.8f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, m_TextureId);
glUniform1i(m_CubeSamplerLoc, 0);
glDrawArrays(GL_TRIANGLES, 0, 36);
實現代碼路徑:
NDK_OpenGLES_3_0
參考
https://learnopengl.com/Advanced-OpenGL/Cubemaps
聯繫與交流
我的公衆號
我的微信