1. 世界坐标系
2. 物体(模型)坐标系
3. 摄像机坐标系
4. 惯性坐标系
1. 世界空间
2. 模型空间
3. 摄像机空间
4. 裁剪空间
5. 屏幕空间
1. 变换发生的过程
2. 各个变换流程分解简述
3. 四次变换与编程应用
- **ep1:** 比如我现在身处广州,要描述我现在所在的空间,对我而言最有意义就是,我身处广州的那里,而此时的广州就是我关心的“世界坐标系”,而不用描述我现在的经纬坐标是多少,不需要知道我身处地球的那个经纬位置。
世界坐标系形成的空间,光线计算一般是在此空间统一进行;
模型坐标系形成的空间,这里主要包含模型顶点坐标和表面法向量的信息;
第一次变换
模型变换(Model Transforms):就是指从模型空间转换到世界空间的过程
摄像机空间
摄像机空间,就是黄色区域所包围的空间;
摄像机空间在这里就是透视投影,透视投影用于 3D 图形显示,反映真实世界的物体状态;
透视知识扩展 《透视》
第二次变换
视变换(View Transforms):就是指从世界空间转换到摄像机空间的过程
图形属于裁剪空间则保留,图形在裁剪空间外,则剔除(Culled)
摄像机 带注解
标号(3)视景体 ,所指的空间即为裁剪空间,这个空间就由 Left、Right、Top、Bottom、Near、Far 六个面组成的四棱台,即视景体。
视景体
图中紫色区域为视场角
fov & zoom
从而引出,视场缩放为:
zoom
锥面 | 关系 |
---|---|
Near | z < -w |
Far | z > w |
Bottom | y < -w |
Top | y > w |
Left | x < -w |
Right | x > w |
即坐标值,不符合这个范围的,都会被裁剪掉
坐标 | 值范围 |
---|---|
x | -w , w |
y | -w, w |
z | -w, w |
第三次变换
投影变换(Projection Transforms): 当然包括正交、透视投影了,就是指从摄影机空间到视景体空间的变换过程
它就是显示设备的物理屏幕所在的坐标系形成的空间,它是 2D 的且以像素为单位,原点在屏幕的几何中心点
屏幕坐标空间.jpg
第四次变换(最后一次)
视口变换(ViewPort Transforms): 指从裁剪空间到屏幕空间的过程,即从 3D 到 2D
这里主要是关注像素的分布,即像素纵横比;因为图形要从裁剪空间投影映射到屏幕空间中,需要知道真实的环境的像素分布情况,不然图形就会出现变形;
《OpenGL ES 2.0 (iOS)[02]:修复三角形的显示》这篇文章就是为了修复屏幕像素比例不是 1 : 1 引起的拉伸问题,而它也就是视中变换中的一个组成部分。
像素缩放比
OpenGL ES 2 变换流程图
函数 | 描述 |
---|---|
glViewport | 调整视窗位置和尺寸 |
glDepthRange | 调整视景体的 near 和 far 两个面的位置 (z) |
glViewport | |
---|
| void glViewport(GLint x, GLint y, GLsizei w, GLsizei h) |
| x, y 以渲染的屏幕坐标系为参考的视口原点坐标值(如:苹果的移动设备都是是以左上角为坐标原点) |
| w, h 要渲染的视口尺寸,单位是像素 |
glDepthRange | |
---|
| void glDepthRange(GLclampf n, GLclampf f) |
| n, f n, f 分别指视景体的 near 和 far ,前者的默认值为 0 ,后者的默认值为 1.0, 它们的值范围均为 0.0, 1.0 , 其实就是 z 值 |
#version 100
attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView;
attribute vec4 v_Color;
varying mediump vec4 f_color;
void main(void) {
f_color = v_Color;
gl_Position = v_Projection * v_ModelView * v_Position;
}
v_Projection 表示投影变换;v_ModelView 表示模型变换和视变换;
请看《OpenGL ES 2.0 (iOS)[02]:修复三角形的显示》 这篇文章,专门讲模型变换的。
Camera Model
要完成摄像机正确地显示模型,要设置摄像机位置、摄像机的焦距:
上面提到, ES 版本没有 gluLookAt 这个函数,但是我们知道,这里做的都是矩阵运算,所以可以自己写一个功能一样的矩阵函数即可;
// 我不想写,所以可以用 GLKit 提供给我们的函数
/*
Equivalent to gluLookAt.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ);
Frustum
函数的 eye x、y、z 就是对应图片中的 Eye at ,即摄像机的位置;
函数的 center x、y、z 就是对应图片中的 z-axis 可视区域的中心点;
函数的 up x、y、z 就是对应图片中的 up 指摄像机上下的位置(就是角度);
view frustum
当模型处于视景体外时会被剔除掉,如果模型有一部分在视景体内时,模型的点信息只会剩下在视景体内的,其它的点信息不渲染;
/*
Equivalent to glFrustum.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeFrustum(float left, float right,
float bottom, float top,
float nearZ, float farZ);
这个是设置视景体六个面的大小的;
透视投影
对应的投影公式 :
完整的透视投影公式
使用 GLKit 提供的函数:
/*
Equivalent to gluPerspective.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakePerspective(float fovyRadians, // 视场角
float aspect, // 屏幕像素纵横比
float nearZ, // 近平面距摄像机位置的距离
float farZ); // 远平面摄像机位的距离
Orthographic projection
对应的投影公式 :
完整的正交投影公式
/*
Equivalent to glOrtho.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeOrtho(float left, float right,
float bottom, float top,
float nearZ, float farZ);
这里就是设置 glViewPort 和 glDepthRange 当然 2D 图形不用设置 glDepthRange ;
1. Generate ,请求 depth buffer ,生成相应的内存标识符
2. Bind,绑定申请的内存标识符
3. Configure Storage,配置储存 depth buffer 的尺寸
4. Attach,装载 depth buffer 到 Frame Buffer 中 具体的程序代码:
#version 100
attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView; // 投影变换、模型视图变换
attribute vec4 v_Color;
varying mediump vec4 f_color;
void main(void) {
f_color = v_Color;
gl_Position = v_Projection * v_ModelView * v_Position;
}
一般是把四次变换写成这两个,当然也可以写成一个;因为它们是一矩阵,等同于一个常量,所以使用的是 uniform 变量,变量类型就是 mat4 四乘四方阵(齐次矩阵);
注意,要在 glUseProgram 函数后,再使用 glUniform 函数来赋值变量,不然是无效的;*
依次完成 模型变换、视变换、投影变换,即可;它们两两用矩阵乘法进行连接即可;
如:modelMatrix 点乘 viewMatrix , 它们的结果再与 projectionMatrix 点乘,即为 ModelViewMatrix ;
GLKit 点乘函数,
GLK_INLINE GLKMatrix4 GLKMatrix4Multiply(GLKMatrix4 matrixLeft, GLKMatrix4 matrixRight);
使用 glClear(GL_DEPTH_BUFFER_BIT); 进行清除,当然之后就是要使能深度测试 glEnable(GL_DEPTH_TEST); 不然图形会变形;
最好,也使能 glEnable(GL_CULL_FACE); 这里的意思就是,把在屏幕后面的点剔除掉,就是不渲染;判断是前还是后,是利用提供的模型顶点信息中点与点依次连接形成的基本图元的时钟方向进行判断的,这个 OpenGL 会自行判断;
ClockWise & Counterclockwise
左为顺时针,右为逆时针;
使用 OpenGL ES 提供的 glViewPort 和 glDepthRange 函数即可;
Github: 《DrawSquare_3DFix》
五、参考书籍
《OpenGL ES 2.0 Programming Guide》
《OpenGL Programming Guide 8th》
《3D 数学基础:图形与游戏开发》
《OpenGL 超级宝典 第五版》
《Learning OpenGL ES For iOS》