目錄
- 紋理簡介
- 紋理映射
- 紋理映射流程
- 示例代碼:
- 紋理的環繞和過濾方式
- 紋理的過濾方式
紋理簡介
現實生活中,紋理(Texture
) 類似于游戲中皮膚的概念,最通常的作用是裝飾 3D
物體,它像貼紙一樣貼在物體的表面,豐富物體的表面和細節
在 OpenGL-ES
開發中,紋理除了用于裝飾物體表面,還可以用來作為存儲數據的容器
所以在 OpenGL-ES
中,紋理實際上是一個可以被采樣的復雜數據集合,是 GPU
的圖像數據結構,紋理分為 2D紋理、立方圖紋理和 3D紋理
2D
紋理是OpenGL-ES
中最常見和最常用的紋理形式,是一個表示圖像數據的二維數組,紋理中一個單獨的數據單元被稱為紋素或者紋理像素- 立方圖紋理(
CubeMap
)是一個由6個單獨的2D
紋理面組成的紋理,立方圖紋理像素的讀取是使用三維坐標(s, t, r)作為紋理坐標 3D
紋理 可以看做2D
紋理的集合,2D
紋理是3D
紋理的一個切面,使用三維坐標對齊進行訪問
紋理映射
在 OpenGL-ES
中,紋理映射就是通過為圖元的頂點坐標指定恰當的紋理坐標,通過紋理坐標在紋理圖中選定特定范圍的紋理區域,最后通過紋理坐標和頂點的映射關系,將選定的紋理區域映射到指定的圖元上;
紋理映射也稱為紋理貼圖,簡單說就是將紋理坐標所指定的紋理區域,映射到頂點坐標對應的渲染區域
紋理坐標是使用紋理坐標系
頂點坐標是使用渲染坐標系或者 OpenGL-ES
坐標系
4個紋理坐標T0(0,0), T1(0,1), T2(1,1), T3(1,0)
對應的頂點坐標為V0(-1,0.5),V1(-1,-0.5),V2(1,-0.5),V3(1,0.5)
OpenGL-ES
的基本圖元是以三角形為單位的,設置繪制兩個三角形V0V1V2
和 V0V2V3
當我們調整紋理坐標的順序保持頂點坐標的順序不變,T0T1T2T3
==》T1T2T3T0
繪制后將會得到一個順時針旋轉 90
度為紋理貼圖,所以調整紋理坐標和頂點坐標的對應關系可以實現紋理貼圖的簡單旋轉
紋理坐標和紋理像素的映射關系如下:
紋理坐標(0,0)對應紋理像素的第一個元素
紋理坐標(1,0)對應紋理像素的index = width - 1的元素
紋理坐標(0,1)對應紋理像素的index = width* (height - 1) 的元素
紋理坐標(1,1)對應紋理像素的index = width* height - 1 的元素
對應紋理像素區域內的元素都會被采樣
紋理映射流程
紋理映射的一般步驟:
- 生成紋理,編譯鏈接著色器程序
- 確定紋理坐標和對應的頂點坐標
- 加載圖形數據到紋理,加載頂點坐標和紋理坐標到著色器程序
- 開始繪制
軟件流程如下:
對紋理采樣的 FragmentShader
:
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 v_texCoord; \n"
"layout(location = 0) out vec4 outColor; \n"
"uniform sampler2D s_texture; \n"
"void main() \n"
"{ \n"
" outColor = texture( s_texture, v_texCoord ); \n"
"}
其中 texture
是內置的采樣函數,v_texCoord
是頂點著色器傳入的紋理坐標,根據紋理坐標進行采樣,輸出為4向量的 RGBA 值
uniform sampler2D s_texture
是加載后的紋理內容
示例代碼:
typedef struct
{// Handle to a program objectGLuint programObject;// Sampler locationGLint samplerLoc;// Texture handleGLuint textureId;
} UserData;// load texture form tga file
static GLuint CreateSimpleTexture2D()
{// Texture object handleGLuint textureId;// load texture from tga fileconst char* files = "./Huskey.tga";int width;int height;GLubyte* pixels = esLoadTGA(NULL, files, &width, &height);// Use tightly packed dataglPixelStorei(GL_UNPACK_ALIGNMENT, 1);// Generate a texture objectglGenTextures(1, &textureId);// Bind the texture objectglBindTexture(GL_TEXTURE_2D, textureId);// Load the texture notice width height format must align with real sizeglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);// Set the filtering modeglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);return textureId;
}///
// Initialize the shader and program object
//
static int Init(ESContext *esContext) {UserData *userData = esContext->userData;char vShaderStr[] ="#version 300 es \n""layout(location = 0) in vec4 a_position; \n""layout(location = 1) in vec2 a_texCoord; \n""out vec2 v_texCoord; \n""void main() \n""{ \n"" gl_Position = a_position; \n"" v_texCoord = a_texCoord; \n""} \n";char fShaderStr[] ="#version 300 es \n""precision mediump float; \n""in vec2 v_texCoord; \n""layout(location = 0) out vec4 outColor; \n""uniform sampler2D s_texture; \n""void main() \n""{ \n"
//使用內建函數在Fragmanet Shader 中進行紋理采樣" outColor = texture( s_texture, v_texCoord ); \n""} \n";// Load the shaders and get a linked program objectuserData->programObject = esLoadProgram(vShaderStr, fShaderStr);// Get the sampler locationuserData->samplerLoc = glGetUniformLocation(userData->programObject, "s_texture");// Load the textureuserData->textureId = CreateSimpleTexture2D();glClearColor(1.0f, 1.0f, 1.0f, 0.0f);return TRUE;
}///
// Draw a triangle using the shader pair created in Init()
//
static void Draw(ESContext *esContext)
{UserData *userData = esContext->userData;GLfloat vVertices[] = { -0.5f, 0.5f, 0.0f, // Position 00.0f, 0.0f, // TexCoord 0 -0.5f, -0.5f, 0.0f, // Position 10.0f, 1.0f, // TexCoord 10.5f, -0.5f, 0.0f, // Position 21.0f, 1.0f, // TexCoord 20.5f, 0.5f, 0.0f, // Position 31.0f, 0.0f // TexCoord 3};GLushort indices[] = { 0, 1, 2, 0, 2, 3 };// Set the viewportglViewport(0, 0, esContext->width, esContext->height);// Clear the color bufferglClear(GL_COLOR_BUFFER_BIT);// Use the program objectglUseProgram(userData->programObject);// Load the vertex positionglVertexAttribPointer(0, 3, GL_FLOAT,GL_FALSE, 5 * sizeof(GLfloat), vVertices);// Load the texture coordinateglVertexAttribPointer(1, 2, GL_FLOAT,GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3]);glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);// Bind the textureglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, userData->textureId);// Set the sampler texture unit to 0glUniform1i(userData->samplerLoc, 0);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
}
注意其中的glTexImage2D是用于加載紋理的函數:
void glTexImage2D( GLenum target,GLint level,GLint internalFormat,GLsizei width,GLsizei height,GLint border,
GLenum format,GLenum type,const void * data);
- GLenum target : 指定texture 類型,一般為 GL_TEXTURE_2D
- GLint level : 一般設置為0
- GLint internalFormat : 設置紋理的存儲格式 GL_RGB
- GLsizei width : texture 的寬度
- GLsizei height : texture 的高度
- GLint border : 一般設置為0
- GLenum format : 設置紋理輸入圖片的存儲格式 GL_RGB
- GLenum type : 設置紋理輸入圖片的存儲格式 GL_RGB
- const void * data : 指向加載為紋理內容的圖片的指針
實際顯示效果:
紋理的環繞和過濾方式
紋理坐標的范圍通常是從(0, 0) 到 (1, 1),如果設置的坐標超出這個范圍,就會有重復的效果,比如 紋理的 S 坐標設置到 2,就表示 S 軸上要重復兩次采樣紋理,如果 將T 坐標設置為 2,表示 T 軸上重復兩次采樣紋理,通過設置不同的紋理坐標,就可以產生不同的重復的效果,紋理的環繞方式也可以設置,可以設置的類型如下:
設置的代碼如下:
// GL_REPEAT GL_CLAMP_TO_EDGE GL_MIRRORED_REPEAT
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
紋理的過濾方式
紋理映射的原理是:光柵化之后,圖元轉換為一個個光柵,每個光柵化之后的 pixel
都會經過 PixelShader
,PixelShader
中從紋理采樣得到每個 pixel
的顏色,這里就會涉及采樣方式的問題,如果圖元的分辨率很大,但是關聯到圖元的紋理很小,就需要進行插值,紋理的過濾方式實際就是紋理到光柵的插值算法
現在只討論最重要的兩種:GL_NEAREST
和 GL_LINEAR
-
GL_NEAREST
(也叫鄰近過濾,Nearest Neighbor Filtering)是OpenGL
默認的紋理過濾方式。當設置為GL_NEAREST
的時候,OpenGL
會選擇中心點最接近紋理坐標的那個像素,原理是最近鄰插值算法 -
GL_LINEAR
(也叫線性過濾,(Bilinear Filtering
)它會基于紋理坐標附近的四個紋理像素的值,計算出一個插值,原理是雙線性插值算法
鄰近過濾算法簡單,但是在放大圖像的時候,會有嚴重的馬賽克,線性過濾計算過程復雜,但是效果比鄰近過濾算法好