a.目标
b.效果
c.分析
a.OpenGL ES 2 的渲染管线
b.简述绘制流程的每一个单元【至左向右】
1) OpenGL ES 2.0 API
2) Vertex Arrays / Buffer Objects
3) Vertex Shader
4) Primitive Assembly
5) Rasterization
6) Texture Memory
7) Fragment Shader
8) Per-Fragment Operations
9) Render Buffer & Frame Buffer
10) EAGL API
c. OpenGL ES Shader Language 简述
*) 简单流程图
a.OpenGL ES 2 的渲染流程 细化
1) 配置环境
2) 初始化数据
3) 配置 OpenGL ES Shader
4) 渲染绘制
b.流程代码化
一、配置渲染环境
1) 配置渲染窗口 [ 继承自 UIView ]
2) 配置渲染上下文
3) 配置帧渲染
4) 配置渲染缓存
5) 帧缓存装载渲染缓存的内容
6) 渲染上下文绑定渲染窗口(图层)
二、修改背景色
三、 初始化数据
四、 配置 OpenGL ES Shader
1) 编写 Vertex Shader Code 文件
2) 编写 Fragment Shader Code 文件
3) 配置 Vertex Shader
4) 配置 Fragment Shader
5) 创建 Shader Program
6) 装载 Vertex Shader 和 Fragment Shader
7) 链接 Shader Program
五、渲染绘制
1) 清空旧渲染缓存
2) 设置渲染窗口
3) 使用 Shder Program
4) **关联数据**
5) 绘制图形
c.面向对象的重新设计
a.修改背景色
b.修改三角形的填充色
c.修改三角形的三个顶点的颜色(填充色)
使用 OpenGL ES 2.0 在 iOS 模拟器中绘制一个三角形。
--> 三个端点 + 三条线 + 中间的填充色,即三个点连成线形成一个三角面。
1). 三个什么端点(屏幕坐标点)? 要回答这个问题要先了解 OpenGL ES 的坐标系在屏幕上是怎样分布的:
OpenGL ES 的坐标系{x, y, z}
注:图片截自 《Learning OpenGL ES For iOS》一书
a. 通过图片的三维坐标系可以知道: - 它是一个三维坐标系 {x, y, z} - 三维坐标中心在正方体的几何中心 {0, 0, 0} - 整个坐标系是 [0, 1] 的点,也就是说 OpenGL 中只支持 0 ~ 1 的点
注意,这里所讲的 0 和 1 ,最好理解成 0 --> 无限小, 1 --> 无限大 ,它并不是指 0 个单位的长度,或 1 个单位的长度。
b. 再来看看我们绘制的三角形,在 iOS 模拟器 或真机上 的坐标是怎样构成的:
三维坐标+坐标值 演示图
注:图片通过 CINEMA4D (c4d)三维软件绘制
二维就是长这样的了:
二维坐标( z = 0 )
a.连接三个端点形成封闭的三角面,那么 OpenGL ES 能不能直接绘制三角形 ? --> 答案是能。
b.那么 OpenGL 能直接画正方形么? --> 答案是不能。
c.那OpenGL 能直接绘制什么? --> 答案是:点精灵、线、三角形,它们统称为 图元(Primitive)。
注:答案来自于《OpenGL ES 2.0 Programming Guide》 7. Primitive Assembly and Rasterization 一章,截图如下:
Line Strip , 指首尾相接的线段,第一条线和最后一条线没有连接在一起; Line Loops, 指首尾相接的线段,第一条线和最后一条线连接在一起,即闭合的曲线;
Line
Triangle Strip, 指条带,相互连接的三角形 Triangle Fan, 指扇面,相互连接的三角形
Triangle
扇面
3)填充色?
就是指 RGBA 的颜色值;( _ 感觉好废但还是要说)
怎么去画,就是通过多少个步骤完成一个完整的绘制渲染流程,当然这里指 OpenGL ES 2 的渲染管线流程)
图形管线(Graphics Pipeline)
因为这里是 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】
OpenGL ES 2.0 API :
iOS 环境下
gltypes.h
是包含了 OpenGL ES 2.0 的基本数据类型的定义;
glext.h
是包含各种宏定义,以及矩阵运算等常用的函数;
gl.h
是 OpenGL ES 2.0 所有的核心函数(命令);
左边选择要查询的函数即可 离线的函数 Card
红框处单击打开
红箭头处选择保存即可
本人推荐使用离线的卡,不受网络影响,而且一目了然。配合官方的编程指南使用就最佳了。
【官方解释:Vertex data may be sourced from arrays that are stored in application memory (via a pointer) or faster GPU memory (in a buffer object).(意指:顶点数组保存在程序内存或快速GPU内存中,前者通过数组指针访问数据,后者直接通过 Buffer Objects 访问。【就是指 VAOs 或 VBOs 方式访问】)】
绘制的三角形的数组(三个顶(端)点坐标)如下图:
顶点数组
VFVertex
这是 C 语言的知识,应该不难理解。
【官方解释:Buffer objects hold vertex array data or indices in high-performance server memory. (意指:VBOs 是持有保存在GPU快速内存区的顶点数据或顶点数据下标的缓存对象。)】
a. 为什么是 server ? --> 答,OpenGL 是基于 CS 模式的设计而成,客户端操作就相当于我们写的 OpenGL API ( OpenGL commands ) 的各种操作,服务器就是图形处理相关的硬件。( ES 当然也是这意思咯。)
【官方解释:OpenGL is implemented as a client-server system, with the application you write being considered the client, and the OpenGL implementation provided by the manufacturer of your computer graphics hardware being the server.】
注: **1) **a.b. 里面的【官方解释...】在 OpenGL ES 2.0 Reference Card 可以找到。 **2) **b.1 的【官方解释...】在《OpenGL Programming Guide》第八版 Introduction OpenGL 一章的第一小节 What Is OpenGL 中的解释。
处理顶点相关的数据,包括顶点在屏幕的位置(矩阵变换),顶点处的光照计算,纹理坐标等。
顶点着色器的信号图:
注:图片截自:《OpenGL ES 2.0 Programming Guide》 1. Introduction to OpenGL ES 2.0 -- OpenGL ES 2.0 -- Vertex Shader 一节中
a. Attributes : 属性的意思,指每一个顶点数据;
b. __Uniforms : __
b-1. 统一的意思 , 是一个只读全局常量,存储在程序的常量区; b-2. 当 Vertex Shader 和 Fragment Shader 定义了同名同类型的 Uniform 常量时,此时的 Uniform 常量就变成了全局常量(指向同一块内存区的常量);
c. __Samplers (可选的) : __ 是一个特殊的 Uniforms 保存的是 Texteures(纹理) 数据;
__Varying : __ a. 它是 Vertex Shader 与 Fragment Shader 的接口,是为了解决功能性问题(两个 Shader 的信息交互);
b. 储存 Vertex Shader 的输出信息;
c. Vertex Shader 与 Fragment Shader 中必须要有必须要同名同类型的Varying 变量,不然会编译错误;(因为它是两个 Shader 的信息接口啊,不一样还接什么口啊。)
Temporary Variables : a. 指临时变量; b. 储存 Shader 处理过程中的中间值用的; c. 声明在 Funtions(函数) 或 Variable(变量) 内部;
a. gl_Position ** ( highp vec4 变量 ) : 就是 Vertex Position,Vertex Shader 的输出值,而且是必须要赋值的变量;只有在 Vertex Shader 中使用才会有效**;
注:highp vec4, highp ( high precision ) 高精度的意思,是精度限定符;vec4 ( Floating Point Vector ) 浮点向量 , OpenGL ES 的数据类型。
b. __ gl_PointSize ( mediump float 变量 ) :__ 告诉 Vertex Shader 栅格化点的尺寸(pixels,像素化),想要改变绘制点的大小就是要用这个变量 只有在 Vertex Shader 中使用才会有效;
注:mediump , mediump ( medium precision ) 中等精度的意思,是精度限定符;还有最后一个精度限制符是 lowp ( low precision ),低精度的意思。
c. __ gl_FrontFacing ( bool 变量 ) : __ 改变渲染物体的 Front Facing 和 Back Facing , 是用于处理物体光照问题的变量,双面光照(3D 物体里外光照)问题的时候才会使用的变量,只能在 Vertex Shader 中进行设置, Fragment Shader 是只读的;
注: 视锥体,实际上是一个三维锥体包含的空间区域,由摄影机和物体的捕捉关系形成;
视锥体 图片来源 《透视投影详解》一文
光栅化的信号图:
作用是,将基本图元(点、线、三角形)转换成二维的片元(Fragment, 包含二维坐标、颜色值、纹理坐标等等属性), 像素化基本图元使其可以在屏幕上进行绘制(显示)。
Texture 就是指保存了图片(位图)的所有颜色的缓存;Texture Memory 就是图片的颜色(像素)内存;每一个嵌入式系统对 Texture Memory 的大小都是有限制的;
片元着色器信号图:
a. __ gl_FragCoord ( mediump vec4 只读变量 ) :__ 是保存窗口相对坐标的 { x, y, z, 1/w } 的变量,z 表示深度 (will be used for the fragment's depth), w 表示旋转;
b. __ gl_PointCoord ( mediump int 只读变量 ) : __ 是包含了当前片元原始点位置的二维坐标;点的范围是 [ 0, 1 ] ;
c. gl_FrontFacing : 请查看 Vertex Shader 处的解释;
a. **gl_FragColor ( mediump vec4 ) ** : 片元的颜色值;
b. __gl_FragData ( mediump vec4 ) : __ 是一个数组,片元颜色集;
注:两个输出信号只能同时存在一个,就是 写了 gl_FragColor 就不要写 gl_FragData , 反之亦然;【If a shader statically assigns a value to gl_FragColor, it may not assign a value to any element of gl_FragData. If a shader statically writes a value to any element of gl_FragData, it may not assign a value to gl_FragColor. That is, a shader may assign values to either gl_FragColor or gl_FragData, but not both.】
补充知识 ( For Shader )
信号图:
关系图:
a. 简称 RBO , Render Buffer Object; b. 是由程序(Application)分配的 2D 图片缓存; c. Render Buffer 可以分配和存储颜色(color)、深度(depth)、模版(stectil)值,也可以把这三种值装载到 Frame Buffer 里面;
a. 简称 FBO , Frame Buffer Object; b. 是颜色、深度、模板缓存装载在 FBO 上所有装载点的合集; c. 描述颜色、深度、模板的大小和类型的属性状态; d. 描述 Texture 名称的属性状态; e. 描述装载在 FBO 上的 Render Buffer Objects ( 渲染缓存对象 ) 的属性状态;
扩充知识(FBO):
FBO API 支持的操作如下:
官方的是 EGL API 与平台无关,因为它本身是可以进行平台定制的,所以 iOS 下就被 Apple 定制成了 EAGL API 。
EAGL.h
: 里面的核心类是 EAGLContext , 上下文环境;
EAGLDrawable.h
: 用于渲染绘制输出的 EAGLContext 分类;
注:除了上面的两个外,还有一个类 CAEAGLLayer ,它就是 iOS 端的渲染窗口寄宿层;
【 看这里:
a. 可以进行显示的设备( iOS 下当然是手机或模拟器 ) b. 创建渲染面(rendering surface), 设备的屏幕 ( on-screen ) 或 像素缓存 ( pixel Buffer ) ( off-screen )
注: pixel Buffer , 这种 buffer 是不能直接显示的,只能成为渲染面或通过其它 API 分享出去,如: pbuffers 经常被用于 Texture 的 maps , 因为 Texture 本身也是像素嘛;
注:
__OpenGL ES Context : __ 保存了渲染过程中的所有数据和状态信息; 图示解释:
图片截自, RW. Beginning. OpenGL ES.and.GLKit Tutorials 教程
流程图中出现的 Vertex Shader 与 Fragment Shader 都是要使用 GLSL ES 语言来进行编程操作的
OpenGL ES 2.0 对应的 GLSL ES 版本是 1.0,版本编号是 100;
iOS 环境下 GLKit 提供了一个简单的 Shader 类——GLKBaseEffect 类;
GLKit APIs
简单流程图:
OpenGL ES 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 等);
注:此处只是做了一个 Program 的图,不是只能有一个 Program,而是可以有多个,需要使用多少个,由具体项目决定。
以本文的小三角为例,开始浪吧~~~!
e981fd1c1e0c35f7e91735fb473b2bec.gif
OpenGL ES 2 iOS 渲染逻辑流程图.png
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 也有;
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 属性来改变渲染属性。
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.)
这里主要是考虑是否使用 VBOs ,由于移动端对效率有所要求,所以一般采用 VBOs 快速缓存;
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 | 使用 帧缓存对象 |
glGenFramebuffers | |
---|---|
void glGenFramebuffers (GLsizei n, GLuint * framebuffers) | |
n 指返回多少个 Frame Buffer 对象 | |
framebuffers 指 Frame Buffer 对象的标识符的内存地址 |
glBindFramebuffer | |
---|---|
void glBindFramebuffer (GLenum target, GLuint framebuffer) | |
target 只能填 GL_FRAMEBUFFER | |
framebuffer 指 Frame Buffer 对象的标识符 |
- (GLuint)createRenderBuffer {
GLuint ID;
glGenRenderbuffers(RenderMemoryBlock, &ID);
glBindRenderbuffer(GL_RENDERBUFFER, ID);
return ID;
}
函数 | 描述 |
---|---|
glGenRenderbuffers | 创建 渲染缓存对象 |
glBindRenderbuffer | 使用 渲染缓存对象 |
glGenRenderbuffers | |
---|---|
void glGenRenderbuffers(GLsizei n, GLuint *renderbuffers) | |
n 指返回多少个 Render Buffer 对象 | |
renderbuffers 指 Render Buffer 对象的标识符的内存地址 |
glBindRenderbuffer | |
---|---|
void glBindRenderbuffer(GLenum target, GLuint renderbuffer) | |
target 只能填 GL_RENDERBUFFER | |
renderbuffers 指 Render Buffer 对象的标识符 |
- (void)attachRenderBufferToFrameBufferWithRenderID:(GLuint)renderBufferID {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferID);
}
函数 | 描述 |
---|---|
glFramebufferRenderbuffer | 装载 渲染缓存的内容到帧缓存对象中 |
glFramebufferRenderbuffer | |
---|---|
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) | |
target 只能填 GL_FRAMEBUFFER | |
attachment *只能是三个中的一个:GL_COLOR_ATTACHMENT0 ( 颜色缓存 )、GL_DEPTH_ATTACHMENT ( 深度缓存 )、GL_STENCIL_ATTACHMENT ( 模板缓存 ) * | |
renderbuffertarget 只能填 GL_RENDERBUFFER | |
renderbuffer 指 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<EAGLDrawable>)drawable | |
target 只能填 GL_RENDERBUFFER | |
drawable *只能是 CAEAGLLayer 对象 * |
函数解释:
函数 | 描述 |
---|---|
glRenderbufferStorage | 保存渲染缓存内容 |
glRenderbufferStorage | |
---|---|
void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) | |
target 只能填 GL_RENDERBUFFER | |
internalformat *分三种 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_OES |
depth render buffer [01] | GL_DEPTH_COMPONENT16, |
depth render buffer [02] | GL_DEPTH_COMPONENT24_OES, GL_DEPTH_COMPONENT32_OE |
stencil render buffer | GL_STENCIL_INDEX8, GL_STENCIL_INDEX4_OES, GL_STENCIL_INDEX1_OE |
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 颜色 |
glClearColor | |
---|---|
void glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); | |
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; // 是一个共用体
#endif
GLK_INLINE GLKVector4 GLKVector4Make(float x, float y, float z, float w)
{
GLKVector4 v = { x, y, z, w };
return v;
}
如果要使用 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 | 关联顶点数据,并创建内存 |
glGenBuffers | |
---|---|
void glGenBuffers (GLsizei n, GLuint * buffers) | |
n *指返回多少个 VBO * | |
buffers 指 VBO 的标识符内存地址 |
glBindBuffer | |
---|---|
void glBindBuffer (GLenum target, GLuint buffer) | |
target 可以使用 GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAY_BUFFER | |
buffer 指 VBO 的标识符 |
glBufferData | |
---|---|
void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage) | |
target 可以使用 GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAY_BUFFER | |
size 字节单位,数据在内存中的大小(sizeof(...)) | |
data 顶点数据的内存指针 | |
usage 告诉程序怎么去使用这些顶点数据 |
usage | 值 |
---|---|
GL_STATIC_DRAW | 程序只指定一次内存对象的数据(顶点数据),而且数据会被多次(非常频繁地)用于绘制图元。 |
GL_DYNAMIC_DRAW | 程序不断地指定内存对象的数据(顶点数据),而且数据会被多次(非常频繁地)用于绘制图元。 |
GL_STREAM_DRAW | 程序只指定一次内存对象的数据(顶点数据),而且数据会被数次(不确定几次)用于绘制图元。 |
glGenBuffers 、glBindBuffer、glBufferData 都干了什么?
注: 图片截自, RW. Beginning. OpenGL ES.and.GLKit Tutorials 教程;图片中的 “~~ 3) 拷贝顶点数据~~ ” 更正为 “ 3) 关联顶点数据 ”, 因为从 CPU 拷贝数据到 GPU 是在 OpenGL ES 触发绘制方法(后面会进到)的时候才会进行;
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 的每一个顶点数据到顶点着色器(一个一个地);
注:
补充:其它的存储类型限定符
限定符 | 描述 |
---|---|
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 顶点数据;
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"
"}";
}
- (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 | 获取着色器的打印消息 |
glCreateShader | |
---|---|
GLuint glCreateShader (GLenum type) | |
type 只能是 GL_VERTEX_SHADER、GL_FRAGMENT_SHADER中的一个 | |
return GLuint 返回着色器的内存标识符 |
glShaderSource | |
---|---|
void glShaderSource (GLuint shader, GLsizei count, const GLchar __ __ const* string, const GLint length) | |
shader 着色器的内存标识符 | |
count 有多少块着色代码字符串资源 | |
string 着色代码字符串首指针 | |
length 着色代码字符串的长度 |
glCompileShader | |
---|---|
void glCompileShader(GLuint shader) | |
shader 着色器的内存标识符 |
glGetShaderiv | |
---|---|
void glGetShaderiv(GLuint shader, GLenum pname, GLint *params) | |
shader 着色器的内存标识符 | |
pname 指定获取信息的类型,有 GL_COMPILE_STATUS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_SHADER_SOURCE_LENGTH、GL_SHADER_TYPE 五种 | |
params 用于存储当前获取信息的变量内存地址 |
glGetShaderInfoLog | |
---|---|
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsei** *length, GLchar *infoLog) | |
shader 着色器的内存标识符 | |
maxLength 指最大的信息长度 | |
length *获取的信息长度,如果不知道可以是 NULL * | |
infoLog 存储信息的变量的内存地址 |
- (GLuint)createShaderProgram {
return glCreateProgram();
}
函数 | 描述 |
---|---|
glCreateProgram | 创建 Shader Program 对象 |
glCreateProgram | |
---|---|
GLuint glCreateProgram() | |
return GLuint 返回着色器程序的标识符 |
- (void)attachShaderToProgram:(GLuint)programID vertextShader:(GLuint)vertexShaderID fragmentShader:(GLuint)fragmentShaderID {
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
}
函数 | 描述 |
---|---|
glAttachShader | 装载 Shader 对象 |
glAttachShader | |
---|---|
void glAttachShader(GLuint program, GLuint shader) | |
program 着色器程序的标识符 | |
shader 要装载的着色器对象标识符 |
- (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 | 获取 着色器程序的打印信息 |
glLinkProgram | |
---|---|
void glLinkProgram(GLuint program) | |
program 着色器程序的标识符 |
glGetProgramiv | |
---|---|
void glGetProgramiv(GLuint program, GLenum pname,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_STATUS | |
params 存储信息的变量的内存地址 |
glGetProgramInfoLog | |
---|---|
void glGetProgramInfoLog(GLuint program,GLsizei maxLength, GLsizei** *length, GLchar *infoLog) | |
program 着色器程序的标识符 | |
maxLength 指最大的信息长度 | |
length *获取的信息长度,如果不知道可以是 NULL * | |
infoLog 存储信息的变量的内存地址 |
- (void)clearRenderBuffer {
glClear(GL_COLOR_BUFFER_BIT);
}
函数 | 描述 |
---|---|
glClear | 清空 渲染缓存的旧内容 |
glClear | |
---|---|
void 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 | 设置 渲染视窗的位置和尺寸 |
glViewport | |
---|---|
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h) | |
x,y 渲染窗口偏移屏幕坐标系左下角的像素个数 | |
w,h 渲染窗口的宽高,其值必须要大于 0 |
- (void)userShaderWithProgramID:(GLuint)programID {
glUseProgram(programID);
}
函数 | 描述 |
---|---|
glUseProgram | 使用 Shader Program |
glUseProgram | |
---|---|
void glUseProgram(GLuint program) | |
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. 使能顶点缓存
glEnableVertexAttribArray | |
---|---|
void glEnableVertexAttribArray(GLuint index) | |
index attribute 变量的下标,范围是[ 0, GL_MAX_VERTEX_ATTRIBS - 1] |
b. 关联顶点数据
glVertexAttribPointer | |
---|---|
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr) | |
index attribute 变量的下标,范围是[ 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_OES * | |
normalized *指是否进行数据类型转换的意思,GL_TRUE 或 GL_FALSE * | |
stride *指每一个数据在内存中的偏移量,如果填 0(零) 就是每一个数据紧紧相挨着。 * | |
ptr 数据的内存首地址 |
知识扩展:
GLint maxVertexAttribs; // n will be >= 8
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
注, 图片截自,《OpenGL ES 2 Programming Guide》第6章
注: 图片截自,《OpenGL ES 2 Programming Guide》第6章
若使用了 CVOs 作为输入数据的,要使用以下处理函数来替代 glVertexAttribPointer 函数:
注: 图片截自,《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 | 绘制所有图元 |
glDrawArrays | |
---|---|
void glDrawArrays(GLenum mode, GLint first, GLsizei count) | |
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)target | |
target *只能是 GL_RENDERBUFFER * | |
return BOOL 返回是否绑定成功 |
补充:同时,这个函数也说明了kEAGLDrawablePropertyRetainedBacking 为什么要设为 YES 的原因:
如果要保存 Renderbuffer 的内容就要把 CARAGLLayer 的 drawableProperties 属性的 kEAGLDrawablePropertyRetainedBacking 设置为 YES 。
上面所有代码的工程文件, 在Github 上 DrawTriangle_OneStep
消息处理的主流程就是上面的信号流程图的步序。 面向对象,就是把所有的消息交给对象来处理咯,关注的就是消息的传递和处理。【可以按照你的喜好来设计,反正可扩展性和可维护性都比较好就行了,当然也不能把消息的传递变得很复杂咯】
OpenGL ES 2 iOS 渲染逻辑流程图_面向对象化
项目文件结构:
完整代码在 Github 上 DrawTriangle_OOP
建议按照自己的思路重新写一个项目
提示:glClear 函数
提示:CVOs,三个顶点是统一的颜色数据
提示:VAOs / VBOs ,在三个顶点的基础上添加新的颜色数据
它们三个主要是为了 [ 学 + 习 ] 如何关联数据,对应的项目是:Github: DrawTriangle_OOP_Challenges_1
如果你发现文章有错误的地方,请在评论区指出,不胜感激!!!