三、OpenGL 3D绘制

1、三角形变金字塔

有了上面OpenGL绘制2D三角形的经验,大家可以想下,要绘制3D的图形,需要怎么做呢?

也许大家会想到,添加z坐标就可以实现了。前面画2D三角形时候,坐标点z的值都是0,是不是只要z的值不为0,那么3D的效果就出来了呢?

下面我们把上面的三角形改造为金字塔,带大家认识3D绘制。

金字塔模型为:

image

添加金字塔的顶点坐标:

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、坐标系

观察点:也叫摄像机

image

如上图,我们需要把物体移动到摄像机空间中,这样才能看到物体。

如何移动物体呢?


世界空间-世界坐标系

摄像机和物体必须在同一个坐标系空间中,才能把物体移到摄像机视野中。现实中,我们可以看到物体,是因为物体和我们都在同一个世界坐标系中。对应手机,也有世界坐标系的概念,我们可以认为手机中的世界坐标为:

image

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、投影变换

image

OpenGL中有两种投影方式:

  • 透视投影:有远小近大的效果
  • 正投影:跟距离没关系,远近的同一物体一样大

透视投影产生的3D图形更接近我们眼睛观察到的物体,所以绘制3D图形,一般选择透视投影

透视投影原理


下面我直接截取3D图形-9.4.4章节,解析小孔成像原理,感兴趣的自行去阅读:

image
image
image
image

通过上面分析,除了知道小孔成像原理,产生远小近大效果。还让我们知道计算机如何将三维坐标投影到同一个平面,形成二维图像,这样我们才能在屏幕上显示,因为我们屏幕本来就是二维的。


代码实现:

GLfloat projectionScaleFix = width / height;
GLKMatrix4 projectionMat4 = GLKMatrix4MakePerspective(FOV, projectionScaleFix , 1, 180);
image
  • 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轴旋转一定角度。这里使用了个旋转组合。

这里引出一个问题,旋转一定角度,是往哪个方向旋转呢?这里就得明确正反方向。

image

前面提到OpenGL使用的是右手坐标系,所以正反方向如右图。

image

GLKit还提供很多操作函数:

GLKMatrix4RotateX() 绕x轴旋转
GLKMatrix4RotateY()
GLKMatrix4RotateZ()
GLKMatrix4Scale() 缩放的
等等....

一些第三方库和系统库已经帮我们封装了各种3D图形线性变换的函数。使用很方便,就算不理解矩阵变换原理,也能使用。

至此,你对3D绘图应该有了大体的了解。可以自己绘制3D图形,并且可以让3D图形动起来。下节是介绍矩阵变换的原理,主要是告诉你为什么一个矩阵可以让3D图形旋转,缩放等。理解原理,可能你后续开发中还是使用系统提供的api,但增加了自定制的可能性。有兴趣的可以继续深入学习。

具体实现可参考Demo代码:https://github.com/wulang150/MyProject/tree/master/MyProject/Controller/TmpTestController/OpenGL

你可能感兴趣的:(三、OpenGL 3D绘制)