上一篇文章介绍了平移变换,旋转变换,缩放变换三种矩阵,这篇文章来说明下两个重要的变换矩阵:透视矩阵和正交矩阵。要了解这两个矩阵,我们想要了解下面两种投影方式。
在计算机三维图像中,投影可以看作是一种将三维坐标变换为二维坐标的方法,常用到的有正交投影和透视投影。正交投影多用于三维建模(例如建筑的平面图采用的就是正交投影),透视投影则由于和人的视觉系统相似,多用于在二维平面中对三维世界的呈现。
1.透视投影
它是为了获得接近真实三维物体的视觉效果而在二维的纸或者画布平面上绘图或者渲染的一种方法,也称为透视图 。它具有消失感、距离感、相同大小的形体呈现出有规律的变化等一系列的透视特性,能逼真地反映形体的空间形象。一个常用的例子就是对于我们人眼来说,同一个物体近大远小的形象。这种投影方式就是为了还原人眼观察世界的效果。
原理:
基本的透视投影模型由视点E和视平面P两部分构成(要求E不在平面P上)。视点可以认为是观察者的位置,也是观察三维世界的角度。视平面就是渲染三维对象透视图的二维平面。如下图所示。对于世界中的任一点X,构造一条起点为E并经过X点的射线R,R与平面P的交点Xp即是X点的透视投影结果。三维世界的物体可以看作是由点集合 { Xi} 构成的,这样依次构造起点为E,并经过点Xi的射线Ri,这些射线与视平面P的交点集合便是三维世界在当前视点的透视图
透视投影的标准模型
这里一般取近界面为视平面,实际应用中会取近界面和远界面两个界面之间的范围为可视范围。
下面看一下GLKit中构造透视投影的方法:
GLKMatrix4MakePerspective(<#float fovyRadians#>, <#float aspect#>, <#float nearZ#>, <#float farZ#>)
第一个参数代表视角度,第二个是近截面在Z轴上距离原点的距离,第三个是远截面在Z轴上距离原点的距离。
2.正交投影
参考文章:正交投影推导过程
GLKit中构造正交投影矩阵的方法:
GLKMatrix4MakeOrtho(<#float left#>, <#float right#>, <#float bottom#>, <#float top#>, <#float nearZ#>, <#float farZ#>)
前面四个表示屏幕的范围,nearZ和farZ表示Z轴的可视范围,不在这个范围内的就被裁剪掉了。
下面看代码的实现:
1.透视投影:
//往Z轴负方向平移1.6个距离
GLKMatrix4 translate = GLKMatrix4MakeTranslation(0, 0, -1.6);
//随着时间绕着Y轴旋转
GLKMatrix4 rotate = GLKMatrix4MakeRotation(varyFactor, 0, 1, 0);
//透视投影
GLKMatrix4 perspectiveMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90), view.frame.size.width / view.frame.size.height, 0.2, 8.0);
//先旋转,后平移,最后透视投影(切记顺序)
self.transformMatrix = GLKMatrix4Multiply(translate, rotate);
self.transformMatrix = GLKMatrix4Multiply(perspectiveMatrix , self.transformMatrix);
//加载统一变量
GLuint uniformLocation = glGetUniformLocation(_esContext.program, "transform");
glUniformMatrix4fv(uniformLocation, 1, GL_FALSE, self.transformMatrix.m);
这里注意一点,farZ和NearZ表示远截面和近界面到原点的距离,这里为正值,但实际的位置在Z的负轴,所以这里我们要对绘制的图元,向 负Z轴平移1.6个单位,这样图元才能展示在界面上,效果如图所示:
2.正交投影
代码实现
_elapsedTime += 2.0;
float varyFactor = GLKMathDegreesToRadians(_elapsedTime);
_esContext.width = view.drawableWidth;
_esContext.height = view.drawableHeight;
// x, y, z, r, g, b,每一行存储一个点的信息,位置和颜色
static GLfloat triangleData[36] = {
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
};
glClear(GL_COLOR_BUFFER_BIT);
//x,y放大200倍,本来是展示的一个(1 * 1)像素点,需要将矩形显示出来
GLKMatrix4 scale = GLKMatrix4MakeScale(300, 300, 300);
//随着时间绕着Y轴旋转
GLKMatrix4 rotate = GLKMatrix4MakeRotation(varyFactor, 0, 1, 0);
CGFloat width = view.frame.size.width / 2.0;
CGFloat height = view.frame.size.height / 2.0;
//正交投影(可视的Z轴范围是-10.0 ~ 10.0)
GLKMatrix4 orthoMatrix = GLKMatrix4MakeOrtho(-width, width, -height, height, -10.0, 10.0);
//注意顺序,先旋转,后平移,最后矩阵投影
self.transformMatrix = GLKMatrix4Multiply(scale, rotate);
self.transformMatrix = GLKMatrix4Multiply(orthoMatrix, self.transformMatrix);
//加载统一变量
GLuint uniformLocation = glGetUniformLocation(_esContext.program, "transform");
glUniformMatrix4fv(uniformLocation, 1, GL_FALSE, self.transformMatrix.m);
//gen Buffer and Bind
if (_vertexBufferId == 0) {
glGenBuffers(1, &_vertexBufferId);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangleData), triangleData, GL_STATIC_DRAW);
}
int colorLocation = glGetAttribLocation(_esContext.program, "vcolor");
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(colorLocation);
GLuint offset = 3 * sizeof(float);
//加载顶点属性数据
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), NULL);
glVertexAttribPointer(colorLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (const void *) offset);
_esContext.drawFunc(&_esContext);
glDisableVertexAttribArray(GLKVertexAttribPosition);
glDisableVertexAttribArray(colorLocation);
}
效果如图:
摘自书中8.2.1节
MVP矩阵用于将3D世界中的物体坐标转换为裁剪坐标,最后裁剪坐标进行视口变换,就变换为屏幕上的坐标了,这两篇文章介绍了模型矩阵(M)和投影矩阵(p),下一篇文章介绍视图矩阵(摄像机),后面就可以正式进入3D渲染的学习了。