获取示例代码


本文将要介绍如何使用代码绘制一个圆柱体,通过绘制圆柱体可以更好的掌握法线,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之绘制圆柱体相关推荐

  1. 从零开始学习OpenGL ES之五 – 材质

    从零开始学习OpenGL ES之五 – 材质 作者: iPhoneGeek 爱疯极客 09-Jan-10 iPhone Development 浏览次数: 411 |  评论 ↓ Tweet Shar ...

  2. 从显示一张图片开始学习OpenGL ES

    前言 网上很多介绍OpenGL ES的文章,但由于OpenGL ES内容太多,所以这些文章难免过于臃肿杂乱,很难抓住重点,对于初学者来说最后还是云里雾里.很多人(包括笔者本人)开始深入了解OpenGL ...

  3. OpenGL ES之三——绘制纯色背景

    概述 这是一个系列的Android平台下OpenGl ES介绍,从最基本的使用最终到VR图的展示的实现,属于基础篇.(后面针对VR视频会再有几篇文章,属于进阶篇) OpenGL ES之一--概念扫盲 ...

  4. 【OpenGL ES】绘制圆形

    1 前言 [OpenGL ES]绘制三角形 中介绍了绘制三角形的方法,[OpenGL ES]绘制正方形中介绍了绘制正方形的方法,本文将介绍绘制圆形的方法. OpenGL 以点.线段.三角形为图元,没有 ...

  5. 【OpenGL ES】绘制魔方

    1 前言 在立方体贴图(6张图)中,绘制了一个立方体,贴了 6 张图,本文的魔方案例,将实现绘制 27个立方体,贴 162 张图.贴图图片如下: 说明:inside.png 为魔方内部色块,用粉红色块 ...

  6. 【OpenGL ES】绘制立方体

    1 前言 本文主要介绍使用 OpenGL ES 绘制立方体,读者如果对 OpenGL ES 不太熟悉,请回顾以下内容: 绘制三角形 绘制彩色三角形 绘制正方形 绘制圆形 在绘制立方体的过程中,主要用到 ...

  7. 【OpenGL ES】绘制正方形

    1 前言 [OpenGL ES]绘制三角形 中介绍了绘制三角形的方法,本文将介绍绘制正方形的方法. OpenGL 以点.线段.三角形为图元,没有提供绘制正方形内部的接口.要绘制正方形内部,必须通过三角 ...

  8. [转载]从零开始学习OpenGL ES之八 – 交叉存取顶点数据

    Technote 2230提出了很多用OpenGL ES来提升iphone程序性能的建议.我们现在远远不能深刻理解OpenGL ES所以你需要学习以下内容.不信?是真的,试试看,我等着你的读后感. 好 ...

  9. 学习OpenGL ES之透明和混合

    获取示例代码 本文主要讲解OpenGL ES对于透明颜色的处理,在例子中我绘制了三个平面,分别赋予绿色半透明纹理,红色半透明纹理,和不透明纹理. 首先为这三张图生成纹理. - (void)genTex ...

最新文章

  1. Xamarin Visual Studio不识别JDK路径
  2. 世界四大重要检索系统简介
  3. C# 系统应用之TreeView控件 (一).显示树状磁盘文件目录及加载图标
  4. opencv实现对象跟踪_如何使用opencv跟踪对象的距离和角度
  5. EasyNVR、EasyDSS二次开发之:RTMP、HLS流在web页面进行无插件播放示例Demo代码
  6. ThinkPad R400 安装win2003网卡驱动
  7. windows10+MongDb4.0.4下载和安装
  8. Bzoj 2683: 简单题(CDQ分治)
  9. Spring 3.x jar 包详解 与 依赖关系
  10. 计算机网络使用的通信线路分为两类,计算机网络技术阶段测试题
  11. kvm 上部署虚拟机两种方法
  12. Mac升级node版本
  13. [语音识别] 单音素、三音素、决策树
  14. 手写HashMap,快手面试官直呼内行
  15. 统计学 计算机论文发表,数学科学学院博士生史册在统计学顶级期刊 《Annals of Statistics》上发表论文...
  16. 图文详解!java开发面试简历模板java
  17. mysql python电影院购票系统毕业设计源码221133
  18. 苹果更新提示:已接入无线局域网却提示需要接入
  19. mysql运行测速_定时检测网测速
  20. 技嘉主板设置硬盘启动操作教程

热门文章

  1. 绘画技巧:怎样才能画好动漫人物身体比例?
  2. 科技新品 | TCL平板电脑等亮相IFA 2020;华为首推方形智能手表;卡西欧新款G-SHOCK可互换表圈表带...
  3. 酷狗java秋招笔试题
  4. MySQL 主键详解
  5. NullPointException空指针异常
  6. Python-项目实战--飞机大战-碰撞检测(8)
  7. Android应用性能优化下电子书pdf下载
  8. 好用的企业直播平台有哪些
  9. 更换电脑,文件数据如何快速迁移?
  10. java购物车jsp 代码,基于JSP java web Sql 简单的购物车源代码