关于本文
本文主要想介绍一下3D渲染的基本流程,及怎样把一个三角形(0,1,0),(1,0,1),(0,0,1)最终渲染到屏幕上来。文章的目的是对3D渲染流程做一个简单的介绍,其中不涉及任何语言的API
参考资料
- 《3D游戏编程大师技巧》 PFD地址 http://download.csdn.net/detail/iamzealotwang/652886 (中文)
是我很早之前分享的一本书.非常不错,最早是在学校图书馆里面看到的,市面上一度绝版,后来找了一家淘宝店复印了一本才留了下来。
刚刚查了一下,现在已经又有卖的的,如果对3D感兴趣建议一定要留一套 - 《3D数学基础:图形与游戏开发》 PDF地址 http://download.csdn.net/detail/iamzealotwang/4886344
- How Stage3D works http://www.adobe.com/devnet/flashplayer/articles/how-stage3d-works.html(英文)
局部坐标和世界坐标
接触Flash的人应该不太难理解这个概念吧,比如在库中建立一个边长为50的正方形,设置其左上角为原点,局部坐标的(0,0)为原点
然后将其拖入Flash的主舞台上面以后,就会得到一个全局坐标(84,110)
那刚才拖动的一下操作,完成的是怎样的一个运算呢?
抛开填充的蓝色先不谈,那么几何上面来说,正方形是由4个点构成的,A(0,0) B(0,50) C(50,0) D(50,50)
刚才做的操作可以理解把该正方形拖放到舞台的(84,100)位置。对应的得到ABCD四点坐标分别为A’(0+81,0+100) B’(0+84,0+50) C’(50+84,0+100) D’(50+84,50+100)
这样也就是完成了一次局部坐标到世界坐标的转换
那同样的道理,对于一个3D坐标点A(0,0,0) 也可以执行一次坐标变换,比如(84,100,37),这样也能得到一个对应的3D坐标点A’(0+81,0+100,0+37)
为何要进行局部坐标到世界坐标转换
在平常我们做Flash时候也一样,比如要做一个小人站在房子边上的图,那肯定是单独建立两个元件 元件::小人 元件::房子 ,然后将其拖入主舞台拼凑在一起。3D中也是一样的各种物体也是现在自己的坐标系内制作好,
最后放入场景内进行的拼接
了解矩阵
在进一步介绍3D之前,首先需要了解一下矩阵。本来想给出一些自己的例子,不过参考了一下现有资料,发现没有太多好补充的东西。如果对矩阵的基本改变还不是很了解
(比如矩阵是做什么用的,矩阵相乘,旋转矩阵,平移矩阵等)
请参考如下部分
《3D游戏编程大师技巧》 P161~P165
《3D数学基础:图形与游戏开发》 P85~P107
摄像机&视景体
在把所有物体完成了从局部坐标系到全局坐标系的转化,及完成了构建一个3D场景,只是这个场景中全部是由点构成的。这个时候就需要引入摄像机的概念,摄像机就像一双在这3D世界中的眼睛,眼睛的位置不同,
看的方向不同,那最终看到的景象也就不同.
和现实世界中的摄像机稍微有些不同,在于需要给这个摄像空间定义一个区域,及近剪裁面和远剪裁面。也就是小于近剪裁面和大于远剪裁面的物体将不会被渲染
在远剪裁面之外的物体: 可以理解为即使渲染了该物体也会是一个点,所以不进行渲染。在玩儿一些早期的3D游戏时候有时候会出现这种情况,一直向前走,前方的建筑物会突然从远处出现,我想也是这种原因吧
在近剪裁面之外的物体: 如果渲染的话会是一个非常大的物体,所以也不进行渲染。(近剪裁面如果用现实中的相机做比喻似乎不是很容易理解,当时若吧这个放入数学定义中去理解,反而容易一些)
世界坐标到相机坐标的转换
当确定好视景体后,也就可以确定了哪些物体最终可以见,哪些是不可以见。剔除这些不可见的物体后,需要将其余物体进行世界坐标到相机坐标的转换。
在一种极端情况下是不需要进行该步骤的,就是相机在原点(0,0,0)且方向和坐标轴的方向是一致的,此时不需要再进行转换
但是大部分时候,相机其实是不在原点的,且所朝的方向也不是坐标轴方向。
此时就要对这些物体(物体的每个顶点)进行世界坐标到相机坐标的变换。
变换一共分为几个步骤,
第一步 平移
如图,也就是假设相机的坐标点为(cam_x,cam_y,cam_z),那对所有剩余物体的顶点均执行平移操作(world_x-cam_x,world_y-cam_y,world_z-cam_z)
第二步 旋转
此时相机已经处于坐标原点处了,不过相机镜头仍旧是歪的,仍旧需要对其进行x,y,z三方向旋转才可以将相机还原为最开始那张图所画的样子。
对于先旋转哪个,后旋转哪个是无所谓的。及按xyz还是zyx方向旋转都是一样
最终流程:
为何要进行相机坐标转换
如果还不是很理解为何要进行相机坐标转换,可以再回到2D世界,还是最早的例子,那个正方形A(0,0) B(0,50) C(50,0) D(50,50) 经过的局部到世界坐标的转换是A'(0+81,0+100) B'(0+84,0+50) C'(50+84,0+100) D'(50+84,50+100)
A'点也就是为(0+81,0+100)->(81,100)
此时如果假设你自己就是相机,眼睛和舞台的0,0点对齐,眼部和显示器距离为300,那么A'点在你眼中的三维坐标也就是(81,100,300)
此时也就是之前所说的极端情况,摄像机和全局坐标同轴且共相。那A'点(81,100,300) 也就是最终渲染到屏幕上面的坐标点,但是
如果此时你离开显示器一段距离,且歪一下脑袋,再看A'点(81,100,300),这时候其所在你自己眼睛(摄像机)中的位置就不是原来那个位置了,
假设此时你看到的景象是M,
那你可以想象此时你回到原位,然后有另外一个人,抱起你的显示器放在另外一个位置且稍微转一个角度
此时你看到的景象仍旧是M,
那个人对那个物体进行的操作(抱起来放边上且转了一个角度)就是相机坐标变换
物体剔除&背面消除
这块知识我想简单的说一下,在《3D游戏编程大师技巧》 里面有非常详细的说明
物体剔除:
物体剔除在2D中也是存在的,对一个物体进行包围盒测试,如果不再场景内则不显示该物体,3D中也是做同样的操作,只不过包围盒是三维的,且探测范围也不再是一个平面,而是整个视景体。
背面消除:
当一个物体确实是在视景体内部时候,也不是所有面均需要最终渲染出来,此时要做的就是背面剔除,将那些被完全挡住的平面剔除出去
为何要进行剔除
在渲染流程的前段步骤,所有操作均是针对点的,比如坐标转换,剔除等.当最终确定了一个面确实要被渲染以后。后半段的操作及要对该面进行着色(设置纹理),光照处理等。
所以如果可以在前期多剔除掉一个面,就应该多剔除一个。
透视变换
当完成了一切剔除操作后,对于剩下的平面(此时剩余的仅为一个个平面了),还需要进行透视变换。也就是将一个三维点A'(81,100,300) 变换为一个二维的坐标点。
这里面的转换涉及两种
正交投影
来源 http://baike.baidu.com/view/3153027.htm
这种投影比较简单,及直接去除z点的值即可,一般用在工业制图上面(AutoCAD),对于Starling(Flash上面使用Stage3D技术加速2D游戏的引擎),目前我还不是很了解。不过为自己写的一个Demo,投影选择的就是
正交投影。因为原本需要渲染的就是一个2D平面,只要其在视景体内,其大小就和所在位置无关了
透视投影
来源 http://baike.baidu.com/view/1318206.htm
透视投影及实现了近大远小的投影,及离摄像机距离较远的物体看起来会相对小一些,离摄像机进的物体看起来更大一些。和真实世界中看到的是一样的。在三维游戏中用到的均为这种投影
两者的区别下面这张图很好的标示出来
左面的为透视投影,右面的为正交投影
整体流程概览
3D渲染流程上面大体就是这样了
图中在背面消除后采取了两种方式,
B路线为画家算法(http://baike.baidu.com/view/2020287.htm),
A路线为在常规的物体消除后再进行一次空间剪裁(及把那些不是完全在视景体内部的元件再剪裁,及AABB测试压线的物体)
但是无论是经过A路线,还是B路线 最终剩余的平面就会开始光栅化(忽略屏幕坐标变化,具体的请看书)。及真正的对其进行着色,打光等操作了
本文和Stage3D的关系
首先请阅读两篇文章
GPU大百科全书第二章 凝固生命的光栅化 http://vga.zol.com.cn/236/2366808.html
How Stage3D works http://www.adobe.com/devnet/flashplayer/articles/how-stage3d-works.html
看完这两篇文章以后可以看出,本文其实只是大体上面说明了一下3D渲染的基本过程,但是和目前真正的技术(我参考的资料已经是十几年前的东西了吧)还是有一定出入的,
Stage3D最相关的东西也就是这张图中的
VertexShader 还有 FragmentShader(也就是Pixel shaders)
我在初探Stage3D(二) 了解AGAL中做了一些说明,
VertexShader 主要作用就是3D流程中的前半段操作(对顶点进行一系列的矩阵变换)
FragmentShader 操作是对这些变换后的顶点(及流程中的光栅化部分)进行渲染