前言
本文是關(guān)于OpenGL ES的系統(tǒng)性學(xué)習過程,記錄了自己在學(xué)習OpenGL ES時的收獲屋确。
這篇文章的目標是學(xué)習OpenGL ES 2.0中的紋理貼圖技術(shù)。
環(huán)境是Xcode8.1+OpenGL ES 2.0
目前代碼已經(jīng)放到github上面,OpenGL ES入門05-OpenGL ES 紋理貼圖
歡迎關(guān)注我的 OpenGL ES入門專題
概述
紋理 是表示物體表面細節(jié)的一幅或幾幅二維圖形(甚至也有一維和三維的紋理),也稱紋理貼圖(texture mapping)當把紋理按照特定的方式映射到物體表面上的時候能使物體看上去更加真實气破。紋理映射是一種允許我們?yōu)槿切钨x予圖象數(shù)據(jù)的技術(shù);這讓我們能夠更細膩更真實地表現(xiàn)我們的場景餐抢。
實現(xiàn)效果
紋理坐標
紋理坐標在x和y軸上堵幽,范圍為0到1之間(當然也可以大于1)。使用紋理坐標獲取紋理顏色叫做采樣(Sampling)弹澎。紋理坐標起始于(0, 0),也就是紋理圖片的左下角努咐,終始于(1, 1)苦蒿,即紋理圖片的右上角。
紋理環(huán)繞方式
紋理坐標的范圍通常是從(0, 0)到(1, 1)渗稍。但是如果紋理坐標不在該范圍里佩迟,OpenGL ES默認的行為是重復(fù)這個紋理圖像团滥,但是我們也可以自己設(shè)置其它處理的方式。
環(huán)繞方式(Wrapping) | 描述 |
---|---|
GL_REPEAT | 對紋理的默認行為报强,重復(fù)紋理圖像灸姊。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一樣,但每次重復(fù)圖片是鏡像放置的秉溉。 |
GL_CLAMP_TO_EDGE | 紋理坐標會被約束在0到1之間力惯,超出的部分會重復(fù)紋理坐標的邊緣,產(chǎn)生一種邊緣被拉伸的效果召嘶。 |
GL_CLAMP_TO_BORDER | 超出的坐標為用戶指定的邊緣顏色父晶。 |
- 我們可以使用glTexParameter*函數(shù)對單獨的一個坐標軸設(shè)置(二維紋理為s、t坐標弄跌,三維紋理為s甲喝、t、r坐標)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
參數(shù) target:紋理目標是铛只;
參數(shù) pname :指定坐標軸S軸埠胖、T軸、R軸直撤;
參數(shù) param :環(huán)繞方式;
如果我們選擇 GL_CLAMP_TO_BORDER 選項凯肋,我們還需要指定一個邊緣的顏色谊惭。這需要使用glTexParameterfv(表示float類型的數(shù)組)函數(shù),用GL_TEXTURE_BORDER_COLOR作為它的選項侮东,并且傳遞一個float數(shù)組作為邊緣的顏色值
float borderColor[] = { 0.5f, 0.5f, 0.5f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
紋理過濾
紋理坐標不依賴于分辨率圈盔,它可以是任意浮點值,所以O(shè)penGL ES需要知道怎樣將紋理像素映射到紋理坐標悄雅。OpenGL ES默認的紋理過濾方式是鄰近過濾驱敲。
|紋理過濾 | 描述|總結(jié)|
|:---:|:---:|:---:|:---:|
|GL_LINEAR 線性過濾| 它會基于紋理坐標附近的紋理像素,計算出一個插值宽闲,近似出這些紋理像素之間的顏色众眨。一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大 | GL_LINEAR能夠產(chǎn)生更平滑的圖案容诬,很難看出單個的紋理像素娩梨。
|GL_NEAREST 鄰近過濾|當設(shè)置為GL_NEAREST的時候,會選擇中心點最接近紋理坐標的那個像素览徒。|GL_NEAREST產(chǎn)生了顆粒狀的圖案狈定,我們能夠清晰看到組成紋理的像素|
- 使用glTexParameter*函數(shù)為放大和縮小指定過濾方式。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
加載紋理
在實例中使用的JPEG的圖片。因此需要將壓縮數(shù)據(jù)解碼成RGB像素數(shù)據(jù)纽什。在iOS中可以使用UIImage和CGImage獲取到RGB數(shù)據(jù)措嵌。在實例中我使用了libjpeg庫來解碼jpeg圖片,因此跨平臺型好一些芦缰。(因為希望通過代碼累積最終得到一個跨平臺的工具)
struct my_error_mgr
{
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
};
typedef struct my_error_mgr* my_error_ptr;
void my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
longjmp(myerr->setjmp_buffer, 1);
}
int read_jpeg_file(const char* jpegfile,
unsigned char** data,
int* size,
int* width,
int* height)
{
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
FILE* fp;
JSAMPARRAY buffer;
int row_stride = 0;
unsigned char* tmp_buffer = NULL;
int rgb_size;
fp = fopen(jpegfile, "rb");
if (fp == NULL)
{
printf("open file %s failed.\n", jpegfile);
return -1;
}
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
if (setjmp(jerr.setjmp_buffer))
{
jpeg_destroy_decompress(&cinfo);
fclose(fp);
return -1;
}
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, fp);
jpeg_read_header(&cinfo, TRUE);
// we only support RGB or grayscale
if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
cinfo.out_color_space = JCS_GRAYSCALE;
}else {
cinfo.out_color_space = JCS_RGB;
}
jpeg_start_decompress(&cinfo);
row_stride = cinfo.output_width * cinfo.output_components;
*width = cinfo.output_width;
*height = cinfo.output_height;
rgb_size = row_stride * cinfo.output_height; // 總大小
*size = rgb_size;
buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
*data = (unsigned char *)malloc(sizeof(char) * rgb_size); // 分配總內(nèi)存
printf("jpeg debug:\nrgb_size: %d, size: %d w: %d h: %d row_stride: %d \n",
rgb_size,
cinfo.image_width*cinfo.image_height*3,
cinfo.image_width,
cinfo.image_height,
row_stride);
tmp_buffer = *data;
while (cinfo.output_scanline < cinfo.output_height) // 解壓每一行
{
jpeg_read_scanlines(&cinfo, buffer, 1);
// 復(fù)制到內(nèi)存
memcpy(tmp_buffer, buffer[0], row_stride);
tmp_buffer += row_stride;
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(fp);
return 0;
}
生成紋理
和之前生成的VBO企巢、VAO對象一樣,紋理生成也類似之前緩存對象的生成让蕾,生成步驟也相差無幾浪规。
- 創(chuàng)建紋理對象
void glGenTextures(GLsizei n, GLuint * textures);
參數(shù) n : 表示需要創(chuàng)建紋理對象的個數(shù)
參數(shù) textures :用于存儲創(chuàng)建好的紋理對象句柄
- 將紋理對象設(shè)置為當前紋理對象
void glBindTexture (GLenum target, GLuint texture);
參數(shù) target :指定綁定的目標
參數(shù) texture :紋理對象句柄
- 指定紋理
void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
參數(shù) target :指定紋理單元的類型,二維紋理需要指定為GL_TEXTURE_2D
參數(shù) level:指定紋理單元的層次涕俗,非mipmap紋理level設(shè)置為0罗丰,mipmap紋理設(shè)置為紋理的層級
參數(shù) internalFormat:指定OpenGL ES是如何管理紋理單元中數(shù)據(jù)格式的
參數(shù) width:指定紋理單元的寬度
參數(shù) height:指定紋理單元的高度
參數(shù) border:指定紋理單元的邊框,如果包含邊框取值為1再姑,不包含邊框取值為0
參數(shù) format:指定data所指向的數(shù)據(jù)的格式
參數(shù) type:指定data所指向的數(shù)據(jù)的類型
參數(shù) data:實際指向的數(shù)據(jù)
- 激活紋理單元萌抵。GL_TEXTURE0默認激活,在使用其它紋理單元的時候需要手動激活元镀。OpenGL ES支持的最小紋理單元與設(shè)備特性有關(guān)绍填,通常情況下OpenGL ES2.0最少支持8個紋理單元,OpenGL ES3.0最少支持16個紋理單元栖疑。
void glActiveTexture (GLenum texture);
參數(shù) texture :需要激活的紋理單元
- 釋放紋理對象
void glDeleteTextures (GLsizei n, const GLuint* textures);
參數(shù) n : 表示紋理對象的個數(shù)
參數(shù) textures :紋理對象句柄
GLuint createTexture2D(GLenum format, int width, int height, void *data)
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_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);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
實例
1讨永、創(chuàng)建頂點數(shù)據(jù)以及紋理坐標
- (void)setupVBO
{
_vertCount = 6;
// GLfloat vertices[] = {
// 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // 右上
// 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // 右下
// -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // 左下
// -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // 左上
// };
GLfloat vertices[] = {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 左下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 左下
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, // 左上
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
};
// 創(chuàng)建VBO
_vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
glEnableVertexAttribArray(glGetAttribLocation(_program, "position"));
glVertexAttribPointer(glGetAttribLocation(_program, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
glEnableVertexAttribArray(glGetAttribLocation(_program, "texcoord"));
glVertexAttribPointer(glGetAttribLocation(_program, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}
2、創(chuàng)建紋理對象
- (void)setupTexure
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"wood" ofType:@"jpg"];
unsigned char *data;
int size;
int width;
int height;
// 加載紋理
if (read_jpeg_file(path.UTF8String, &data, &size, &width, &height) < 0) {
printf("%s\n", "decode fail");
}
// 創(chuàng)建紋理
_texture = createTexture2D(GL_RGB, width, height, data);
if (data) {
free(data);
data = NULL;
}
}
3遇革、創(chuàng)建頂點著色器和片元著色器卿闹。在片元著色器中使用采樣器texture2D根據(jù)紋理坐標進行采樣。
attribute vec3 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main()
{
gl_Position = vec4(position, 1.0);
vTexcoord = texcoord;
}
precision mediump float;
uniform sampler2D image;
varying vec2 vTexcoord;
void main()
{
gl_FragColor = texture2D(image, vTexcoord);
}
4萝快、激活紋理并渲染
- (void)render
{
glClearColor(1.0, 1.0, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2.0);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// 激活紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _texture);
glUniform1i(glGetUniformLocation(_program, "image"), 0);
glDrawArrays(GL_TRIANGLES, 0, _vertCount);
// 索引數(shù)組
//unsigned int indices[] = {0,1,2,3,2,0};
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
//將指定 renderbuffer 呈現(xiàn)在屏幕上锻霎,在這里我們指定的是前面已經(jīng)綁定為當前 renderbuffer 的那個,在 renderbuffer 可以被呈現(xiàn)之前揪漩,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲空間旋恼。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}