关于加载图片,需要先将图片数据通过 cgContextRef 加载到内存中,然后再复制到 OpenGL 引擎中,开启纹理绘制。

加载图片到 OpenGL 引擎

使用 CocoaTouch 的 CGImageRef 和 CGContextRef 来获取图片的位图数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (void)loadTextures
{
CGImageRef textureImage = [UIImage imageNamed:@"checkerplate"].CGImage;
if (textureImage == nil) {
NSLog(@"Failed to load texture image.");
return;
}
//获取到图片的宽高,以便于创建承载图片数据的数组。
NSInteger texWidth = CGImageGetWidth(textureImage);
NSInteger texHeight = CGImageGetHeight(textureImage);

//开辟内存空间以存储图片,位图的每一位都是4个字节来保存的 RGBA 数据。即图片是由宽 * 高 个颜色位组成的,每个颜色位为 32 位,4 个字节。
GLubyte *textureData = (GLubyte *)malloc(texWidth * texHeight * 4);
//创建一个上下文,句柄,管道,突然有点理解了这两个参数的意思,我们创建一个管道,同时绑定输入和输出,以及管道内通过数据的格式和内容,输出后,这个管道就可以丢弃了。
CGContextRef textureContext = CGBitmapContextCreate(textureData, texWidth, texHeight, 8, 4 * texWidth, CGImageGetColorSpace(textureImage), kCGImageAlphaPremultipliedLast);
//将位图的数据存储到数据中
CGContextDrawImage(textureContext, CGRectMake(0, 0, (float)texWidth, (float)texHeight), textureImage);
//存储完成,释放 context 的内存。
CGContextRelease(textureContext);

//为 textures 开辟内存
glGenTextures(1,&textures[0]);

//激活纹理,并将 textures 的指针传递过去,之后复制纹理数据到 OpenGL 的时候,会自动存放到这个指针中开头的数组中,关于内存空间的开辟,应该是在下一步做的。
glBindTexture(GL_TEXTURE_2D, textures[0]);

//将图片数据复制到 OpenGL 引擎中,应该是在这一步定义了数据的
glTexImage2D(GL_TEXTURE_2D, //通常是 GL_TEXTURE_2D
0, //规定纹理的详细程度。0表示允许图片的全部细节。
GL_RGBA, //通常是 GL_RGBA
texWidth, //数据的宽
texHeight, //数据的高
0, //border ,必须设置为 0,OpenGL ES 不支持纹理边界
GL_RGBA, //通常是 GL_RGBA
GL_UNSIGNED_BYTE, //每个像素的数据类型,
textureData); //源数据的指针

//复制以后,我们之前保存图片数据的 textureData 也没有用了,可以释放。
free(textureData);

//告诉 OpenGL ,当距离变大,即缩小的时候(GL_TEXTURE_MIN_FILTER),该怎么处理
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//告诉 OpenGL ,当距离变小,即放大的时候(GL_TEXTURE_MAG_FILTER),该怎么处理,为了映射纹理,最好都设置一下,
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

//启用 OpenGL 纹理
glEnable(GL_TEXTURE_2D);
}

上面的代码将纹理数据放到了 OpenGL 中,之后使用的时候,将会自动将纹理渲染到具体位置上。

渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

//纹理渲染坐标数组
const GLshort squareTextureCoords[] = {
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1 // top right
};

glLoadIdentity();
glColor4f(1.0, 1.0, 1.0, 1.0);
glTranslatef(1.5, 0.0, -6.0);
glRotatef(rate, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, squareVertices);

glEnableClientState(GL_VERTEX_ARRAY);
glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords);

glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// glColorPointer(4, GL_FLOAT, 0, squareColours); // NEW
// glEnableClientState(GL_COLOR_ARRAY); // NEW
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
// glDisableClientState(GL_COLOR_ARRAY); // NEW
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);

glEnableClientState(GL_TEXTURE_COORD_ARRAY); 告诉 OpenGL 开启纹理渲染。
glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords);
告诉 OpenGL 我们纹理的坐标,以及每个坐标点的顶点个数,数据类型,从而能准确定位到模型坐标。具体实现细节不清楚,但是 OpenGL 应该是根据纹理的顶点数据,将图片纹理中的数据点与原来的图像数据点进行换算,图片纹理大小将会适应我们的原图。
glDisableClientState(GL_TEXTURE_COORD_ARRAY); 关闭纹理,防止影响到三角形。

注意点

需要注意的或者是需要了解的是:
OpenGL 是如何将纹理与原来的图像颜色混合起来的:
每个像素点,原来坐标点像素点 rgba 值,乘以图片纹理的 rgba 值。
假如原图有个像素点是 1.0 1.0 0.0 1.0
图片纹理对应的像素点 1.0 1.0 1.0 1.0,
那么最后的像素点为: 1.0 1.0 0.0 1.0 即1.0 * 1.0, 1.0 * 1.0,0.0 * 1.0,1.0 * 1.0 ,所以代码中,使用 glClearColor(1.0,1.0,1.0,1.0) 设置了原图颜色为白色,这样就原封不动的贴上去了纹理。

还有一个,我们可以使用自己的纹理图片,但是需要保证这个纹理图片的尺寸,必须是 2 的幂数,比如 1 2 4 8 等等。