OpenGLES(六)-综合案例:索引绘图
效果图
索引绘图
相信看这篇文章的同学应该对图元装配方式很熟悉了吧?提供一个参考资料,及时使用合理的图元连接方式,还是难以避免顶点的重复声明,不可避免的会占据额外的缓存区内存。列如这种图形:
图出自:月月
五个面需要申明18个顶点(6 * 3),根据观察却只是用了5个顶点,永远可以相信OpenGL一定会提供一种简便方式:索引绘图
。
索引绘图
: 我们除了一个顶点缓存区
外,还有一个索引缓存区
用来存放顶点的索引值。通过索引的顺序加之图元连接
方式就可以构成一个基本图元(多数情况为三角形)
。共享机制在提高内存使用效率上非常重要。
根绝索引绘图的原理,绘制前只需要将5个顶点坐标传入顶点缓存区,除此之外还需要定义一下索引数组:
代码语言:javascript复制[{2,3,4},{3,1,4},{0,4,1},{2,4,0},{2,3,0},{3,1,0}]
//当然索引数组不是唯一的
- 这里一样需要注意正背面的顶点顺序,将当前能看到的面设置为
逆时针
,看不到的面按照顺时针
来设置。 - 即使设置了索引数组,也不能违背图元连接方式.
GLSL实现
1.fsh
代码语言:javascript复制precision highp float;
varying lowp vec2 varyingCoord;
uniform sampler2D colorMap;
void main() {
gl_FragColor = texture2D(colorMap, varyingCoord);
}
- 片元着色器只包含最基础功能,从纹理中按照纹理坐标取出对应
纹素
的色值,传递给内建变量gl_FragColor
- vsh
attribute vec4 position;
attribute vec2 textureCoord;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
varying lowp vec2 varyingCoord;
void main() {
varyingCoord = textureCoord;
gl_Position = projectionMatrix * modelViewMatrix * position;
}
- 增加两个uniform修饰的4x4矩阵,模型矩阵和投影矩阵
MVP
. 将它们矩阵相乘后,将顶点坐标的偏移结果传递给内建变量gl_Position
; - Layer & Content 初始化
self.myLayer = (CAEAGLLayer *)self.layer;
[self.myLayer setContentsScale:[[UIScreen mainScreen] scale]];
self.myLayer.opaque = YES;
self.myLayer.drawableProperties = @{
kEAGLDrawablePropertyRetainedBacking:@false,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8,
};
self.myContent = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.myContent){
NSLog(@"content init failed");
exit(1);
}
if(![EAGLContext setCurrentContext:self.myContent]){
NSLog(@"Set Current Context failed");
exit(1);
}
- RenderBuffer & FrameBuffer 初始化
glGenRenderbuffers(1, &_myRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, self.myRenderBuffer);
if (self.myRenderBuffer == GL_FALSE) {
NSLog(@"create Render Buffer Failed");
exit(1);
}
[self.myContent renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myLayer];
GLuint frame;
glGenFramebuffers(1, &frame);
glBindFramebuffer(GL_FRAMEBUFFER, frame);
if (frame == GL_FALSE) {
NSLog(@"create Frame Buffer Failed");
exit(1);
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, frame);
- shader的编译 这部分就不放出代码了,这里有详细的注释iOS- OpenGLES中本地着色器编译
- render
...省略部分clean代码
//顶点数组, 前3位顶点, 后3位颜色(RGB,A默认为1.0)
GLfloat vertex[] = {
-0.5f, 0.0f, -0.5f, 0, 1, //左上
-0.5f, 0.0f, 0.5f, 0, 0, //左下
0.5f, 0.0f, 0.5f, 1, 0, //右下
0.5f, 0.0f, -0.5f, 1, 1, //右上
0.0f, 1.0f, 0.0f, 0.5, 0.5, //顶点
};
//索引数组
//需要根据初始位置的正背面,来确定绘制顺序(逆时针为正面)
GLuint indices[] = {
0, 2, 1, //下左
3, 2, 0, //下右
0, 1, 4, //上左
1, 2, 4, //上前
2, 3, 4, //上右
0, 4, 3, //上后
};
//顶点缓存区初始化
GLuint verBuffer;
glGenBuffers(1, &verBuffer);
glBindBuffer(GL_ARRAY_BUFFER, verBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), &vertex, GL_DYNAMIC_DRAW);
//获取shader中的句柄
//着色器从顶点缓存区的读取方式
GLuint positon = glGetAttribLocation(self.myProgram, "position");
glEnableVertexAttribArray(positon);
glVertexAttribPointer(positon, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL 0);
GLuint textureCoord = glGetAttribLocation(self.myProgram, "textureCoord");
glEnableVertexAttribArray(textureCoord);
glVertexAttribPointer(textureCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL 3);
//开启正背面剔除
glEnable(GL_CULL_FACE);
//将图形变换相关的矩阵按Uniform的方式传入
GLKMatrix4 projectMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(30.0), self.frame.size.width / self.frame.size.height, 1.0, 100.0);
GLKMatrix4 viewModelMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -15.0);
viewModelMatrix = GLKMatrix4RotateX(viewModelMatrix, GLKMathDegreesToRadians(xDegree));
viewModelMatrix = GLKMatrix4RotateY(viewModelMatrix, GLKMathDegreesToRadians(yDegree));
viewModelMatrix = GLKMatrix4RotateZ(viewModelMatrix, GLKMathDegreesToRadians(zDegree));
GLuint pro = glGetUniformLocation(self.myProgram, "projectionMatrix");
glUniformMatrix4fv(pro, 1, GL_FALSE, (GLfloat *)&projectMatrix.m00);
GLuint viewM = glGetUniformLocation(self.myProgram, "modelViewMatrix");
glUniformMatrix4fv(viewM, 1, GL_FALSE, (GLfloat *)&viewModelMatrix.m00);
//加压缩图片
GLuint texture = [self loadImage:@"cat"];
if(texture == GL_FALSE){
NSLog(@"Load Image Failed");
return;
}
//载入纹理
//激活当前纹理
glActiveTexture(texture);
//0 代表第一个纹理图片
//若有多个纹理需要载入,需先激活当前纹理,然后根据顺序从0开始算。
glUniform1i(glGetUniformLocation(self.myProgram, "colorMap"), 0);
glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
[self.myContent presentRenderbuffer:GL_RENDERBUFFER];
- 使用coreGraphics来完成图片解压缩,这里有详细的注释iOS-使用coreGraphics进行图片解压缩
- 相比顶点绘制方式而言,索引绘图只有在最后的绘制API的选择上不同:
glDrawElements
放出以上代码的部分详细注释:传送门
GLKit实现
相比GLSL来说代码量会小很多
1.EAGLContext、GLKBaseEffect初始化
代码语言:javascript复制- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//EAGLContext
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:context];
self = [super initWithFrame:frame context:context];
self.delegate = self;
self.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
self.drawableDepthFormat = GLKViewDrawableDepthFormat24;
//GLKBaseEffect初始化
self.myEffect = [[GLKBaseEffect alloc] init];
self.myEffect.texture2d0.enabled = YES;
glEnable(GL_DEPTH_TEST);
}
return self;
}
- render
//顶点、索引数据与GLSL部分一致
//顶点缓存区、着色器读取数据的方式
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), vertex, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5,(GLfloat *)NULL 0);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5,(GLfloat *)NULL 3);
//纹理图片加载
NSError *error;
GLKTextureInfo *texInfo = [GLKTextureLoader textureWithCGImage:[[UIImage imageNamed:@"cat"] CGImage] options:@{GLKTextureLoaderOriginBottomLeft: @(YES)} error:&error];
self.myEffect.texture2d0.name = texInfo.name;
self.myEffect.texture2d0.target = texInfo.target;
//绘制
[self display];
- glkView绘制代理
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(0.5, 0.3, 0.2, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//准备绘制
[self.myEffect prepareToDraw];
//索引数组
//需要根据初始位置的正背面,来确定绘制顺序(逆时针为正面)
GLuint indices[] = {
0, 2, 1, //下左
3, 2, 0, //下右
0, 1, 4, //上左
1, 2, 4, //上下
2, 3, 4, //上左
0, 4, 3, //上后
};
glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
}
- 放出以上代码的部分详细注释:传送门