关于加载图片,需要先将图片数据通过 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 等等。