OpenGL ES学习笔记之二(纹理学习)
这次我将用OpenGL ES来显示一张图片,可能我的学习笔记跳跃性很大,对于刚开始学习OpenGL ES人来说有弄不懂的地方也是正常的,我也是看了大量的资料才学会的。现在网上有大量的资料可以学习但有的代码会用苹果的GLKit开发有的代码不会,所以会造成本来看懂的代码到下一个博客又不明白了。我会在笔记中用两种方式去实现以有对比性。建议能用苹果的GLKit最好,因为代码减少很多。下面上代码。
学习的代码都在我的github仓库欢迎大家学习指教!
不用GLKit的方法
为了减少重复说相同的话这次对于上一个笔记写的东西这里将不再介绍而是直接用,这里只说与上一个笔记增加的内容。首先在原项目里添加一张图。接下来是修改顶点数据,在上一个笔记中我们创建了一个三角形,而显示图片需要一个矩形,其实我们可以有通过两个直角三角形来拼接成一个正方形。(如果你学到最后你会发现在OpenGL的世界里那些复杂的图形或三维物体都是由成千上万个三角形拼接而成的)。顶点数据如下:
GLfloat vertex[] = {
// 第一个三角形
-0.5f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
// 第二个三角形
-0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
};
有了图形的顶点显示图片还是不行的,还要告诉程序图片是怎么放的。也就是纹理顶点数据,要注意这个坐标和iOS平常用的坐标是不一样的,它的原点在左下角,从左向右和从下到上的取值范围是0~1。但这里还有一个问题,就是你最后渲染出来的图片是上下颠倒的,这是因为纹理顶点的原点在左下角但图片纹理的原点在右上角,坐标系统不一样就造成上下颠倒了。有两种解决方案:方案一、将纹理顶点数据的y坐标反着写也就是将原来的y变成1-y。方案二、就是把图片数据上下颠倒过来。这里我用的是方案二,因为在写这篇博客的时候我的代码已经过了很长时间了不想再改代码了,其实方案一代码更好一点,还是等大家自己尝试去改吧。纹理顶点数据如下:
GLfloat textureVertex[] = {
// 第一个三角形 纹理坐标
0.0f, 1.0f, // 左上
0.0f, 0.0f, // 左下
1.0f, 0.0f, // 右下
// 第二个三角形
0.0f, 1.0f, // 左上
1.0f, 0.0f, // 右下
1.0f, 1.0f // 右上
};
接下来我们要修改着色器,原来我们传的是固定的颜色,这次要根据纹理来生成相应的颜色。将顶点着色器的原码修改成如下:
attribute vec4 myPosition; // 顶点位置
// 输入的色彩
attribute vec2 textureCoord;
// 输出的色彩
varying vec2 outTextTureCoord;
void main()
{
gl_Position = myPosition; // 传递顶点位置数据
outTextTureCoord = textureCoord; // 传递色彩数据
}
将片元着色器的原码改成下如:
precision mediump float; // 声明精度
// 声明色彩变量
varying vec2 outTextTureCoord;
// 传递图片数据
uniform sampler2D myTexture;
void main()
{
vec4 color = texture2D(myTexture, outTextTureCoord);
// 通过纹理坐标数据来获取对应坐标色值并传递
gl_FragColor = vec4(color.rgb, 1.0);
}
代码里的注释已经写的很明白了,这里就不多说了。
接下来我们开始添加一个属性用于传递纹理数据。
GLuint _myTextTureCoordSlot; // 纹理坐标对应的槽
然后在原来链接着色器的代码下面添加一个获取槽的代码:
// 获取纹理坐标的槽
_myTextTureCoordSlot = glGetAttribLocation(_myPrograme, "textureCoord");
前面的准差不多了,该获取图片传递纹理信息了,在渲染前写以下代码:
- (void)setupTexture {
// 能实现图片翻转
CGImageRef cgImageRef = [UIImage imageNamed:@"testImage"].CGImage;
GLuint width = (GLuint)CGImageGetWidth(cgImageRef);
GLuint height = (GLuint)CGImageGetHeight(cgImageRef);
CGRect rect = CGRectMake(0, 0, width, height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *data = malloc(width * height * 4);
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGColorSpaceRelease(colorSpace);
CGContextClearRect(context, rect);
CGContextDrawImage(context, rect, cgImageRef);
glEnable(GL_TEXTURE_2D); // 开启2D纹理
// 申请纹理id
glGenTextures(1, &_myTextTure);
// 绑定纹理id
glBindTexture(GL_TEXTURE_2D, _myTextTure);
// 设置图像拉伸变形时的处理方法
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 将图片数据传递给GL_TEXTURE_2D,因为上面已绑定纹理对象所以会把数据传递给_myTextTure
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
CGContextRelease(context);
free(data);
}
好了可以渲染了,因为我们这次用了6个顶点所以在Draw方法里要把顶点数改成6.
glDrawArrays(GL_TRIANGLES, 0, 6);
运行一下代码看看效果怎么样。
运行结果.png用GLKit实现的方式
用GLKit会省去很多代码,这里不用你自定义着色器,也不用想纹理上下颠倒的问题,GLKit都给你解决了,该讲的上面已经讲这了,这里就直接把全部代码放这了,关键地方有注释。
#import "ViewController.h"
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
typedef struct{
GLKVector3 position;
} Vertex;
typedef struct{
GLKVector2 position;
} TextureCoord;
Vertex vertex[] = {
{-0.5f, 0.5f, 0.0f}, // 左上
{-0.5f, -0.5f, 0.0f}, // 左下
{0.5f, -0.5f, 0.0f}, // 右下
{-0.5f, 0.5f, 0.0f}, // 左上
{0.5f, -0.5f, 0.0f}, // 右下
{0.5f, 0.5f, 0.0f}, // 右上
};
TextureCoord textrueCoord[] = {
{0.0f, 1.0f}, // 左上
{0.0f, 0.0f}, // 左下
{1.0f, 0.0f}, // 右下
{0.0f, 1.0f}, // 左上
{1.0f, 0.0f}, // 右下
{1.0f, 1.0f}, // 右上
};
@interface ViewController ()
@property (nonatomic,strong)GLKBaseEffect * effect;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
GLKView * glView = (GLKView *)self.view;
EAGLContext * contex = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!contex) {
NSLog(@"context创建失败");
}
if (![EAGLContext setCurrentContext:contex]) {
NSLog(@"设置当前context失败");
}
glView.context = contex;
glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
self.effect = [[GLKBaseEffect alloc] init];
// 加载纹理图片
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil];//GLKTextureLoaderOriginBottomLeft 纹理坐标系是相反的
NSError * error;
CGImageRef image = [UIImage imageNamed:@"myImage"].CGImage;
GLKTextureInfo * textureInfo = [GLKTextureLoader textureWithCGImage:image options:options error:&error];
// 设置纹理可用
self.effect.texture2d0.enabled = GL_TRUE;
// 传递纹理信息
self.effect.texture2d0.name = textureInfo.name;
// 设置顶点
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, vertex);
// 设置纹理坐标
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, textrueCoord);
glClearColor(1.0, 1.0, 1.0, 1.0);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);
}
代码是不是很少,这次写博客与上一次有一段时间间隔,可能上面会有知识点忘记讲了,如果有不清的地放可以在下面问,我给心力说明白。