1、三角形变金字塔
有了上面OpenGL绘制2D三角形的经验,大家可以想下,要绘制3D的图形,需要怎么做呢?
也许大家会想到,添加z坐标就可以实现了。前面画2D三角形时候,坐标点z的值都是0,是不是只要z的值不为0,那么3D的效果就出来了呢?
下面我们把上面的三角形改造为金字塔,带大家认识3D绘制。
金字塔模型为:
添加金字塔的顶点坐标:
const GLfloat vPyramid[18*3] = {
1.0, -0.8, 0.0,
0.0, -0.8, -1.0,
0.0, 0.8, 0.0,
0.0, -0.8, 1.0,
1.0, -0.8, 0.0,
0.0f, 0.8f, 0.0f,
-1.0, -0.8, 0.0,
0.0, -0.8, 1.0,
0.0, 0.8, 0.0,
0.0, -0.8, -1.0,
-1.0, -0.8, 0.0,
0.0, 0.8, 0.0,
0.0, -0.8, 1.0,
-1.0, -0.8, 0.0,
0.0, -0.8, -1.0,
1.0, -0.8, 0.0,
0.0, -0.8, 1.0,
0.0, -0.8, -1.0,
};
由三个点组成一个三角形面,一组有三个点,前面四组分别表示金字塔的四个侧面,下面两组刚好组成金字塔底部的矩形。
然后修改:
glDrawArrays(GL_TRIANGLES, 0, 18);
最后一个参数改为18,表示一个有18个点。这时候运行代码,发现没有生成3D的金字塔。可见单单通过添加z轴坐标是不行的。
3D图理论:
我们现实中如何观察3D物体。先找到一个观察位置,然后由观察位置看向物体。调整不同的观察位置,可以看到物体不同的角度。并且与观察物体的距离发生变化,观察到的物体大小也会发生变化,产生远小近大的效果。
计算机要绘制3D效果,也模仿了我们现实中部分的观察原理。首先需要设置观察位置,然后再把物体移动到观察视野中,并且通过透视投影,让物体产生远小近大的效果。
2、坐标系
观察点:也叫摄像机
如上图,我们需要把物体移动到摄像机空间中,这样才能看到物体。
如何移动物体呢?
世界空间-世界坐标系
摄像机和物体必须在同一个坐标系空间中,才能把物体移到摄像机视野中。现实中,我们可以看到物体,是因为物体和我们都在同一个世界坐标系中。对应手机,也有世界坐标系的概念,我们可以认为手机中的世界坐标为:
OpenGL使用的是右手坐标系。关于左右手坐标系可参考:3D图形.pdf-2.3.3
模型空间-模型坐标系
模型自身的坐标系,坐标原点在模型的某一点上,一般是几何中心位置为原点,坐标系随物体移动而移动
摄像机空间-摄像机坐标系
摄像机坐标系就是以摄像机本身为原点建立的坐标系,摄像机本身并不可见,它表示的是有多少区域可以被显示(渲染)
变换过程:模型空间转换到世界空间,再由世界空间转换到摄像机空间。不能直接从模型空间转换到摄像机空间,因为它们不在同一个坐标系。需要以世界空间作为转换的中介。
关于更多坐标系的知识,可以参考:
- 3D图形.pdf-第三章
- OpenGL ES 2.0 (iOS)[04]:坐标空间 与 OpenGL ES 2 3D空间
其实看完网上资料说的各种坐标系的解析,可能会感觉更懵。
对于OpenGL,关于坐标系可以这样理解:
对于3D物体,因为我们给的坐标点就是在世界坐标系中的,所以物体已存在世界坐标系中。我们给的坐标点是以世界坐标系原点作为中心点定的,所以物体的原点默认与世界坐标系原点重合。
对于摄像机,也是在世界坐标系中的,并且默认在原点(0,0,0),摄像头朝向为z轴的负方向。
我们需要做的,就是把物体移动到摄像机视野中,或者把摄像机移动到可以看到物体的位置。因为3D物体和摄像机都在世界坐标系中,所以可以省略掉模型空间转换到世界空间。直接由模型空间转换到摄像机空间。
3、模型与视变换
物体与摄像机原点一样,那么物体肯定不在摄像机视野中,根据摄像头朝向为z轴的负方向,只需要把物体往z轴负方向移动一段距离,那么物体就在摄像机视野中了。
GLKMatrix4 modelMat4 = GLKMatrix4Identity;
modelMat4 = GLKMatrix4Translate(modelMat4, 0.0, 0.0, -3);
上面代码是得到一个让物体往z轴负方向移动3个单位的矩阵。
这里你对矩阵暂时不需要理解太深,只需要知道物体移动、旋转和缩放等操作,都记录在矩阵中。物体只需要乘上这个矩阵,就发生了矩阵记录的所有动作。
4、投影变换
OpenGL中有两种投影方式:
- 透视投影:有远小近大的效果
- 正投影:跟距离没关系,远近的同一物体一样大
透视投影产生的3D图形更接近我们眼睛观察到的物体,所以绘制3D图形,一般选择透视投影
透视投影原理:
下面我直接截取3D图形-9.4.4章节,解析小孔成像原理,感兴趣的自行去阅读:
通过上面分析,除了知道小孔成像原理,产生远小近大效果。还让我们知道计算机如何将三维坐标投影到同一个平面,形成二维图像,这样我们才能在屏幕上显示,因为我们屏幕本来就是二维的。
代码实现:
GLfloat projectionScaleFix = width / height;
GLKMatrix4 projectionMat4 = GLKMatrix4MakePerspective(FOV, projectionScaleFix , 1, 180);
- FOV:可以理解为摄像机视野的大小,值越大,那么对应的Near和Far面也越大,投影的物体大小不变,场景变大了,相对物体就变小了。
- projectionScaleFix:Near和Far面的宽高比,只有是比率,才能随FOV值改变面的大小
经过透视函数GLKMatrix4MakePerspective处理后,得到一个透视投影的矩阵。所以,矩阵在3D绘制中是相当重要的。后面也会讲到矩阵的一些重要知识。
5、输入部分
好了,这里我们得到了两个矩阵:
- 模型移动矩阵:使物体往z轴负方向移动--modelMat4
- 透视投影矩阵:产生透视投影效果--projectionMat4
这两个矩阵,我们必须给到OpenGL,才能发生作用了。如何给OpenGL呢,这里可以参考OpenGL 2D绘制时候的输入部分:
attribute vec4 Position; // position of vertex
attribute vec4 SourceColor; // color of vertex
uniform highp mat4 u_Projection;
uniform highp mat4 u_ModelView;
varying vec4 DestinationColor;
void main(void) {
DestinationColor = SourceColor;
// gl_Position is built-in pass-out variable. Must config for in vertex shader
gl_Position = u_Projection * u_ModelView * Position;
}
对比2D的顶点着色器,我们增加了:
uniform highp mat4 u_Projection;
uniform highp mat4 u_ModelView;
gl_Position = u_Projection * u_ModelView * Position;
很明显,增加的两个变量分别对应透视矩阵和移动矩阵。最后在让物体的每一个点乘以这两个矩阵,使物体发生对应的改变。
6、输出部分
输入部分已基本完成。对于输出部分,我们还需要进行一些调整。增加个深度缓存区:
//3D 深度缓冲区
glGenRenderbuffers(1, &_depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER,
GL_DEPTH_COMPONENT16,
_renderbufferWidth,
_renderbufferHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
绘制2D图的时候,有帧缓冲区和渲染缓冲区,这里得增加个深度缓冲区来缓存3D图的深度值,在深度测试和颜色混合阶段需要用到。
至此,基本工作已完成。无其他问题的话,应该是可以绘制出3D图形了。
7、让金字塔动起来
这部分,我们要让金字塔动起来。
让金字塔旋转起来,代码如下:
//设置模型矩阵
GLKMatrix4 modelMat4 = GLKMatrix4Identity;
//加入位移
modelMat4 = GLKMatrix4Translate(modelMat4, 0.0, 0.0, -3);
//加入旋转
modelMat4 = GLKMatrix4Rotate(modelMat4, degree, 1, 0, 0);
modelMat4 = GLKMatrix4Rotate(modelMat4, degree, 0, 1, 0);
modelMat4 = GLKMatrix4Rotate(modelMat4, degree, 0, 0, 1);
GLKMatrix4Translate(modelMat4, 0.0, 0.0, -3):这个是上节提到的,让物体往z轴负方向移动3个单位,就是把物体移动到摄像机可视范围中。
GLKMatrix4Rotate(modelMat4, degree, 1, 0, 0):让物体绕[1, 0 ,0]轴旋转degree度,[1, 0 ,0]就是x轴。
同理,剩下两个分别是绕y轴和z轴旋转一定角度。这里使用了个旋转组合。
这里引出一个问题,旋转一定角度,是往哪个方向旋转呢?这里就得明确正反方向。
前面提到OpenGL使用的是右手坐标系,所以正反方向如右图。
GLKit还提供很多操作函数:
GLKMatrix4RotateX() 绕x轴旋转
GLKMatrix4RotateY()
GLKMatrix4RotateZ()
GLKMatrix4Scale() 缩放的
等等....
一些第三方库和系统库已经帮我们封装了各种3D图形线性变换的函数。使用很方便,就算不理解矩阵变换原理,也能使用。
至此,你对3D绘图应该有了大体的了解。可以自己绘制3D图形,并且可以让3D图形动起来。下节是介绍矩阵变换的原理,主要是告诉你为什么一个矩阵可以让3D图形旋转,缩放等。理解原理,可能你后续开发中还是使用系统提供的api,但增加了自定制的可能性。有兴趣的可以继续深入学习。
具体实现可参考Demo代码:https://github.com/wulang150/MyProject/tree/master/MyProject/Controller/TmpTestController/OpenGL