学习OpenGL ES之绘制圆柱体
获取示例代码
本文将要介绍如何使用代码绘制一个圆柱体,通过绘制圆柱体可以更好的掌握法线,UV,TriangleFan,TriangleStrip等相关知识。在绘制之前,先进行一些准备工作。
GLGeometry
为了更方便的进行顶点数据的管理,我创建了一个GLGeometry
类。
typedef enum : NSUInteger {GLGeometryTypeTriangles,GLGeometryTypeTriangleStrip,GLGeometryTypeTriangleFan,
} GLGeometryType;typedef struct {GLfloat x;GLfloat y;GLfloat z;GLfloat normalX;GLfloat normalY;GLfloat normalZ;GLfloat u;GLfloat v;
} GLVertex;@interface GLGeometry () {GLuint vbo;BOOL vboValid;
}
@property (strong, nonatomic) NSMutableData *vertexData;
@end@implementation GLGeometry- (instancetype)initWithGeometryType:(GLGeometryType)geometryType
{self = [super init];if (self) {self.geometryType = geometryType;vboValid = NO;self.vertexData = [NSMutableData data];}return self;
}- (void)dealloc {if (vboValid) {glDeleteBuffers(1, &vbo);}
}- (void)appendVertex:(GLVertex)vertex {void * pVertex = (void *)(&vertex);NSUInteger size = sizeof(GLVertex);[self.vertexData appendBytes:pVertex length:size];
}- (GLuint)getVBO {if (vboValid == NO) {glGenBuffers(1, &vbo);vboValid = YES;glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, [self.vertexData length], self.vertexData.bytes, GL_STATIC_DRAW);}return vbo;
}- (int)vertexCount {return [self.vertexData length] / sizeof(GLVertex);
}
复制代码
这个类里我定义了描述顶点数据的结构体GLVertex
,描述顶点绘制方式的枚举GLGeometryType
,追加顶点数据的方法- (void)appendVertex:(GLVertex)vertex
,生成VBO的方法- (GLuint)getVBO
,获取顶点个数的方法- (int)vertexCount
。有了这些我们就可以很方便的构建3D几何体了。
分解圆柱体
如果我们有一个纸质的圆柱体模型,我们可以把它剪开成两个圆形和一个矩形。
所以我们可以将圆柱体看做三个几何体来绘制,绘制两个圆形和一个卷成桶状的矩形。我们将圆形半径定义为 radius
,矩形高为 height
,宽既是圆形的周长。
下面绘制的代码在
Cylinder
类中,Cylinder
继承自GLObject
。
绘制圆形
可以采取多边形逼近的方式绘制圆形,比如我们可以构建一个正36边形来表示一个圆。本文的代码就是利用这个原理来绘制圆的。定义构成圆形的边数为sideCount
。
- (GLGeometry *)topCircle {if (_topCircle == nil) {_topCircle = [[GLGeometry alloc] initWithGeometryType:GLGeometryTypeTriangleFan];float y = self.height / 2.0;// 中心点GLVertex centerVertex = GLVertexMake(0, y, 0, 0, 1, 0, 0.5, 0.5);[_topCircle appendVertex:centerVertex];for (int i = self.sideCount; i >= 0; --i) {GLfloat angle = i / (float)self.sideCount * M_PI * 2;GLVertex vertex = GLVertexMake(cos(angle) * self.radius, y, sin(angle) * self.radius, 0, 1, 0, (cos(angle) + 1 ) / 2.0, (sin(angle) + 1 ) / 2.0);[_topCircle appendVertex:vertex];}}return _topCircle;
}
复制代码
上面是Cylinder.m
中的代码,用来构建圆柱体上方的圆形。构建圆形是我使用的是TriangleFan,可以大大减少绘制需要的顶点数。首先添加圆心的顶点,然后围绕中心顶点,依次加入边上的顶点。上面的法线都是朝上的,既(0, 1, 0)
。UV和顶点的取值如下图所示。
图示为5条边的情况演示,第n条边的Angle等于2 * Pi * n / sideCount
,因为sin函数的范围是-1到1,所以使用(sin(angle) + 1 ) / 2.0
就可以得到0~1的uv范围。
下方的圆形和上方主要的区别就是y轴的位置和法线,它位于-height/2
处,法线向下。上方的圆形处于height/2
处,法线向上。
- (GLGeometry *)bottomCircle {if (_bottomCircle == nil) {_bottomCircle = [[GLGeometry alloc] initWithGeometryType:GLGeometryTypeTriangleFan];float y = -self.height / 2.0;// 中心点GLVertex centerVertex = GLVertexMake(0, y, 0, 0, -1, 0, 0.5, 0.5);[_bottomCircle appendVertex:centerVertex];for (int i = 0; i <= self.sideCount; ++i) {GLfloat angle = i / (float)self.sideCount * M_PI * 2;GLVertex vertex = GLVertexMake(cos(angle) * self.radius, y, sin(angle) * self.radius, 0, -1, 0, (cos(angle) + 1 ) / 2.0, (sin(angle) + 1 ) / 2.0);[_bottomCircle appendVertex:vertex];}}return _bottomCircle;
}
复制代码
细心的读者可能还会发现,循环的顺序也不一样,上面是for (int i = self.sideCount; i >= 0; --i)
,下面是for (int i = 0; i <= self.sideCount; ++i)
。为什么要这样呢?因为我开启了剔除表面,glEnable(GL_CULL_FACE);
,并且剔除的是背面glCullFace(GL_BACK);
。剔除背面就意味着背面将不会被渲染,只有正面面向摄像机的时候我们才能看到它被渲染。那么OpenGL如何判断正面还是背面呢?
- (void)draw:(GLContext *)glContext {glEnable(GL_CULL_FACE);glCullFace(GL_BACK);...
}
复制代码
Cull Face
默认情况下,投影到屏幕后顶点顺序为逆时针的面为正面。
图中右边的是逆时针,所以如果使用了Cull Face,我们只能看见右边的面。当然你也可以使用 void glFrontFace(GLenum mode);
将顺时针改为正面。
因为我需要顶部圆形的上面一侧显示,所以必须保证从上往下看时,组成三角形的顶点顺序是逆时针的。底部的圆形则相反,从下往上看时,需要保证组成三角形的顶点顺序是逆时针的。
绘制中间的矩形
中间的矩形可以使用三角带来绘制。
- (GLGeometry *)middleCylinder {if (_middleCylinder == nil) {_middleCylinder = [[GLGeometry alloc] initWithGeometryType:GLGeometryTypeTriangleStrip];float yUP = self.height / 2.0;float yDOWN = -self.height / 2.0;for (int i = 0; i <= self.sideCount; ++i) {GLfloat angle = i / (float)self.sideCount * M_PI * 2;GLKVector3 vertexNormal = GLKVector3Normalize(GLKVector3Make(cos(angle) * self.radius, 0, sin(angle) * self.radius));GLVertex vertexUp = GLVertexMake(cos(angle) * self.radius, yUP, sin(angle) * self.radius, vertexNormal.x, vertexNormal.y, vertexNormal.z, i / (float)self.sideCount, 0);GLVertex vertexDown = GLVertexMake(cos(angle) * self.radius, yDOWN, sin(angle) * self.radius, vertexNormal.x, vertexNormal.y, vertexNormal.z, i / (float)self.sideCount, 1);[_middleCylinder appendVertex:vertexDown];[_middleCylinder appendVertex:vertexUp];}}return _middleCylinder;
}
复制代码
可以把它看做self.sideCount
个矩形组成的几何体。只需要按照下图方向依次追加顶点即可。
注意添加顶点时候我使用的是从0到2Pi的方向,正如上图所示,这样才能保证顶点顺序是逆时针的。UV直接使用顶点在宽高上的比例即可。
绘制圆柱体
有了这三个几何体,就可以组合成一个圆柱体了。下面是绘制代码。
- (void)draw:(GLContext *)glContext {glEnable(GL_CULL_FACE);glCullFace(GL_BACK);[glContext setUniformMatrix4fv:@"modelMatrix" value:self.modelMatrix];bool canInvert;GLKMatrix4 normalMatrix = GLKMatrix4InvertAndTranspose(self.modelMatrix, &canInvert);[glContext setUniformMatrix4fv:@"normalMatrix" value:canInvert ? normalMatrix : GLKMatrix4Identity];[glContext bindTexture:self.diffuseTexture to:GL_TEXTURE0 uniformName:@"diffuseMap"];[glContext drawGeometry:self.topCircle];[glContext drawGeometry:self.bottomCircle];[glContext drawGeometry:self.middleCylinder];
}
复制代码
和之前唯一不同的是[glContext drawGeometry:self.topCircle];
方法,这个是新增的用于绘制GLGeometry
的方法。实现如下:
- (void)drawGeometry:(GLGeometry *)geometry {glBindBuffer(GL_ARRAY_BUFFER, [geometry getVBO]);[self bindAttribs:NULL];if (geometry.geometryType == GLGeometryTypeTriangleFan) {glDrawArrays(GL_TRIANGLE_FAN, 0, [geometry vertexCount]);} else if (geometry.geometryType == GLGeometryTypeTriangles) {glDrawArrays(GL_TRIANGLES, 0, [geometry vertexCount]);} else if (geometry.geometryType == GLGeometryTypeTriangleStrip) {glDrawArrays(GL_TRIANGLE_STRIP, 0, [geometry vertexCount]);}
}
复制代码
主要就是根据不同的geometryType
绘制vbo,很好理解。最后回到ViewController
,利用三个圆柱体组装个锤子吧。
- (void)createCylinder {GLKTextureInfo *metal1 = [GLKTextureLoader textureWithCGImage:[UIImage imageNamed:@"metal_01.png"].CGImage options:nil error:nil];GLKTextureInfo *metal2 = [GLKTextureLoader textureWithCGImage:[UIImage imageNamed:@"metal_02.jpg"].CGImage options:nil error:nil];GLKTextureInfo *metal3 = [GLKTextureLoader textureWithCGImage:[UIImage imageNamed:@"metal_03.png"].CGImage options:nil error:nil];// 四边的圆柱体就是一个四方体Cylinder * cylinder = [[Cylinder alloc] initWithGLContext:self.glContext sides:4 radius:0.9 height:1.2 texture:metal1];cylinder.modelMatrix = GLKMatrix4MakeTranslation(0, 2, 0);[self.objects addObject:cylinder];Cylinder * cylinder2 = [[Cylinder alloc] initWithGLContext:self.glContext sides:16 radius:0.2 height:4.0 texture:metal3];[self.objects addObject:cylinder2];// 四边的圆柱体就是一个正方体Cylinder * cylinder3 = [[Cylinder alloc] initWithGLContext:self.glContext sides:4 radius:0.41 height:0.3 texture:metal2];cylinder3.modelMatrix = GLKMatrix4MakeTranslation(0, -2, 0);[self.objects addObject:cylinder3];
}
复制代码
最终效果图如下。
本文通过绘制圆柱体来介绍使用代码生成基本几何体的思路和方法。下篇文章中将介绍如何使用一张地形图片生成一个复杂的地形模型,敬请期待。
学习OpenGL ES之绘制圆柱体相关推荐
- 从零开始学习OpenGL ES之五 – 材质
从零开始学习OpenGL ES之五 – 材质 作者: iPhoneGeek 爱疯极客 09-Jan-10 iPhone Development 浏览次数: 411 | 评论 ↓ Tweet Shar ...
- 从显示一张图片开始学习OpenGL ES
前言 网上很多介绍OpenGL ES的文章,但由于OpenGL ES内容太多,所以这些文章难免过于臃肿杂乱,很难抓住重点,对于初学者来说最后还是云里雾里.很多人(包括笔者本人)开始深入了解OpenGL ...
- OpenGL ES之三——绘制纯色背景
概述 这是一个系列的Android平台下OpenGl ES介绍,从最基本的使用最终到VR图的展示的实现,属于基础篇.(后面针对VR视频会再有几篇文章,属于进阶篇) OpenGL ES之一--概念扫盲 ...
- 【OpenGL ES】绘制圆形
1 前言 [OpenGL ES]绘制三角形 中介绍了绘制三角形的方法,[OpenGL ES]绘制正方形中介绍了绘制正方形的方法,本文将介绍绘制圆形的方法. OpenGL 以点.线段.三角形为图元,没有 ...
- 【OpenGL ES】绘制魔方
1 前言 在立方体贴图(6张图)中,绘制了一个立方体,贴了 6 张图,本文的魔方案例,将实现绘制 27个立方体,贴 162 张图.贴图图片如下: 说明:inside.png 为魔方内部色块,用粉红色块 ...
- 【OpenGL ES】绘制立方体
1 前言 本文主要介绍使用 OpenGL ES 绘制立方体,读者如果对 OpenGL ES 不太熟悉,请回顾以下内容: 绘制三角形 绘制彩色三角形 绘制正方形 绘制圆形 在绘制立方体的过程中,主要用到 ...
- 【OpenGL ES】绘制正方形
1 前言 [OpenGL ES]绘制三角形 中介绍了绘制三角形的方法,本文将介绍绘制正方形的方法. OpenGL 以点.线段.三角形为图元,没有提供绘制正方形内部的接口.要绘制正方形内部,必须通过三角 ...
- [转载]从零开始学习OpenGL ES之八 – 交叉存取顶点数据
Technote 2230提出了很多用OpenGL ES来提升iphone程序性能的建议.我们现在远远不能深刻理解OpenGL ES所以你需要学习以下内容.不信?是真的,试试看,我等着你的读后感. 好 ...
- 学习OpenGL ES之透明和混合
获取示例代码 本文主要讲解OpenGL ES对于透明颜色的处理,在例子中我绘制了三个平面,分别赋予绿色半透明纹理,红色半透明纹理,和不透明纹理. 首先为这三张图生成纹理. - (void)genTex ...
最新文章
- Xamarin Visual Studio不识别JDK路径
- 世界四大重要检索系统简介
- C# 系统应用之TreeView控件 (一).显示树状磁盘文件目录及加载图标
- opencv实现对象跟踪_如何使用opencv跟踪对象的距离和角度
- EasyNVR、EasyDSS二次开发之:RTMP、HLS流在web页面进行无插件播放示例Demo代码
- ThinkPad R400 安装win2003网卡驱动
- windows10+MongDb4.0.4下载和安装
- Bzoj 2683: 简单题(CDQ分治)
- Spring 3.x jar 包详解 与 依赖关系
- 计算机网络使用的通信线路分为两类,计算机网络技术阶段测试题
- kvm 上部署虚拟机两种方法
- Mac升级node版本
- [语音识别] 单音素、三音素、决策树
- 手写HashMap,快手面试官直呼内行
- 统计学 计算机论文发表,数学科学学院博士生史册在统计学顶级期刊
《Annals of Statistics》上发表论文...
- 图文详解!java开发面试简历模板java
- mysql python电影院购票系统毕业设计源码221133
- 苹果更新提示:已接入无线局域网却提示需要接入
- mysql运行测速_定时检测网测速
- 技嘉主板设置硬盘启动操作教程