【Unity】Unity坐标系变换那些事
说实话,Unity和它的SRP是真的很方便,不仅能相对便捷且自由地修改管线,还能拥有unity整个编辑器(相比自己撸一个框架,unity这点还是很香的)。因此用它快速复现一些图形学算法是很值得推荐的。不过万事开头难,想要享受这份便利,总得踩些坑。其中,unity为了兼容各种API,坐标系搞成了四不像,第一次接触不熟悉可能得翻车。幸好我接触的API比较多,对unity究竟在搞什么名堂有自己的一些浅薄理解,就在此一并梳理了。
令人作呕的坐标系变换
看着这些API的坐标系,我陷入了对图形学和人生的大思考……为什么感觉这些API像是在玩一样,各种排列组合,非得整出各不一样的花活?左手系右手系像是在结印一样变来变去,一会儿翻转y轴,一会儿翻转z轴,NDC z轴范围有的0-1,有的-1到1。
最气的是,unity也要参上一脚,硬生生给组合出第四种来,让本就不富裕的脑细胞雪上加霜。但是静下心来,每个API为什么这么设计还都能分析出点原因来,真让人无语凝噎。
unity整体上的封装思路,是想在上层将各种坐标系(包括uv坐标系)抽象成OpenGL的规则(除了它的世界坐标系是左手系的,与OpenGL相反)。
unity的两套变换矩阵
在开始痛苦的坐标系变换踩坑之前,我们有必要先分清楚unity的两套矩阵:
[*]unity开头的矩阵,对应的是unity这套左右左的坐标系变换规则(如上图所示)
[*]glstate开头的矩阵,对应的是底层真正使用的矩阵,最终会将坐标系变换到特定api对应的NDC。因为无论上层怎么抽象封装,底层使用什么API就得老老实实给它变换到它想要的NDC。
Z轴的朝向
NDC Z轴必定是朝里的,但OpenGL和Vulkan前面几个坐标系是朝外的,所以之后不得不翻转。而unity就比较拧巴了,因为它世界坐标系是跟DirectX3D一样朝里的,摄像机坐标系又要像OpenGL一样朝外,所以unity开头的Z轴得来回折腾。
还有一点需要特别注意,由于float精度在储存 1-Z 时(透视的缘故),分布更加合理,因此反向存Z轴是一个优化。如果优化开启则我们可以通过UNITY_REVERSED_Z得知,最终存储的“新Z轴”是朝外的。因为这是要直接作用到最终深度缓冲里的深度值上的,因此这个翻转是被写到glstate透视矩阵里的。
Y轴的翻转
所有API的世界坐标都是y轴向上,屏幕空间都是y轴向下,因此整个流水线过程中,必然需要经历奇数次的翻转:DirectX3D选择在视口变换翻转,OpenGL选择一路头铁到底让操作系统去翻转,而Vulkan则直接规定NDC向下由用户在之前自己想办法翻转(一般在投影矩阵)
当渲染到纹理时,Unity遵从 OpenGL-like 平台的标准,即uv空间y轴向上。所以在dx和vulkan上,在渲染到纹理的部分情况下,unity会在glstate投影矩阵上偷偷翻转y轴,以让得到的图倒过来,这样采样这张UV空间y轴向下的倒着的纹理,就等价于采样UV空间y轴向上的正着的纹理了。(比较绕,仔细理解一下,其实就是负负得正的意思)
但有一些情况我们并不想被翻转y轴,比如我们想利用光栅化插值uv。那么就得自己在顶点着色器里得将顶点输出的uv自己再倒过来。我们可以通过内置变量_ProjectionParams.x查看unity是否翻转了投影矩阵的y轴,-1代表翻转了。
被坑案例:场景相机投影时翻转了y轴,因为我们自己也翻转了uv,所以不管是uv+1还是uv,采样结果都和翻转前一样,因此求差结果自然也一样。但是如果自己使用硬件ddx和ddy,它自己根据图像方位(右减左,下减上)求差,方向就会相反。
<hr/>如果这篇分析能帮你理清一点关于unity坐标系变换的困惑,本人不胜荣幸^O^。想看到后续更多关于引擎、图形和GPU的相关文章,也欢迎关注我哦~ 坐标系这玩意儿是真的恶心人,不难理解但是各个api不统一,搞得很恶心 unity更恶心的是编辑器面板上的旋转角度和你获取的角度完全不一样 看来大家对unity怨气很深啊[捂脸]
页:
[1]