理清渲染流水线2 :模型顶点坐标是如何在多个不同的坐标空间中变换的


       以下内容均基于自己对各类计算图形学及Unity3D引擎相关资料的学习、试验与思考,不能保证各种结论与解释完全正确,如有错误或不同观点欢迎留言讨论。



· 一切的源头(源数据):由三维建模软件产生的模型空间下的顶点坐标

    Unity 约定的是在模型空间下使用左手坐标系(x轴右,z轴前),但在实际使用中模型的自然方向(如人物的脸朝向的方向一般被称作“自然方向中的前方”)并不一定与Unity 约定的前方(即模型空间的z轴方向)一致,因为模型空间的原点和坐标轴通常是由美术资源已经确定好的,而所有模型空间下的顶点坐标都是相对于这个原点(通常是模型重心)来定义的。在Unity Shader的顶点着色器中,模型空间下的顶点信息(主要指顶点坐标和顶点法线)是shader编写者能拿到的最原始的信息之一,而为了实现不同的渲染效果,模型空间下的顶点信息往往需要变化到不同的坐标空间下与另一些变量进行运算,因此一切渲染效果的正确实现首先要基于对顶点信息的正确变换。本文所介绍的是最常见的两种顶点变换方式:

1.将模型空间下的顶点坐标变换到齐次裁剪坐标空间下。这也是顶点着色器必须要完成的一项基本任务

2.将模型空间下的顶点坐标映射到屏幕左边空间下。这一操作让我们知道模型的一个顶到到底对应的是屏幕上的哪个像素



· 最容易理解的坐标空间:世界空间下的顶点坐标

    Unity 的世界坐标空间使用的也是左手坐标系,如果一个GameObject没有任何父节点,那么他的 transform 组件中的 position 表示的就是世界空间下的该物体的模型空间的原点的坐标(为了接下来便于理解,此时也可以把 position 看作这个 GameObject 的模型空间的原点相较于世界空间的原点在xyz三个方向上分别进行了多少距离的位移),而如果一个GameObject有父节点,那么 transform.position 表示的坐标则是这个物体在其父节点的模型空间中的位置,这一规定可以推广到 transform.rotation 和 transform.scale 。

    顶点变换的第一步,首先就要把顶点坐标从模型空间变换到世界空间下,称为模型变换(model transform)。在进行模型变换时,依据世界坐标空间下的Scale ,Rotation 和 Position 可以得出缩放、旋转、平移三个变换矩阵,依次将模型空间下的顶点坐标右乘【#注释0#】缩放矩阵,再将结果右乘旋转矩阵,再将结果右乘平移矩阵,得出世界空间下的顶点坐标。



· 一个容易混淆的坐标空间:观察空间(摄像机空间)下的顶点坐标

    观察空间是一个以摄像机为原点的坐标空间,Unity在观察空间中使用右手坐标系,即z轴的负方向指向摄像机的前方(即摄像机所观察的方向),这种设计是符合OpenGL传统的。

    易混淆的点:观察空间和屏幕空间是不同的,最显著的区别就是观察空间是三维空间,而屏幕空间是二维空间。观察空间下的顶点坐标需要经过投影(projection)来转换到屏幕空间下。

    顶点变换的第二步,是将世界空间下的顶点坐标变换到观察空间下,为了完成这一操作需要计算世界空间到观察空间的变换矩阵,有两种方法来计算注【#注释1#】:1.先算出观察空间到世界空间的变换矩阵,再对此矩阵求逆来得到所需的变换矩阵;2.根据相机的 transform 组件上的信息,还原这个相机相对于世界空间的坐标原点和坐标轴所经历的变换,表示这个还原的变换的过程的矩阵就是我们需要的变换矩阵;

   有了世界空间到观察空间的变换矩阵后,就可以通过世界空间下的顶点坐标右乘此矩阵得到观察空间下的顶点坐标。



· 重要的坐标空间:裁剪空间(齐次裁剪空间)下的顶点坐标

   完全位于裁剪空间内的图元会被保留(即这些顶点的顶点信息会在渲染管线中接着向下传递),完全位于裁剪空间外的图元会被舍弃,而与裁剪空间边界相交的图元会被裁剪。注意,我们之前一直在说顶点信息在不同坐标空间下的变换,但到了裁剪空间中突然变成了对“图元”的操作,顶点与图元的关系可以参见【#注释2#】,此时可以先将一个图元理解为由三个顶点所构成的一个实体三角面片。

    裁剪空间是由视椎体决定的,视椎体是空间中的一块区域,实际上就是 Unity 中 点击了Camera 类的 GameObject 后在场景界面显示出的由六块裁剪平面所围成的一个空间区域,正交摄像机的视椎体的近裁剪平面和远裁剪平面的大小是一致的,即正交摄像机的视椎体是一个标准的长方体;而透视摄像机的近裁剪平面和远裁剪平面的大小不一致,近裁剪平面面积小,远裁剪平面面积大,但是两个裁剪平面的长宽比是相同的,而且如果将侧面的四个裁剪平面向摄像机方向延伸的话,会发现他们相交于摄像机处,形成了一个真正的“椎体”

    但是判断一个图元与一个不规则的锥状空间的空间位置关系需要复杂的计算,因此我们采用一个更具有通用性的方法来判断一个图元是否可以保留或是否需要被裁剪:通过一个裁剪矩阵(也可以叫投影矩阵,但是投影矩阵这个名字容易与屏幕映射中的投影操作混淆)把顶点变换到一个裁剪空间中。

     在这里不讨论这个裁剪矩阵的推导过程和几何含义,只需要知道,这个裁剪矩阵和视椎体的以下数值有密切关系

1. 远/近裁剪平面的高度值(exp:近裁剪平面的高度值 = 2 * 近裁剪平面中心点到摄像机位置的距离 * 二分之一FOV角的正切值 )

2.远/近裁剪平面的宽度值(由于已知裁剪远/近裁剪平面的高度值及其横纵比Aspect,其宽度值很好求得)

     通过以上的数据我们可以构造出两种不同的由观察空间到到裁剪空间的变换矩阵(分别对应透视摄像机和正交摄像机),即之前所说的裁剪矩阵,一个观察空间下的顶点坐标和裁剪矩阵右乘后会得到裁剪空间下的顶点坐标。

   重要:一个观察空间下的顶点坐标在经过裁剪矩阵的变换后,其四个分量会产生这些变化:

    经透视摄像机的裁剪矩阵变换后,顶点的x,y,z分量都进行了不同程度的缩放,z分量还额外做了一个平移,而之前一直为1的w分量(此处涉及以四维向量(齐次坐标)来表示点和矢量与4*4变换矩阵相乘的知识,在此处只需要知道一个点的齐次坐标w分量为1,一个矢量的齐次坐标w分量为0)此时变为了原本的z分量取反的结果

    经正交摄像机的裁剪矩阵变换后,顶点的x,y,z分量都进行了不同程度的缩放,z分量还额外做了一个平移,而之前一直为1的w分量此时仍然是1

    但不管是透视摄像机还是正交摄像机,顶点经由裁剪矩阵变换后我们制定的剔除顶点或裁减图元的标准是统一的,即 “一个顶点此时的xyz分量必须都满足大于等于 -w 且小于等于 w ,任何不满足的图元都要被剔除或裁剪”。这也体现了“通过裁剪矩阵变换顶点来裁剪顶点”这一方法通用性。顺带一提,如果不理解以下这句话也不要紧:在经过裁剪矩阵变换后的透视摄像机视椎体依然是一个玛雅金字塔式的椎体,而正交摄像机的视椎体已经变成了一个立方体。



· 我们终于知道了像素的位置 :从裁剪空间到屏幕空间进行映射

    屏幕空间是一个二维空间(为避免与Unity逻辑层中所说的Screen Space产生歧义,更准确的说法是“在渲染流水线中提到的屏幕空间是二维空间”),想要将裁剪空间下的顶点左边映射到屏幕空间需要两个步骤:

1.透视除法(齐次除法):实际操作就是用w分量去除xyz分量,在OpenGL中我们把这一步得到的坐标称为归一化的设备坐标 Normalized Device Coordinates (NDC)。经过齐次除法后我们会发现所有顶点坐标的xyz分量都被映射到了[-1,1]的范围内(如果我们使用的是DirectX,那么上一步中的裁剪矩阵会有不同的构造方法最终使得DirectX中z分量的范围是[0,1])。承接上文最后一句,经过齐次除法的透视摄像机视椎体现在也变成了一个立方体(即长宽高都为2,坐标为[-1,1]的归一化的设备坐标系),而经过齐次除法的正交摄像机视椎体本身就是立方体了,由于在上一步结束时其w分量是1,因此齐次除法并不会对其xyz分量产生影响(这就是正交摄像机的裁剪矩阵使得顶点坐标的w分量统一为1的意义)。

2.映射屏幕空间对应的像素坐标(屏幕坐标)【#注释3#】:在 Unity 中,屏幕空间左下角的像素坐标是(0,0),右上角的像素坐标是(pixelWidth,pixelHeight)。而当前的顶点坐标的xy分量都在[-1,1]的范围内,所以还有最后进行一次缩放来得到正确的屏幕坐标。

    而顶点坐标的z分量通常会被用于深度缓冲,在片元着色器等后续的流水线工作中继续发挥作用。一般情况下是把z分量除以w分量的值写入深度缓冲区,但根据不同显卡驱动厂商的做法也可能有其他的储存方式。并且一般都不会把w分量在此丢弃,之后w分量也还有重要作用。


【#注释0#】

        此处的的“右乘”是一种约定俗成规则,即我们习惯于把矢量当做列矩阵来参与矩阵运算,但在实际编写Shader的过程中,本篇所提到的一系列操作都可以由Unity内置的UnityObjectToClipPos函数或旧版的MVP矩阵直接得出结果,所以这里的右乘规则就显得不那么重要了

【#注释1#】

       如相机的transform.position为(0,10,-10),表示这个相机相较于世界空间的坐标原点的偏移量

【#注释2#】

        面片、图元、片元...这些概念的具体定义与其定义的界限往往很模糊,需要根据上下文语境来理解,但大体上来说,模型的顶点从外部进入到渲染流水线中除了我们常用的顶点位置顶点颜色等信息其实还带有一些其他的信息,例如顶点排列顺序,渲染引擎通过获取顶点排列顺序可以将多个顶点以一定的规则组装成一个面片(在Unity中这一组装过程是底层渲染引擎自动完成的),这个面片就被称为 “图元(在某些语境中顶点和面片统称为图元) ” 或 “片元”,而通常情况都是三个顶点组成一个三角面片且两个相邻的三角面片共用一条边,这样形成一种网状结构以勾勒出一个模型的外形。而在裁剪操作中,只需要理解裁剪操作的目的和最终结果就足够了,到底裁剪的是顶点还是面片其实并不需要分的那么清楚,因为这两个概念是密切关联的

【#注释3#】

          注意此处的“屏幕空间”这一概念,在Unity引擎中,当涉及到UI组件尤其是编写与UI的位置相关的逻辑时经常会用到 “ScreenPointTo...” 以及 “RectTransformUtility.ScreenPointToLocalPointInRectangle() ” 这两个系列的API,这里的 ScreenPoint 就可以理解为屏幕空间下的坐标,其坐标原点位于屏幕的左下角,坐标值代表横纵方向的像素个数。

          但需要注意的是,Unity中通过 ScreenPoint 相关API获取到的并不是单纯的二维屏幕坐标而是一个携带了深度信息的三维坐标,只不过我们使用相关API的时候往往只关注这个三维坐标中所携带的二维屏幕坐标信息,但是在实现UI显示顺序排序或UI追踪等算法的时候深度信息依然能发挥很大的作用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容