学习之路系列
今天我们实现一个三维空间中物体的变换操作
本篇主要内容
三维坐标变换
效果图
实现过程
首先我们跟之前一样给出以下顶点数据
//4个顶点(分别表示xyz轴)
static const float Vertices[] = {
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
//4个点的颜色(分别表示RGBA值)
static const float Colors[] = {
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1,
};
然后进行渲染, 中间的渲染缓冲区、帧缓冲区、编译着色器
我们暂时先省略,有不明白的小伙伴可以参考这篇文章
- (void)render:(CADisplayLink *)displayLink {
//用指定的颜色清除,清除颜色被设置为(0.5f, 0.5f, 0.5f, 1.0f), 所以为黑色
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//设定窗口的范围(如果不是很明白, 可以自己动手修改下试试)
//他这个是左下角为(0,0) 右上角为(width,height)
glViewport(0, 0, _width, _height);
//指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置。
/*
* indx:指定要修改的顶点属性的索引值
* size:指定每个顶点属性的组件数量。(必须坐标xyz轴就是3, 颜色rgba就是4)
* type:指定数组中每个组件的数据类型。(一般为GL_FLOAT)
* normalized:一般为GL_FALSE
* stride:指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。
* ptr:指向数据的指针
*/
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, Vertices);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, 0, Colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(Vertices) / (sizeof(int) * 3));
//把缓冲区的数据呈现到UIView上
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
渲染结果
我们接下来需要把他弄成正方形的,由于还没有学习投影,我们先改动渲染的窗口来达成这一目的
将render:
方法中的
glViewport(0, 0, _width, _height);
改成
glViewport(0, 0, _width, _width);
看下效果
这样子就实现了正方形了
三维变换
给大家介绍一个网址LearnOpenGL, 上面对三维变换的讲解很详细, 下面就来实现下这三种操作(缩放、位移、旋转)
系统已经提供了矩阵操作的框架, 网上也有一些大神提供的矩阵操作的第三方库,我们可以直接拿到使用,不会矩阵的小伙伴也可以玩转三维操作O(∩_∩)O。这里我们直接使用系统的
我们先修改下顶点着色器
attribute vec4 Position;
attribute vec4 InColor;
varying vec4 OutColor;
uniform mat4 Model; //new
void main(void){
OutColor = InColor;
// gl_Position = Position;
gl_Position = Model * Position;
}
并在编译着色器(compileShaders:)
的地方获取着色器中的ModelView
变量
_modelView = glGetUniformLocation(_program, "ModelView");
下面的的代码直接在
-
缩放
GLKMatrix4 modelView = GLKMatrix4MakeScale(_transformScale[0],
_transformScale[1],
_transformScale[2]);
//给着色器变量赋值
/*
location: 变量的位置
count : 要更改变量的个数
transpose: 是否需要转置
value : 给出对应count个元素的指针
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
GLKMatrix4MakeScale
是系统初始化缩放矩阵的函数
看下效果:
-
位移
GLKMatrix4 modelView = GLKMatrix4MakeTranslation(_transformTranslation[0],
_transformTranslation[1],
_transformTranslation[2]);
//给着色器变量赋值
/*
location: 变量的位置
count : 要更改变量的个数
transpose: 是否需要转置
value : 给出对应count个元素的指针
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
GLKMatrix4MakeTranslation
是系统初始化平移矩阵的函数
看下效果:
-
旋转
GLKMatrix4 modelView = GLKMatrix4MakeRotation(_transformRotation[0],1,0,0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[1], 0, 1, 0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[2], 0, 0, 1);
//给着色器变量赋值
/*
location: 变量的位置
count : 要更改变量的个数
transpose: 是否需要转置
value : 给出对应count个元素的指针
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
旋转操作需要将3个轴(x、y、z)
分开单独操作
上面的旋转函数需要用弧度。
角度 = 弧度 * (180.0f / PI)
弧度 = 角度 * (PI / 180.0f)
看下效果:
-
矩阵组合
学过矩阵的童鞋都知道,矩阵相乘不遵守交换律
缩放、位移、旋转
三种矩阵按照不同的顺序相乘会产生不一样的效果
举个例子: 先缩放
在位移
那么当你缩放完以后,你的位移向量一样会被缩放,比如你要向右移动两个单位,这时候缩小两倍,那么的平移的向量一样会缩小两倍,就变成了一个单位
顺序的话一般都没有硬性规定, 看需求, 本文顺序位移->旋转->缩放
下面我们把三种效果组合试一下
GLKMatrix4 modelView = GLKMatrix4MakeTranslation(_transformTranslation[0],
_transformTranslation[1],
_transformTranslation[2]);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[0], 1, 0, 0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[1], 0, 1, 0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[2], 0, 0, 1);
modelView = GLKMatrix4Scale(modelView,
_transformScale[0],
_transformScale[1],
_transformScale[2]);
//给着色器变量赋值
/*
location: 变量的位置
count : 要更改变量的个数
transpose: 是否需要转置
value : 给出对应count个元素的指针
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
看下效果:
本来想着这篇文章到这样就结束了,但是回头一看发现内容有点少,就顺便在实现多一个效果吧
先看下下面这段顶点数据
//4个顶点(分别表示xyz轴)
static const float Vertices[] = {
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
这里有个Z轴还没用到的,上面是其中一个面
我们还需要把后面的4个坐标点加上,才能凑够一个立方体(8个点)
//4个顶点(分别表示xyz轴)
static const float Vertices[] = {
//前面4个坐标
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
//后面4个坐标
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
};
//4个点的颜色(分别表示RGBA值)
static const float Colors[] = {
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1,
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1,
};
由于我们只给了8个点的坐标, 所以我们用原来的这个方法有点不合适
glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(Vertices) / (sizeof(float) * 3));
我们来介绍另一个方法glDrawElements
, 这个方法跟glDrawArrays
一样都是渲染
这两种方法的介绍,可以看下这篇文章
创建索引数组
static const GLubyte Indices[] = {
0, 1, 2,
2, 3, 1,
4, 5, 6,
6, 7, 5,
2, 3, 6,
6, 7, 3,
0, 1, 4,
4, 5, 1,
0, 4, 2,
2, 6, 4,
1, 5, 3,
3, 7, 5
};
渲染
/*
* mode : 渲染的方式
* count : 索引数组元素的数量
* type : 索引值的类型
* indices: 索引数组的指针
*/
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, Indices);
很明显的看到,层次有点不对
我们需要新增一个深度缓冲区
深度缓冲区简单来说就是让物体的渲染有层次感, 详解看这篇文章
下面这段代码需要在渲染缓冲区前调用
/**
* 设置深度缓冲区
*/
- (void)setupDepthBuffer {
glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _width, _height);
}
在帧缓冲区中新增一段代码
/**
* 设置帧缓冲区
*/
- (void)setupFrameBuffer {
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer); //new
}
将render:
中的glClearColor
修改下
glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
// glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //new
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //new
效果:
结语
自从把这个三维的立方体实现以后,突然感觉OpenGLES很好玩,虽然在这过程中不断的踩坑调试,但是实现以后又有了满满的信心.(๑•̀ㅂ•́)و✧加油
后面的例子会越来越好玩的,一起一步一步的学下去吧
Demo
1.可以尝试着把
缩放、位移、旋转
三种方式的顺序换一下,看会有怎么样子的效果
2.感兴趣的小伙伴可以把纹理加上
3.下篇文章我们来实现一个更好玩的坐标系统