音视频开发:OpenGL + OpenGL ES + Metal 系列文章汇总
通过GLSL实现金字塔旋转案例,了解索引数组的使用,以及GLSL实现图形变换
主要内容:
索引数组的了解
旋转金字塔案例
1、索引数组的了解
索引绘图技巧就是指将图形中的肉眼可见的顶点,通过索引的方式表示顶点之间的连接,将重复顶点复用进行图形绘制的一种技巧
比如说一个金字塔,共有5个面,我们会将其分成6个三角形(底部有两个三角形)
这样我们会创建6个三角形,需要存储18个顶点。但是金字塔本身其实只有5个顶点,每个顶点都会重复使用。这样就造成了顶点在内存/显存中重复存储。
通过索引数组就可以实现顶点的复用。
图示:
可以写一个数组存储其三角形的连接方式
比如:
GLuint indices[] =
{
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
};
2、金字塔案例
案例地址: 三角形变换案例
效果:
2.1 简单介绍
实现功能:
- 绘制金字塔
- 金字塔设置颜色
- 金字塔旋转
分析如何实现:
- 绘制金字塔 -- 通过索引数组组织顶点以构建金字塔
- 设置颜色 -- 需要传入色值到片元着色器中
- 金字塔旋转 -- 传入视图模型矩阵和投影矩阵到顶点着色器,在着色器中进行计算。
主要学习:
- 索引数组的使用
- 矩阵的使用
实现思路:
具体的GLSL的渲染流程在八、OpenGL ES - GLSL的使用已经有足够详细的讲解
因此本案例只讲述新的功能实现,重复的内容不再说明。
- 初始化
- 创建图层
- 创建上下文环境
- 清空缓存区
- 设置RenderBuffer、FrameBuffer
- 着色器的使用
- 着色器程序的编写
- 着色器的编译
- 链接到程序对象中
- 程序对象的连接
- 程序对象的开启
- 顶点/纹理坐标数据的传递
- 获取数据
- 在显存中创建缓存区并拷贝数据
- 获取传递数据的通道的ID
- 开启通道ID
- 设置数据传递的方式(也就是获取缓存区中的哪部分数据来进行传递)
- 色值的传递(新增)
- 设置色值
- 传递色值
- 矩阵的传递(新增)
- 矩阵获取
- 传递到顶点着色器
- 矩阵计算
- 绘图并渲染上屏
- 使用索引绘图(新增)
- 渲染上屏
2.2 色值传递
定义色值
//(1)顶点数组 前3顶点值(x,y,z),后3位颜色值(RGB)
GLfloat attrArr[] =
{
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, //左上0
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, //右上1
-0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, //左下2
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, //右下3
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, //顶点4
};
色值传递:
可以看到设置方式与顶点数组一毛一样。
//10.--------处理顶点颜色值-------
//(1).glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.glsl中的输入变量:positionColor保持一致
GLuint positionColor = glGetAttribLocation(self.myProgram, "positionColor");
//(2).设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(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);
4.3 矩阵
矩阵计算过程与OpenGL中所学的矩阵的计算一样,只是这里用到了第三方库的API。
因此API不需要专门记忆,只要知道过程即可。
通过矩阵对视图进行变换的过程以及实现可以查看之前的博客 四、OpenGL - 图形变换
矩阵计算过程
代码:
float width = self.frame.size.width;
float height = self.frame.size.height;
//12.创建4 * 4投影矩阵
KSMatrix4 _projectionMatrix;
//(1)获取单元矩阵
ksMatrixLoadIdentity(&_projectionMatrix);
//(2)计算纵横比例 = 长/宽
float aspect = width / height; //长宽比
//(3)获取透视矩阵
/*
参数1:矩阵
参数2:视角,度数为单位
参数3:纵横比
参数4:近平面距离
参数5:远平面距离
参考PPT
*/
ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 20.0f); //透视变换,视角30°
//13.创建一个4 * 4 矩阵,模型视图矩阵
KSMatrix4 _modelViewMatrix;
//(1)获取单元矩阵
ksMatrixLoadIdentity(&_modelViewMatrix);
//(2)平移,z轴平移-10
ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);
//(3)创建一个4 * 4 矩阵,旋转矩阵
KSMatrix4 _rotationMatrix;
//(4)初始化为单元矩阵
ksMatrixLoadIdentity(&_rotationMatrix);
//(5)旋转
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轴
//(6)把变换矩阵相乘.将_modelViewMatrix矩阵与_rotationMatrix矩阵相乘,结合到模型视图
ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
计算过程:
- 投影矩阵的创建(参数也一样)
- 模型视图的创建,将模式视图矩阵沿z轴平移-10个距离,这是将观察者远离屏幕10个单位距离
- 旋转矩阵的创建,并进行旋转
- 将模型视图矩阵叉乘上旋转矩阵得到模型视图矩阵
上面四步就是为了得到投影矩阵和模型视图矩阵,而模型视图矩阵做了两件事1)将观察者远离屏幕10个单位;2)模型视图矩阵叉乘旋转矩阵。
矩阵传递:
//11.找到myProgram中的projectionMatrix、modelViewMatrix 2个矩阵的地址。如果找到则返回地址,否则返回-1,表示没有找到2个对象。
GLuint projectionMatrixSlot = glGetUniformLocation(self.myProgram, "projectionMatrix");
GLuint modelViewMatrixSlot = glGetUniformLocation(self.myProgram, "modelViewMatrix");
//(4)将投影矩阵传递到顶点着色器
/*
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]);
//(7)将模型视图矩阵传递到顶点着色器
/*
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]);
过程:
- 先获取通道ID,注意此时的通道名称必须要与顶点着色器中的作为通道的字符串变量保持一致。
- 使用glUniformMatrix4fv方法传递数据到着色器中
重要API:
传递矩阵到顶点着色器
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
参数列表:
location:指要更改的uniform变量的位置
count:更改矩阵的个数
transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
value:执行count个元素的指针,用来更新指定uniform变量
4.4 索引绘图
索引数组:
这里的数组存储了三角形的连接方式
//(2).索引数组
GLuint indices[] =
{
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
};
索引绘图:
这个API也要记住,用来进行索引绘图的每个参数
/*
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);
4.5 着色器的实现
4.5.1 顶点着色器
attribute vec4 position;//顶点数组
attribute vec4 positionColor;//颜色数组
uniform mat4 projectionMatrix;//投影矩阵
uniform mat4 modelViewMatrix;//模型视图矩阵
varying lowp vec4 varyColor;//需要传递到片元着色器的颜色数组的接收者
void main()
{
varyColor = positionColor;//赋值到片元着色器中
vec4 vPos;
// 矩阵计算 投影矩阵*模型视图矩阵*顶点向量
//4*4 * 4*4 * 4*1
vPos = projectionMatrix * modelViewMatrix * position;
gl_Position = vPos;
}
注意:
- 着色器中的其他内容之前都已经熟悉了,这里新增的就是对矩阵的计算。
- 在OpenGL中便知道,矩阵需要左乘。
4.5.2 片元着色器
接收片元着色器中的色值
varying lowp vec4 varyColor;
void main()
{
gl_FragColor = varyColor;
}