深入OpenGL ES 2.0(iOS):从小三角起步教程[第一期]

更新:11-23 现代故事 我要投稿 纠错 投诉

大家好,关于深入OpenGL ES 2.0(iOS):从小三角起步教程[第一期]很多朋友都还不太明白,今天小编就来为大家分享关于的知识,希望对各位有所帮助!

c.分析

第二步,怎么去画(纯理论)

a.OpenGL ES 2渲染管线

b.简单描述一下绘制过程的各个单元【从左到右】

1) OpenGL ES 2.0 API

2) 顶点数组/缓冲区对象

3)顶点着色器

4) 原始装配

5)光栅化

6) 纹理内存

7) 片段着色器

8) 每个片段的操作

9) 渲染缓冲区帧缓冲区

10) EAGL API

c. OpenGL ES Shader语言简介

*) 简单流程图

第三步,怎么去画(实战)

a.OpenGL ES 2渲染流程细化

1)配置环境

2)初始化数据

3)配置OpenGL ES着色器

4)渲染和绘图

b.流程编码

1.配置渲染环境

1)配置渲染窗口【继承自UIView】

2)配置渲染上下文

3)配置帧渲染

4)配置渲染缓存

5)帧缓冲区加载渲染缓存的内容

6)渲染上下文绑定渲染窗口(层)

2.修改背景颜色

3. 初始化数据

4.配置OpenGL ES着色器

1)编写顶点着色器代码文件

2)编写Fragment Shader Code文件

3)配置顶点着色器

4) 配置片段着色器

5) 创建着色器程序

6)加载顶点着色器和片段着色器

7) 链接着色器程序

5. 渲染和绘图

1)清除旧的渲染缓存

2)设置渲染窗口

3)使用Shder程序

4) **相关数据**

5)绘制图形

c.面向对象的重新设计

第四步,练练手

修改背景颜色

b.修改三角形的填充颜色

c.修改三角形三个顶点的颜色(填充颜色)

第一步,明确要干嘛

1. 目标:

使用OpenGL ES 2.0在iOS模拟器中绘制三角形。

2. 效果:

3. 分析图形:

背景颜色为蓝色

--修改背景颜色

直角三角形

--画一个三角形

4.绘制三角形?三角形由什么组成?

--三个端点+三条线+中间填充色,即三个点连成一条线,形成一个三角面。

1)。三个端点(屏幕坐标点)是什么?

要回答这个问题,首先要了解OpenGL ES的坐标系在屏幕上是如何分布的:

OpenGL ES坐标系{x,y,z}注:图片摘自书籍《Learning OpenGL ES For iOS》

一个。从图片的三维坐标系可知:

- 它是一个三维坐标系{x,y,z}

- 三维坐标中心位于立方体的几何中心{0, 0, 0}

- 整个坐标系是[0, 1]的点,也就是说OpenGL只支持0~1之间的点

注意,这里提到的0和1最好理解为0——无限小和1——无限大。它不是指0个单位的长度或1个单位的长度。

b.我们来看看我们画的三角形的坐标在iOS模拟器或者真机上是如何形成的:

三维坐标+坐标值演示图例:图片通过CINEMA4D(c4d)三维软件绘制

二维就是这个样子:

三条线的二维坐标( z=0 )?一个。连接三个端点形成一个封闭的三角形曲面。 OpenGL ES可以直接画三角形吗? ——答案是肯定的。

b.那么OpenGL可以直接画正方形吗?

——答案是否定的。

c. OpenGL可以直接画什么?

——答案是:点精灵、线、三角形,统称为图元。

注:答案来自章节《OpenGL ES 2.0 Programming Guide》 7. Primitive Assembly and Rasterization。截图如下:

线元素线带是指首尾相连的线段。第一行和最后一行没有连接在一起;

线环是指首尾相连的线段。第一条线和最后一条线连接在一起,即一条闭合曲线;

线三角形基元Triangle Strip 指条状、相互连接的三角形

三角扇,指扇形,三角形相互连接

三角形

扇形点精灵【主要用于纹理】 3)填充颜色?

指的是RGBA的颜色值; (感觉很没用,但还是要说)

第二步,怎么去画(纯理论)

怎么去画,就是通过多少个步骤完成一个完整的绘制渲染流程,当然这里指 OpenGL ES 2 的渲染管线流程)

OpenGL ES 2 的渲染管线

图形管道

因为这是iOS端的图,所以我重新画了一下:

OpenGL ES 2 渲染流程图注:此图是根据《OpenGL ES 2.0 programming guide》 的Graphics Pipeline 和Diney Bomfim 的管线图重新绘制的[All about OpenGL ES 2.x - (part 2/3)]。 【绘图软件为:Visio 2016】

1. 简述绘制流程的每一个单元【至左向右】

OpenGL ES 2.0 API :iOS环境

gltypes.h包含OpenGL ES 2.0基本数据类型的定义;

glext.h包含各种宏定义,以及矩阵运算等常用函数;

gl.h是OpenGL ES 2.0的所有核心函数(命令);扩大

OpenGL ES 2.0参考(函数查询)在线

只需在左侧选择您要查询的功能即可

离线功能卡

点击红框即可打开

红色箭头处选择save本人推荐使用离线的卡,不受网络影响,而且一目了然。配合官方的编程指南使用就最佳了。

2. Vertex Arrays / Buffer Objects :

__Vertex Arrays Objects__(缩写:VAOs),顶点数组对象是一个数组,包括顶点坐标、颜色值、纹理坐标等数据;通过CPU内存关联到GPU 内存区域供GPU使用; [官方解释:顶点数据可能来自存储在应用程序内存(通过指针)或更快的GPU 内存(在缓冲区对象中)中的数组。 (含义:顶点数组存储在程序内存中或快速GPU内存中,前者通过数组指针访问数据,后者直接通过Buffer Objects【就是指 VAOs 或 VBOs 方式访问】访问)]

绘制的三角形数组(三个顶点(终点)点坐标)如下图:

顶点数组

VF顶点

这是C语言知识,应该不难理解。

__Vertex Buffer Objects__,(缩写:VBOs[Vertex Buffer Objects]),缓存对象是保存顶点数组数据或数据下标的对象[不是指面向对象中的对象,实际上是一个GPU内存块]。 【官方解释:Buffer对象在高性能服务器内存中保存顶点数组数据或索引。 (含义:VBO是保存存储在GPU快速内存区域中的顶点数据或顶点数据索引的缓存对象。)]

a.为什么是服务器?

--答,OpenGL是基于CS模式设计的。客户端操作相当于我们编写的OpenGL API(OpenGL命令)的各种操作,而服务端则是与图形处理相关的硬件。 (ES当然也是这个意思。)

【官方解释:OpenGL是作为客户端-服务器系统实现的,您编写的应用程序被视为客户端,而您的计算机图形硬件制造商提供的OpenGL实现被视为服务器。】

笔记:

**1) **a.b 中的[官方解释.]可以在OpenGL ES 2.0 参考卡中找到。

**2) **b.1的【官方解释.】在《OpenGL简介》第八版《OpenGL Programming Guide》《OpenGL简介》第8版“什么是OpenGL”章节第一节中进行了解释。

3. __Vertex Shader (顶点着色器) : __

处理顶点相关数据,包括顶点在屏幕上的位置(矩阵变换)、顶点处的光照计算、纹理坐标等。

顶点着色器的信号图:注:图片取自:《OpenGL ES 2.0 Programming Guide》 1.OpenGL ES 2.0简介-- OpenGL ES 2.0 -- 顶点着色器部分

__输入信号:__属性、制服、采样器(可选)a。Attributes :属性的含义是指各个顶点数据;

b. __制服: __

b-1.统一含义是只读全局常量,存储在程序的常量区;

b-2.当Vertex Shader和Fragment Shader定义了同名同类型的Uniform常量时,此时的Uniform常量就变成了全局常量(指向同一内存区域的常量);

c. __采样器(可选): __

它是一种特殊的Uniforms,保存Textures(纹理)数据;

输出信号:变化__变化:__

一个。它是Vertex Shader和Fragment Shader之间的接口,旨在解决功能问题(两个Shader之间的信息交换);

b.存储Vertex Shader的输出信息;

c. Vertex Shader 和Fragment Shader 中必须存在同名同类型的Varying 变量,否则会出现编译错误;(因为它是两个 Shader 的信息接口啊,不一样还接什么口啊。)交互信息:临时变量Temporary Variables :指临时变量;

b.用于存储Shader处理过程中的中间值;

c.在函数或变量内部声明;

__用于输出的内置变量:__gl_Position、gl_FrontFacing、gl_PointSizea.gl_Position **(highp vec4 变量):

它是Vertex Position,即Vertex Shader的输出值,必须为其赋值的变量;。仅在顶点着色器中使用时才有效**;

注:highp vec4,highp(high precision)表示高精度,是精度限定词; vec4(Floating Point Vector)是浮点向量,OpenGL ES的数据类型。

b. __ gl_PointSize(中浮点变量):__

告诉顶点着色器光栅化点的大小(像素、像素化)。如果要改变绘制点的大小,需要使用这个变量只有在 Vertex Shader 中使用才会有效

注:mediump、mediump(中等精度)表示中等精度,是精度限定符;最后一个精度限定符是lowp(低精度),表示低精度。

c. __ gl_FrontFacing(布尔变量): __

更改渲染对象的正面和背面,它们是用于处理对象光照问题的变量。仅在处理双面光照(3D物体的内部和外部光照)问题时使用的变量,只能在 Vertex Shader 中进行设置, Fragment Shader 是只读的

4.Primitive Assembly (图元装配) :

第一步,将Vertex Shader处理后的顶点数据组织成OpenGL ES可以直接渲染的基本图元:点、线、三角形;

第二步是裁剪(Clipping),只保留渲染区域(视锥体、可视区域)内的图元;

第二步是剔除。你可以通过编程决定消除前面、后面或全部;

笔记:

视锥体实际上是由相机与物体的捕捉关系形成的三维圆锥体所包含的空间区域;

查看视锥体图像源《透视投影详解》文章

5.Rasterization ( 光栅化 ) :

光栅化信号图:

它的作用是将基本图元(点、线、三角形)转换为二维片段(Fragments,包括二维坐标、颜色值、纹理坐标等属性),并将基本图元像素化,以便可以显示在屏幕。绘制(显示)。

6. __Texture Memory ( 纹理内存 ) : __

纹理指的是保存图片所有颜色(位图)的缓存; Texture Memory是图片的颜色(像素)内存;每个嵌入式系统对Texture Memory的大小都有限制;

在完整的iOS渲染管线图中,向上指向Vertex Shader的虚线表示通过程序向其提供Texture Coefficient(纹理坐标)信息;

在完整的iOS渲染管线图中,实线指向Fragment Shader,因为Fragment Shader处理的是光栅化数据,即像素数据,而Texture本身就是像素数据,所以Texture Memory可以直接作为Fragment Shader的输入;

7.Fragment Shader ( 片元着色器 ) :

片段着色器信号图:

输入信号:变化、制服、样品

与Vertex Shader的输入意义相同。详细请看Vertex Shader的解释~~~;

输入的内建变量:gl_FragCoord、gl_FrontFacing、gl_PointCoord

一个。 __ gl_FragCoord(中型vec4只读变量):__

是一个变量{ x, y, z, 1/w },保存窗口的相对坐标,z 表示深度(将用于片段的深度),w 表示旋转;

b. __ gl_PointCoord (mediump int 只读变量) : __

是一个二维坐标,包含当前片段的原点位置;点的范围是[0, 1];

c.gl_FrontFacing

参见顶点着色器的解释;

输出信号 (内建变量) :gl_FragColor、gl_FragData(图中未显示) **gl_FragColor(中vec4)** :

片段的颜色值;

b. __gl_FragData(中型vec4): __

是一个数组,片段颜色集;

注意:两个输出信号只能同时存在其中之一。如果写gl_FragColor,就不要写gl_FragData,反之亦然; [如果着色器静态地为gl_FragColor 赋值,则它可能不会为gl_FragData 的任何元素赋值。如果着色器静态地将值写入gl_FragData 的任何元素,则它可能不会为gl_FragColor 分配值。That is, a shader may assign values to either gl_FragColor or gl_FragData, but not both.

补充知识 ( For Shader )

8. __Per-Fragment Operations : __

信号图:

Pixel ownership test ( 像素归属测试 ) :判断该像素在Framebuffer中的位置是否属于当前OpenGL ES Context所有,即测试某个像素是否属于当前Context或者是否显示(是否对用户可见);

Scissor Test ( 裁剪测试 ) :判断像素是否在glScissor*定义的裁剪区域内。不在裁剪区域内的像素将被丢弃;

Stencil Test ( 模版测试 ):将模板缓存中的值与参考值进行比较,进行相应的处理;

Depth Test ( 深度测试 ) :比较下一个片段与帧缓冲区中片段的深度,以确定哪个像素在前面,哪个像素被遮挡;

Blending ( 混合 ) :将片段的颜色与帧缓冲区中已有的颜色值混合,并将新的混合值写入帧缓冲区(FrameBuffer);

Dithering ( 抖动 ) :使用有限的颜色,让您看到比实际图像更丰富的色彩显示,缓解因表示颜色值不够准确而导致颜色发生剧烈变化的问题。

9.Render Buffer Frame Buffer:

关系图:

__Render Buffer(渲染缓冲区): __a。缩写为RBO,渲染缓冲对象;

b.是应用程序(Application)分配的2D图像缓存;

c. Render Buffer可以分配和存储颜色、深度和模板(sectil)值,也可以将这三个值加载到Frame Buffer中;

__Frame Buffer(帧缓冲区): __a。缩写为FBO,帧缓冲对象;

b.是FBO上加载的颜色、深度、模板缓存的所有加载点的集合;

c.描述模板颜色、深度、大小和类型的属性状态;

d.描述Texture名称的属性状态;

e.描述FBO上加载的渲染缓冲区对象的属性状态;

扩充知识(FBO):FBO API支持的操作如下:

FBO对象只能通过OpenGL ES命令(API)创建;使用一个EGL上下文来创建和使用多个FBO,即不要为每个FBO对象创建一个渲染上下文;创建离屏颜色、Depth、模板渲染缓存和纹理需要在FBO上加载;通过多个FBO共享颜色、深度、模板缓存;正确地将纹理颜色或深度加载到FBO中以避免复制操作;

10.EAGL API :

官方的是EGL API 与平台无关,因为可以由平台自己定制,所以已经被苹果定制成了iOS下的EAGL API。

EAGL.h:中的核心类是EAGLContext,上下文环境;

EAGLDrawable.h: 用于渲染绘图输出的EAGLContext 分类;

注意:除了上面两个之外,还有一个类CAEAGLLayer,它是iOS端的渲染窗口托管层;

【 看这里:EGL API 设计用于在OpenGL ES 2 中的窗口系统(屏幕,iOS 是View,其中CAEAGLLayer 类是托管层)中进行渲染和绘制;

EGL 渲染的先决条件是:

一个。一个可以显示的设备(当然是iOS下的手机或者模拟器)

b.创建渲染表面(rendering surface)、设备的屏幕(on-screen)或者像素缓冲区(pixel Buffer)(离屏)

注意:Pixel Buffer,这种缓冲区不能直接显示,只能成为渲染表面或者通过其他API共享。例如:pbuffers常用于Texture贴图,因为Texture本身也是一个像素;

创建渲染上下文(rendering context),即OpenGL ES 2 Rendering Context;笔记:

__OpenGL ES Context : __保存渲染过程中的所有数据和状态信息;

图解说明:

图片取自RW。开始。 OpenGL ES 和GLKit 教程

OpenGL ES Shader Language 简述

流程图中出现的 Vertex Shader 与 Fragment Shader 都是要使用 GLSL ES 语言来进行编程操作的

1. GLSL ES 版本:

OpenGL ES 2.0对应的GLSL ES版本为1.0,版本号为100;

2.iOS着色器类:

h4>iOS 环境下 GLKit 提供了一个简单的 Shader 类——GLKBaseEffect 类; GLKit APIs

3. OpenGL 本身是 C Base 的语言,可以适应多个平台,而在 iOS 下的封装就是 GLKit ;

4. GLSL ES (也称 ESSL ) ?

简单流程图: OpenGL ES Shader 流程图编写 Shader 代码:a. 同时编写 Vertex Code 和 Fragment Code b. 建议以文件的形式来编写,不建议使用 " ...... " 字符串的形式进行编写,前者会有编译器的提示作为辅助防止一定的输入错误,但后者不会,为了不必要的麻烦,使用前者; c. 文件的名称使用应该要形如 xxxVertexShader.glsl / xxxFragmentShader.glsl; 注:(其实文件名和后缀都可以随意的,但是你在编程的时候为了可读性,建议这样写,也是为了防止不必要的麻烦);【 Xcode 只会在 glsl 的文件后缀的文件进行提示,当然有时候会抽一风也是正常的 】 d. 要掌握的知识点是 Shader 的 Data Typies(数据类型,如:GLfloat 等)、Build-in Variables(内置变量,如:attribute 等)、流程控制语句(if、while 等); 除编写 Shader Code 外,其它的流程都由一个对应的 GLSL ES 的 API (函数)进行相应的操作;注:此处只是做了一个 Program 的图,不是只能有一个 Program,而是可以有多个,需要使用多少个,由具体项目决定。

第三步,怎么去画(实战)

以本文的小三角为例,开始浪吧~~~! e981fd1c1e0c35f7e91735fb473b2bec.gif

OpenGL ES 2 的渲染流程 实际绘制环境,流程细化

OpenGL ES 2 iOS 渲染逻辑流程图.png

1.配置环境:

主要工作是,EAGL API 的设置。 EAGL Class核心操作: a. CAEAGLLayer 替换默认的 CALayer,配置绘制属性; b. EAGLContext,即 Render Context ,设置成** OpenGL ES 2 API ** 环境,并使其成为当前活跃的上下文环境; c. Frame Buffers / Render Buffer 的创建和使用,以及内容绑定; d. **EAGLContext 绑定渲染的窗口 (on-screen),CAEAGLLayer **; 扩展: ** CAEAGLLayer **继承链:CALayer有的,当然 CAEAGLLayer 也有;作用:a. The CAEAGLLayer class supports drawing OpenGL content in iPhone applications. If you plan to use OpenGL for your rendering, use this class as the backing layer for your views by returning it from your view’s layerClass class method. The returned CAEAGLLayer object is a wrapper for a Core Animation surface that is fully compatible with OpenGL ES function calls. -->大意就是,CAEAGLLayer 是专门用来渲染 OpenGL 、OpenGL ES 内容的图层;如果要使用,则要重写 layerClass 类方法。b. Prior to designating the layer’s associated view as the render target for a graphics context, you can change the rendering attributes you want using the drawableProperties property. -->大意就是,在 EAGLContext 绑定 CAEAGLLayer 为渲染窗口之前,可以通过修改 drawableProperties 属性来改变渲染属性。使用注意:a. 修改 opaque 属性为 YES ( CAEAGLLayer.opaque = YES; ); b. 不要修改 Transform ; c. 当横竖屏切换的时候,不要去修改 CAEAGLLayer 的 Transform 而进行 Rotate, 而是要通过 OpenGL / OpenGL ES 来 Rotate 要渲染的内容。EAGLContext ** 是管理 OpenGL ES 渲染上下文(包含,信息的状态、openGL ES 的命令(API)、OpenGL ES 需要绘制的资源)的对象,要使用 OpenGL ES 的 API (命令)就要使该 Context 成为当前活跃的渲染上下文。(原文: An EAGLContext object manages an OpenGL ES rendering context—the state information, commands, and resources needed to draw using OpenGL ES. To execute OpenGL ES commands, you need a current rendering context.)

2. 初始化数据

这里主要是考虑是否使用 VBOs ,由于移动端对效率有所要求,所以一般采用 VBOs 快速缓存;

3. 配置 OpenGL ES Shader

这里的核心工作是 Shader Code ,即学习 GLSL ES 语言;iOS 端采用 glsl 后缀的文件来编写代码;

4. 渲染绘制

这里要注意的是 清空旧缓存、设置窗口,虽然只是一句代码的问题,但还是很重要的;核心是学习 glDraw* 绘制 API ;

流程代码化

1.配置渲染环境

配置渲染窗口 [ 继承自 UIView ]a. 重写 layerClass 类方法 + (Class)layerClass { return [CAEAGLLayer class]; }b. 配置 drawableProperties ,就是绘制的属性 - (void)commit { CAEAGLLayer *glLayer = (CAEAGLLayer *)self.layer; // Drawable Property Keys /* // a. kEAGLDrawablePropertyRetainedBacking // The key specifying whether the drawable surface retains its contents after displaying them. // b. kEAGLDrawablePropertyColorFormat // The key specifying the internal color buffer format for the drawable surface. */ glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @(YES), // retained unchange kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8 // 32-bits Color }; glLayer.contentsScale = [UIScreen mainScreen].scale; glLayer.opaque = YES; }配置渲染上下文// a. 定义 EAGLContext @interface VFGLTriangleView () @property (assign, nonatomic) VertexDataMode vertexMode; @property (strong, nonatomic) EAGLContext *context; @end// b. 使用 OpenGL ES 2 的 API,并使该 Context ,成为当前活跃的 Context - (void)settingContext { self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; [EAGLContext setCurrentContext:self.context]; }配置帧渲染- (GLuint)createFrameBuffer { GLuint ID; glGenFramebuffers(FrameMemoryBlock, &ID); glBindFramebuffer(GL_FRAMEBUFFER, ID); return ID; }函数描述glGenFramebuffers创建 帧缓存对象glBindFramebuffer使用 帧缓存对象glGenFramebuffersvoidglGenFramebuffers(GLsizein,GLuint* framebuffers)n指返回多少个 Frame Buffer 对象framebuffers指 Frame Buffer 对象的标识符的内存地址glBindFramebuffervoidglBindFramebuffer(GLenumtarget,GLuintframebuffer)target只能填 GL_FRAMEBUFFERframebuffer指 Frame Buffer 对象的标识符配置渲染缓存- (GLuint)createRenderBuffer { GLuint ID; glGenRenderbuffers(RenderMemoryBlock, &ID); glBindRenderbuffer(GL_RENDERBUFFER, ID); return ID; }函数描述glGenRenderbuffers创建 渲染缓存对象glBindRenderbuffer使用 渲染缓存对象glGenRenderbuffersvoidglGenRenderbuffers(GLsizein,GLuint*renderbuffers)n指返回多少个 Render Buffer 对象renderbuffers指 Render Buffer 对象的标识符的内存地址glBindRenderbuffervoidglBindRenderbuffer(GLenumtarget,GLuintrenderbuffer)target只能填 GL_RENDERBUFFERrenderbuffers指 Render Buffer 对象的标识符帧缓存装载渲染缓存的内容- (void)attachRenderBufferToFrameBufferWithRenderID:(GLuint)renderBufferID { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferID); }函数描述glFramebufferRenderbuffer装载 渲染缓存的内容到帧缓存对象中glFramebufferRenderbuffervoidglFramebufferRenderbuffer(GLenumtarget,GLenumattachment,GLenumrenderbuffertarget,GLuintrenderbuffer)target只能填 GL_FRAMEBUFFERattachment*只能是三个中的一个:GL_COLOR_ATTACHMENT0 ( 颜色缓存 )、GL_DEPTH_ATTACHMENT ( 深度缓存 )、GL_STENCIL_ATTACHMENT ( 模板缓存 ) *renderbuffertarget只能填 GL_RENDERBUFFERrenderbuffer指 Render Buffer 对象的标识符,而且当前的 Render Buffer 对象一定要是可用的渲染上下文绑定渲染窗口(图层)- (void)bindDrawableObjectToRenderBuffer { [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]; }函数描述renderbufferStorage: fromDrawable:关联 当前渲染上下文和渲染窗口renderbufferStorage: fromDrawable:- (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id)drawabletarget只能填 GL_RENDERBUFFERdrawable*只能是 CAEAGLLayer 对象 *函数解释: 为了使创建的 Render Buffer 的内容可以显示在屏幕上,要使用这个函数绑定 Render Buffer 而且分配共享内存;要显示 Render Buffer 的内容, 就要使用 presentRenderbuffer:来显示内容;这个函数的功能等同于 OpenGL ES 中的它【内容太多,简书不好排版】函数描述glRenderbufferStorage保存渲染缓存内容glRenderbufferStoragevoidglRenderbufferStorage(GLenumtarget,GLenuminternalformat,GLsizeiwidth,GLsizeiheight)target只能填 GL_RENDERBUFFERinternalformat*分三种 color render buffer、 depth render buffer、stencil render buffer *width*像素单位,大小必须<= GL_MAX_RENDERBUFFER_SIZE *height*像素单位,大小必须<= GL_MAX_RENDERBUFFER_SIZE *internalformat值color render buffer [01]GL_RGB565, GL_RGBA4, GL_RGB5_A1,color render buffer [02]GL_RGB8_OES, GL_RGBA8_OESdepth render buffer [01]GL_DEPTH_COMPONENT16,depth render buffer [02]GL_DEPTH_COMPONENT24_OES, GL_DEPTH_COMPONENT32_OEstencil render bufferGL_STENCIL_INDEX8, GL_STENCIL_INDEX4_OES, GL_STENCIL_INDEX1_OE

2.修改背景色

typedef struct { CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; } RGBAColor; static inline RGBAColor RGBAColorMake(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { RGBAColor color = { .red = red, .green = green, .blue = blue, .alpha = alpha, }; return color; } - (void)setRenderBackgroundColor:(RGBAColor)color { glClearColor(color.red, color.green, color.blue, color.alpha); }函数描述glClearColor清空 Render Buffer 的 Color Render Buffer 为 RGBA 颜色glClearColorvoidglClearColor(GLfloatred,GLfloatgreen,GLfloatblue,GLfloatalpha);red指 [0, 1] 的红色值green指 [0, 1] 的绿色值blue指 [0, 1] 的蓝色值alpha指 [0, 1] 的透明度值注: 不想定义 RGBAColor 的话,可以直接使用 GLKit 提供的 GLKVector4 ,原型是 #if defined(__STRICT_ANSI__) struct _GLKVector4 { float v[4]; } __attribute__((aligned(16))); typedef struct _GLKVector4 GLKVector4; #else union _GLKVector4 { struct { float x, y, z, w; }; struct { float r, g, b, a; }; // 在这呢...... struct { float s, t, p, q; }; float v[4]; } __attribute__((aligned(16))); typedef union _GLKVector4 GLKVector4; // 是一个共用体 #endifGLK_INLINE GLKVector4 GLKVector4Make(float x, float y, float z, float w) { GLKVector4 v = { x, y, z, w }; return v; }

3. 初始化数据

如果要使用 VBOs 最好在这里创建 VBOs 对象并绑定顶点数据,当然直接在关联数据一步做也没问题; #define VertexBufferMemoryBlock (1) - (GLuint)createVBO { GLuint vertexBufferID; glGenBuffers(VertexBufferMemoryBlock, &vertexBufferID); return vertexBufferID; } #define PositionCoordinateCount (3) typedef struct { GLfloat position[PositionCoordinateCount]; } VFVertex; static const VFVertex vertices[] = { {{-0.5f, -0.5f, 0.0}}, // lower left corner {{ 0.5f, -0.5f, 0.0}}, // lower right corner {{-0.5f, 0.5f, 0.0}}, // upper left corner }; - (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID { glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); // 创建 资源 ( context ) glBufferData(GL_ARRAY_BUFFER, // 缓存块 类型 sizeof(vertices), // 创建的 缓存块 尺寸 vertices, // 要绑定的顶点数据 GL_STATIC_DRAW); // 缓存块 用途

}函数描述glGenBuffers申请 VBOs 对象内存glBindBuffer绑定 VBOs 对象glBufferData关联顶点数据,并创建内存glGenBuffersvoidglGenBuffers(GLsizein,GLuint* buffers)n*指返回多少个 VBO *buffers指 VBO 的标识符内存地址glBindBuffervoidglBindBuffer(GLenumtarget,GLuintbuffer)target可以使用 GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAY_BUFFERbuffer指 VBO 的标识符glBufferDatavoidglBufferData(GLenumtarget,GLsizeiptrsize,const void*data,GLenumusage)target可以使用 GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAY_BUFFERsize字节单位,数据在内存中的大小(sizeof(...))data顶点数据的内存指针usage告诉程序怎么去使用这些顶点数据usage值GL_STATIC_DRAW程序只指定一次内存对象的数据(顶点数据),而且数据会被多次(非常频繁地)用于绘制图元。GL_DYNAMIC_DRAW程序不断地指定内存对象的数据(顶点数据),而且数据会被多次(非常频繁地)用于绘制图元。GL_STREAM_DRAW程序只指定一次内存对象的数据(顶点数据),而且数据会被数次(不确定几次)用于绘制图元。glGenBuffers 、glBindBuffer、glBufferData 都干了什么?glGenBuffers会在 OpenGL ES Context ( GPU )里面,申请一块指定大小的内存区;glBindBuffer会把刚才申请的那一块内存声明为 GL_ARRAY_BUFFER ,就是以什么类型的内存来使用;glBufferData把存放在程序内存的顶点数据 ( CPU 内存 ) 关联到刚才申请的内存区中; 注: 图片截自, RW. Beginning. OpenGL ES.and.GLKit Tutorials 教程;图片中的 “~~ 3) 拷贝顶点数据~~ ” 更正为 “ 3) 关联顶点数据 ”, 因为从 CPU 拷贝数据到 GPU 是在 OpenGL ES 触发绘制方法(后面会进到)的时候才会进行;

4. 配置 OpenGL ES Shader

编写 Vertex Shader Code 文件a. 这是文件形式的,建议使用这种, Xcode 会进行关键字提示 #version 100 attribute vec4 v_Position; void main(void) { gl_Position = v_Position; }a 对应的图片b. 这是直接 GLchar * 字符串形式 + (GLchar *)vertexShaderCode { return "#version 100 n" "attribute vec4 v_Position; n" "void main(void) { n" "gl_Position = v_Position;n" "}"; }b 对应的图片非常明显地看出,a 不管编写和阅读都很轻松,而 b 就是一堆红,不知道是什么鬼,看久了眼睛会很累;代码解释:a.#version 100,首先 OpenGL ES 2 使用的 GLSL ES 版本是100, 这个没什么好解释的。《OpenGL ES 2 programming Guide》有提及 同时也说明了,我们编写 GLSL Code 的时候,要使用 《OpenGL ES Shading Language》的语言版本; b.attribute vec4 v_Position;, b-1.attribute存储类型限定符,表示链接,链接 OpenGL ES 的每一个顶点数据到顶点着色器(一个一个地); 注:attribute只能定义 float, vec2, vec3, vec4, mat2, mat3,mat4 这几种类型的变量,不能是结构体或数组;只能用在顶点着色器中,不能在片元着色器中使用,不然会编译错误;补充:其它的存储类型限定符限定符描述none(默认)表示本地的可读写的内存输入的参数const表示编译期固定的内容只读的函数参数attribute表示链接,链接 OpenGL ES 的每一个顶点数据到顶点着色器(一个一个地)uniform表示一旦正在被处理的时候就不能改变的变量,链接程序、OpenGL ES 、着色器的变量varying表示链接顶点着色器和片元着色器的内部数据b-2. [vec4],基本的数据类型,直接上图 注: 图片截自,OpenGL ES Shading Language 1.0 Quick Reference Card - Page 3 c. **gl_Position **内建变量 因为顶点数据里面 只是用到了 Position 顶点数据; 编写 Fragment Shader Code 文件a. 文件形式 #version 100 void main(void) { gl_FragColor = vec4(1, 1, 1, 1); // 填充色,白色 }b. 字符串形式 + (GLchar *)fragmentShaderCode { return "#version 100 n" "void main(void) { n" "gl_FragColor = vec4(1, 1, 1, 1); n" "}"; }配置 Vertex Shader- (GLuint)createShaderWithType:(GLenum)type { GLuint shaderID = glCreateShader(type); const GLchar * code = (type == GL_VERTEX_SHADER) ? [[self class] vertexShaderCode] : [[self class] fragmentShaderCode]; glShaderSource(shaderID, ShaderMemoryBlock, &code, NULL); return shaderID; } - (void)compileVertexShaderWithShaderID:(GLuint)shaderID type:(GLenum)type { glCompileShader(shaderID); GLint compileStatus; glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compileStatus); if (compileStatus == GL_FALSE) { GLint infoLength; glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLength); if (infoLength >0) { GLchar *infoLog = malloc(sizeof(GLchar) * infoLength); glGetShaderInfoLog(shaderID, infoLength, NULL, infoLog); NSLog(@"%s ->%s", (type == GL_VERTEX_SHADER) ? "vertex shader" : "fragment shader", infoLog); free(infoLog); } } }函数描述glCreateShader创建一个着色器对象glShaderSource关联顶点、片元着色器的代码glCompileShader编译着色器代码glGetShaderiv获取着色器对象的相关信息glGetShaderInfoLog获取着色器的打印消息glCreateShaderGLuintglCreateShader(GLenumtype)type只能是 GL_VERTEX_SHADER、GL_FRAGMENT_SHADER中的一个return GLuint返回着色器的内存标识符glShaderSourcevoidglShaderSource(GLuintshader,GLsizeicount,const GLcharconststring,const GLintlength)shader着色器的内存标识符count有多少块着色代码字符串资源string着色代码字符串首指针length着色代码字符串的长度glCompileShadervoidglCompileShader(GLuintshader)shader着色器的内存标识符glGetShaderivvoidglGetShaderiv(GLuintshader,GLenumpname,GLint*params)shader着色器的内存标识符pname指定获取信息的类型,有 GL_COMPILE_STATUS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_SHADER_SOURCE_LENGTH、GL_SHADER_TYPE 五种params用于存储当前获取信息的变量内存地址glGetShaderInfoLogvoidglGetShaderInfoLog(GLuintshader,GLsizeimaxLength,GLsei** *length,GLchar*infoLog)shader着色器的内存标识符maxLength指最大的信息长度length*获取的信息长度,如果不知道可以是 NULL *infoLog存储信息的变量的内存地址配置 Fragment Shader 与 3) 方法一样; 创建 Shader Program - (GLuint)createShaderProgram { return glCreateProgram(); }函数描述glCreateProgram创建 Shader Program 对象glCreateProgramGLuintglCreateProgram()return GLuint返回着色器程序的标识符装载 Vertex Shader 和 Fragment Shader- (void)attachShaderToProgram:(GLuint)programID vertextShader:(GLuint)vertexShaderID fragmentShader:(GLuint)fragmentShaderID { glAttachShader(programID, vertexShaderID); glAttachShader(programID, fragmentShaderID); }函数描述glAttachShader装载 Shader 对象glAttachShadervoidglAttachShader(GLuintprogram,GLuintshader)program着色器程序的标识符shader要装载的着色器对象标识符链接 Shader Program- (void)linkProgramWithProgramID:(GLuint)programID { glLinkProgram(programID); GLint linkStatus; glGetProgramiv(programID, GL_LINK_STATUS, &linkStatus); if (linkStatus == GL_FALSE) { GLint infoLength; glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLength); if (infoLength >0) { GLchar *infoLog = malloc(sizeof(GLchar) * infoLength); glGetProgramInfoLog(programID, infoLength, NULL, infoLog); NSLog(@"%s", infoLog); free(infoLog); } } }函数描述glLinkProgram链接 Shader Program 对象glGetProgramiv获取 着色器程序的相关信息glGetProgramInfoLog获取 着色器程序的打印信息glLinkProgramvoidglLinkProgram(GLuintprogram)program着色器程序的标识符glGetProgramivvoidglGetProgramiv(GLuintprogram,GLenumpname,GLint*params)program着色器程序的标识符pname可以选择的消息类型有如下几个,GL_ACTIVE_ATTRIBUTES、GL_ACTIVE_ATTRIBUTE_MAX_LENGTH、GL_ACTIVE_UNIFORMS、GL_ACTIVE_UNIFORM_MAX_LENGTH、GL_ATTACHED_SHADERS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_LINK_STATUS、GL_VALIDATE_STATUSparams存储信息的变量的内存地址glGetProgramInfoLogvoidglGetProgramInfoLog(GLuintprogram,GLsizeimaxLength,GLsizei** *length,GLchar*infoLog)program着色器程序的标识符maxLength指最大的信息长度length*获取的信息长度,如果不知道可以是 NULL *infoLog存储信息的变量的内存地址

5.渲染绘制

清空旧渲染缓存- (void)clearRenderBuffer { glClear(GL_COLOR_BUFFER_BIT); }函数描述glClear清空 渲染缓存的旧内容glClearvoid glClear (GLbitfield mask)mask三者中的一个GL_COLOR_BUFFER_BIT ( 颜色缓存 ),GL_DEPTH_BUFFER_BIT ( 深度缓存 ), GL_STENCIL_BUFFER_BIT ( 模板缓存 )设置渲染窗口- (void)setRenderViewPortWithCGRect:(CGRect)rect { glViewport(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); }函数描述glViewport设置 渲染视窗的位置和尺寸glViewportvoidglViewport(GLintx,GLinty,GLsizeiw,GLsizeih)x,y渲染窗口偏移屏幕坐标系左下角的像素个数w,h渲染窗口的宽高,其值必须要大于 0使用 Shder Program- (void)userShaderWithProgramID:(GLuint)programID { glUseProgram(programID); }函数描述glUseProgram使用 Shader ProgramglUseProgramvoidglUseProgram(GLuintprogram)program着色器程序的标识符关联数据#define VertexAttributePosition (0) #define StrideCloser (0) - (void)attachTriangleVertexArrays { glEnableVertexAttribArray(VertexAttributePosition); if (self.vertexMode == VertexDataMode_VBO) { glVertexAttribPointer(VertexAttributePosition, PositionCoordinateCount, GL_FLOAT, GL_FALSE, sizeof(VFVertex), (const GLvoid *) offsetof(VFVertex, position)); } else { glVertexAttribPointer(VertexAttributePosition, PositionCoordinateCount, GL_FLOAT, GL_FALSE, StrideCloser, vertices); } }函数描述glEnableVertexAttribArray使能顶点数组数据glVertexAttribPointer关联顶点数据a. 使能顶点缓存 glEnableVertexAttribArrayvoidglEnableVertexAttribArray(GLuintindex)indexattribute 变量的下标,范围是[ 0, GL_MAX_VERTEX_ATTRIBS - 1]b. 关联顶点数据 glVertexAttribPointervoid glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr)indexattribute 变量的下标,范围是[ 0, GL_MAX_VERTEX_ATTRIBS - 1]size*指顶点数组中,一个 attribute 元素变量的坐标分量是多少(如:position, 程序提供的就是 {x, y ,z} 点就是 3个坐标分量 ),范围是 [1, 4] *type数据的类型,只能是 GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_FLOAT、GL_FIXED、GL_HALF_FLOAT_OESnormalized*指是否进行数据类型转换的意思,GL_TRUE 或 GL_FALSE *stride*指每一个数据在内存中的偏移量,如果填 0(零) 就是每一个数据紧紧相挨着。 *ptr数据的内存首地址知识扩展:获取最大 attribute 下标的方法GLint maxVertexAttribs; // n will be >= 8 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);关于size补充注, 图片截自,《OpenGL ES 2 Programming Guide》第6章使能顶点数组数据? 其实顶点着色器中处理的数据有两种输入类型,CVOs ( Constant Vertex Objects )、VAOs ( Vertex Array Objects ); 而glEnableVertexAttribArrayglDisableVertexAttribArray函数就是使用 CVOs 还是 VAOs 的一组开关,看图 :注: 图片截自,《OpenGL ES 2 Programming Guide》第6章若使用了 CVOs 作为输入数据的,要使用以下处理函数来替代glVertexAttribPointer函数: OpenGL ES 只支持 float-pointer 类型的数据,所以才会有normalized参数;顶点着色器的数据传递图,注: 图片截自,《OpenGL ES 2 Programming Guide》第6章特别提醒,VBOs 只是一种为了加快数据访问和渲染调度的一种手段,而不是数据输入方式的一种;强烈建议您去看一下 《OpenGL ES 2 Programming Guide》的 6. Vertex Attributes, Vertex Arrays, and Buffer Objects 这一章;绘制图形#define PositionStartIndex (0) #define DrawIndicesCount (3) - (void)drawTriangle { glDrawArrays(GL_TRIANGLES, PositionStartIndex, DrawIndicesCount); }函数描述glDrawArrays绘制所有图元glDrawArraysvoidglDrawArrays(GLenummode,GLintfirst,GLsizeicount)mode绘制的图元方式,只能是 GL_POINTS、GL_LINES、GL_LINE_STRIP、GL_LINE_LOOP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN 的一种first从第几个顶点下标开始绘制count指有多少个顶点下标需要绘制渲染图形- (void)render { [self.context presentRenderbuffer:GL_RENDERBUFFER]; }函数描述presentRenderbuffer:把 Renderbuffer 的内容显示到窗口系统 ( CAEAGLLayer ) 中presentRenderbuffer:- (BOOL)presentRenderbuffer:(NSUInteger)targettarget*只能是 GL_RENDERBUFFER *return BOOL返回是否绑定成功补充:同时,这个函数也说明了kEAGLDrawablePropertyRetainedBacking 为什么要设为 YES 的原因:如果要保存 Renderbuffer 的内容就要把 CARAGLLayer 的 drawableProperties 属性的 kEAGLDrawablePropertyRetainedBacking 设置为 YES 。上面所有代码的工程文件, 在Github 上DrawTriangle_OneStep

面向对象的重新设计:

消息处理的主流程就是上面的信号流程图的步序。 面向对象,就是把所有的消息交给对象来处理咯,关注的就是消息的传递和处理。【可以按照你的喜好来设计,反正可扩展性和可维护性都比较好就行了,当然也不能把消息的传递变得很复杂咯】 OpenGL ES 2 iOS 渲染逻辑流程图_面向对象化项目文件结构: 完整代码在 Github 上DrawTriangle_OOP

第四步,练练手

建议按照自己的思路重新写一个项目

1. 修改背景色

提示:glClear 函数

2.修改三角形的填充色:

提示:CVOs,三个顶点是统一的颜色数据

3. 修改三角形的三个顶点的颜色(填充色):

提示:VAOs / VBOs ,在三个顶点的基础上添加新的颜色数据

用户评论

珠穆郎马疯@

看起来是一个很棒的入门教程!

    有11位网友表示赞同!

心已麻木i

我想学习如何使用 OpenGL ES,这对我来说是个很好的起点。

    有13位网友表示赞同!

浮殇年华

从一个简单的三角形开始确实是一个好主意,更容易理解概念。

    有16位网友表示赞同!

寻鱼水之欢

希望这个教程能让我一步步掌握OpenGL ES 2.0的用法。

    有19位网友表示赞同!

何必锁我心

iOS 游戏开发一直是我想尝试的方向,这篇文章很有帮助!

    有19位网友表示赞同!

冷青裳

学习3D渲染技术,从基础开始的确是明智之选。

    有9位网友表示赞同!

心贝

OpenGL ES 是一个强大的图形API,期待学习它的使用技巧。

    有15位网友表示赞同!

一尾流莺

我从来没有接触过OpenGL ES,这个教程看起来很全面。

    有8位网友表示赞同!

素颜倾城

开发者入门指南总是很受欢迎,谢谢分享!

    有16位网友表示赞同!

限量版女汉子

三角形是计算机图形学的基础,这篇文章将是一个很好的引子。

    有9位网友表示赞同!

最怕挣扎

对iOS游戏开发感兴趣的人,可以看看这篇教程!

    有18位网友表示赞同!

颜洛殇

从零开始学习OpenGL ES,看起来很有趣!

    有6位网友表示赞同!

又落空

希望教程能介绍一些实用的案例应用,加深理解。

    有14位网友表示赞同!

淡抹丶悲伤

学习新的技术总是充满挑战和乐趣。

    有16位网友表示赞同!

花花世界总是那么虚伪﹌

这个标题的“一步从一个小三角开始”很吸引人!

    有12位网友表示赞同!

你瞒我瞒

很期待学习OpenGL ES 2.0的知识和技巧!

    有14位网友表示赞同!

发型不乱一切好办

教程用到的代码是不是也能够分享?这样更容易理解。

    有14位网友表示赞同!

日久见人心

学习3D图形渲染技术需要一定的耐心,不过相信这个教程能帮助我入门。

    有18位网友表示赞同!

青衫故人

想学习如何开发自己的iOS应用程序,这篇文章很有参考价值!

    有20位网友表示赞同!

自繩自縛

OpenGL ES的应用范围很广,希望可以从这个教程开始了解它!

    有7位网友表示赞同!

【深入OpenGL ES 2.0(iOS):从小三角起步教程[第一期]】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:即将迈向毕业新篇章?揭秘你的未来之路 下一篇:原创剧本推荐:《男人四十再婚记》第二部精彩呈现