OpenGL ES 渲染立体图像(GLSL)

几何处理阶段

几何阶段主要负责大部分多边形操作和顶点操作,包括顶点着色、坐标变换、生成图元、投影、裁剪、屏幕映射等过程,其中顶点着色、坐标变换由顶点着色器完成

  1. 顶点着色器
  2. 生成图元
  3. 投影
  4. 裁切
  5. 屏幕映射

顶点着色器

完成顶点着色、坐标变换

生成图元

索引数组:
{1,2,3}
{3,2,4}
{4,2,7}
{7,2,5}等等…

投影

裁剪

仅保留视景体内部的图元进行渲染

屏幕映射

这个阶段是不可配置也不可编程的。主要用来实现图元的坐标转换到屏幕坐标

三维坐标转变为二维屏幕坐标的过程

  • MC : 建模坐标系
  • WC : 世界坐标系
  • VC : 观察坐标系
  • PC : 投影坐标系
  • NPC : 规格化投影坐标系
  • DC : 设备坐标系

坐标系详细概念

世界坐标系 : 坐标系统主要⽤用于计算机图形场景中的所有图形对象的空间定位和定义
局部坐标系 : 独立于世界坐标系来定义物体几何特性
观察坐标系 : 观察坐标系通常是以视点的位置为原点,通过用户指定的一个向上的观察向量来定义整个坐标系统,观察坐标系主要⽤于从观察者的角度对整个世界坐标系内的对象进行重新定位和描述,从而简化几何物体在投影面的成像的数学推导和计算
投影坐标系 : 物体从世界坐标描述转换到观察坐标后,可将三维物体投影到二维表⾯上,即投影到虚拟摄像机的胶片上,这个过程就是投影变换。以胶片中心为参考原点的空间坐标系称为投影坐标系,物体在投影坐标系中的坐标称为投影坐标。
设备坐标系 : 是图形设备上采用的与具体设备相关的坐标系。设备坐标系⼀般采⽤整数坐标,其坐标范围由具体设备的分辨率决定。设备坐标系上的一个点一般对应图形设备上的一个像素。由于具体设备的限制,设备坐标系的坐标范围一般是有限的
规格化投影坐标系 : 是为了避免设备相关性⽽定义的⼀种虚拟的设备坐标系。规格化坐标系的坐标范围一般从0到1,也有的是从-1到+1。采⽤规格化设备坐标系的好处是屏蔽了具体设备的分辨率,使得图形处理能够尽量避开对具体设备坐标的考虑。实际图形处理时,先将世界坐标转换成对应的规格化设备坐标,然后再将规格化设备坐标映射到具体的设备坐标上去
屏幕坐标系统 : 也称设备坐标系统,它主要用于某一特殊的计算机图形显示设备(如光栅显示器)的表面的点的定义,在多数情况下,对于每⼀个具体的显示设备,都有一个单独的坐标系统,在定义了成像窗口的情况下,可进一步在屏幕坐标系统中定义称为视图区(view port)的有界区域,视图区中的成像即为实际所观察到的。

平移变换

缩放变换

索引绘图

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
-(void)render
{
//清屏颜色
glClearColor(0, 0.0, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);

CGFloat scale = [[UIScreen mainScreen] scale];
//设置视口
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);

//获取顶点着色程序、片元着色器程序文件位置
NSString* vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"glsl"];
NSString* fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"glsl"];

//判断self.myProgram是否存在,存在则清空其文件
if (self.myProgram) {

glDeleteProgram(self.myProgram);
self.myProgram = 0;
}

//加载程序到myProgram中来。
self.myProgram = [self loadShader:vertFile frag:fragFile];

//4.链接
glLinkProgram(self.myProgram);
GLint linkSuccess;

//获取链接状态
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(self.myProgram, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"error%@", messageString);

return ;
}else {
glUseProgram(self.myProgram);
}

//创建绘制索引数组
GLuint indices[] =
{
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
};

//判断顶点缓存区是否为空,如果为空则申请一个缓存区标识符
if (self.myVertices == 0) {
glGenBuffers(1, &_myVertices);
}

//顶点数组
//前3顶点值(x,y,z),后3位颜色值(RGB)
GLfloat attrArr[] =
{
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, //左上
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, //右上
-0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, //左下
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, //右下
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, //顶点
};

//-----处理顶点数据-------
//将_myVertices绑定到GL_ARRAY_BUFFER标识符上
glBindBuffer(GL_ARRAY_BUFFER, _myVertices);
//把顶点数据从CPU内存复制到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

//将顶点数据通过myPrograme中的传递到顶点着色程序的position
//1.glGetAttribLocation,用来获取vertex attribute的入口的.2.告诉OpenGL ES,通过glEnableVertexAttribArray,3.最后数据是通过glVertexAttribPointer传递过去的。
//注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致
GLuint position = glGetAttribLocation(self.myProgram, "position");

//3.设置读取方式
//参数1:index,顶点数据的索引
//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
//参数5:stride,连续顶点属性之间的偏移量,默认为0;
//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, NULL);

//2.设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(position);

//--------处理顶点颜色值-------
////1.glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.glsl中的输入变量:positionColor保持一致
GLuint positionColor = glGetAttribLocation(self.myProgram, "positionColor");

//3.设置读取方式
//参数1:index,顶点数据的索引
//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
//参数5:stride,连续顶点属性之间的偏移量,默认为0;
//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (float *)NULL + 3);

//2.设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(positionColor);

//注意,想要获取shader里面的变量,这里记得要在glLinkProgram后面,后面,后面!
/*
一个一致变量在一个图元的绘制过程中是不会改变的,所以其值不能在glBegin/glEnd中设置。一致变量适合描述在一个图元中、一帧中甚至一个场景中都不变的值。一致变量在顶点shader和片断shader中都是只读的。首先你需要获得变量在内存中的位置,这个信息只有在连接程序之后才可获得
*/
//找到myProgram中的projectionMatrix、modelViewMatrix 2个矩阵的地址。如果找到则返回地址,否则返回-1,表示没有找到2个对象。
GLuint projectionMatrixSlot = glGetUniformLocation(self.myProgram, "projectionMatrix");
GLuint modelViewMatrixSlot = glGetUniformLocation(self.myProgram, "modelViewMatrix");

float width = self.frame.size.width;
float height = self.frame.size.height;

//创建4 * 4矩阵
KSMatrix4 _projectionMatrix;

//获取单元矩阵
ksMatrixLoadIdentity(&_projectionMatrix);

//计算纵横比例 = 长/宽
float aspect = width / height; //长宽比

//获取透视矩阵
/*
参数1:矩阵
参数2:视角,度数为单位
参数3:纵横比
参数4:近平面距离
参数5:远平面距离
参考PPT
*/
ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 20.0f); //透视变换,视角30°

//设置glsl里面的投影矩阵
/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
参数列表:
location:指要更改的uniform变量的位置
count:更改矩阵的个数
transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
value:执行count个元素的指针,用来更新指定uniform变量
*/
glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);

//开启剔除操作效果
glEnable(GL_CULL_FACE);

//创建一个4 * 4 矩阵,模型视图
KSMatrix4 _modelViewMatrix;
//获取单元矩阵
ksMatrixLoadIdentity(&_modelViewMatrix);
//平移,z轴平移-10
ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);

//创建一个4 * 4 矩阵,旋转矩阵
KSMatrix4 _rotationMatrix;
//初始化为单元矩阵
ksMatrixLoadIdentity(&_rotationMatrix);

//旋转
ksRotate(&_rotationMatrix, xDegree, 1.0, 0.0, 0.0); //绕X轴
ksRotate(&_rotationMatrix, yDegree, 0.0, 1.0, 0.0); //绕Y轴
ksRotate(&_rotationMatrix, zDegree, 0.0, 0.0, 1.0);//绕Z轴

//把变换矩阵相乘,注意先后顺序 ,将平移矩阵与旋转矩阵相乘,结合到模型视图
ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);

// ksMatrixMultiply(&_modelViewMatrix, &_modelViewMatrix, &_rotationMatrix);

// 加载模型视图矩阵 modelViewMatrixSlot
//设置glsl里面的投影矩阵
/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
参数列表:
location:指要更改的uniform变量的位置
count:更改矩阵的个数
transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
value:执行count个元素的指针,用来更新指定uniform变量
*/
glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);

//使用索引绘图
/*
void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid * indices);
参数列表:
mode:要呈现的画图的模型
GL_POINTS
GL_LINES
GL_LINE_LOOP
GL_LINE_STRIP
GL_TRIANGLES
GL_TRIANGLE_STRIP
GL_TRIANGLE_FAN
count:绘图个数
type:类型
GL_BYTE
GL_UNSIGNED_BYTE
GL_SHORT
GL_UNSIGNED_SHORT
GL_INT
GL_UNSIGNED_INT
indices:绘制索引数组

*/
glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);

//要求本地窗口系统显示OpenGL ES渲染<目标>
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];

}

其中关于矩阵变换的操作(旋转/平移/缩放), 需要依赖一个GLES的数学类GLESMath.c导入进工程; 当然也可以用苹果封装好的GLKit中的对应方法代替(下篇文章会讲解)

实现围绕X/Y/Z轴旋转三角体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

{
float xDegree;
float yDegree;
float zDegree;
BOOL bX;
BOOL bY;
BOOL bZ;
NSTimer* myTimer;
}
#pragma mark- XYZClick
- (IBAction)XClick:(id)sender {
//开启定时器
if (!myTimer) {
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
}
//更新的是X还是Y
bX = !bX;
}
- (IBAction)YClick:(id)sender {
//开启定时器
if (!myTimer) {
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
}
//更新的是X还是Y
bY = !bY;
}
- (IBAction)ZClick:(id)sender {
//开启定时器
if (!myTimer) {
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
}
//更新的是X还是Y
bZ = !bZ;
}

-(void)reDegree
{
//如果停止X轴旋转,X = 0则度数就停留在暂停前的度数.
//更新度数
xDegree += bX * 5;
yDegree += bY * 5;
zDegree += bZ * 5;
//重新渲染
[self render];


}

效果图

11

谢谢你赏我糖果吃