【Unity Shader】从NDC(归一化的设备坐标)坐标转换到世界坐标的数学原理
从NDC(归一化的设备坐标)坐标转换到世界坐标要点
参考资料
How to go from device coordinates back to worldspace http://feepingcreature.github.io/math.html
《Unity Shader入门精要》
前情提要,从运动模糊说起
产生运动模糊效果,一般来说有两种做法,第一种是将当前帧和下一帧或上一帧等等图像混合起来作为当前的屏幕图像,这样做法可以导致物体运动时会出现多个残影(因为多个帧混合起来了),可以产生运动模糊效果,但效率不高。
第二种做法是,在Shader里面利用View-Projection矩阵及其逆矩阵获得当前帧和上一帧的世界坐标,通过这两个坐标得到当前像素的运动速度及其方向,再根据这个速度,向这个像素速度方向的N个纹素进行取样并混合,从而产生模糊效果。
而这第二种做法,就是我遇到问题的地方。
总所周知,在Shader里面,可以根据深度图和当前uv坐标得到当前像素的NDC坐标,那么只要采用View-Projection(视图-裁剪)的逆矩阵就可以将NDC坐标变换到世界坐标下(正是我们所需要的值),按理说,这样应该就可以了,但是我在《Unity Shader入门精要》中看到的计算方法却是下面这样。
1 fixed4 frag(v2f i) : SV_Target{
2 // 从深度图中获得深度
3 float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
4 // 根据深度和当前uv坐标反推当前NDC坐标(注意这个坐标已经经过了齐次除法了)
5 float4 NDCPos = float4(i.uv.x*2-1, i.uv.y*2-1, d*2-1,1);
6 // 根据NDC坐标及View-Projection的逆矩阵,将NDC坐标变换到世界坐标下
7 float4 worldPos = mul(_CurrentViewProjectionInverseMatrix,NDCPos);
8
9 worldPos /= worldPos.w;
....
....
....
}
其中第9行是我最疑惑的内容,按说,你进行屏幕映射要除个w分量进行齐次除法我可以理解,但是你从NDC坐标变换到世界坐标还要除于w是个什么理喔?
百思不得其解之下,翻了下作者的GitHub,嗯,终于找到了答案(感谢作者及评论区的小伙伴!!!)。
在 http://feepingcreature.github.io/math.html 中有详细的数学证明,下面我给翻译一下~~
数学推导过程
已知条件
首先考虑从世界坐标是如何变换到NDC坐标下的。
很显然,首先世界坐标是通过VP矩阵(不用MVP,因为这里已经是世界坐标了,不用在从模型空间变换到世界空剑侠了)变换到了裁剪空间下,数学公式如下:

在裁剪空间下,通过进行齐次除法将坐标变换到NDC坐标中,公式如下:

最后,第三个已知条件是,世界坐标下的w分量在Unity中定义通常都是1,那么就是下面这样:

分析问题
那么,我们的问题就变成了下面这样:
已知:
①
②
③
三个公式,给定一个NDC坐标和一个View-Projection矩阵,求得该NDC坐标所对应的世界坐标.
推导
第一步,反转公式③,如下所示:
④
第二步,根据公式2可得:
⑤
同时,又因为clip=(clipxyz,clipw),所以可以根据公式⑤,可得下面的公式:
⑥
根据公式④和⑥,可得下面的公式:
⑦
下面有个解释不是很懂~,原文如下:
And note that since matrices are linear transforms, we can pull that clip_w in front of the matrix multiply:
大致意思是说,因为矩阵是线性变化的,所以可以把clipw放到矩阵的前面.(老实说clipw不是标量么,放哪里应该都可以把?)
总之,经过上面原文的解释,公式⑦变成下面这样.
⑦
clip的w分量怎么得?
那么,一个比较严重的问题就出现了,clip坐标的w分量我们并不知道欸~~~我们已知的只有NDC坐标下的(X,Y,Z)分量(分别可以通过当前像素的uv值和深度算出来).
别急,已知条件不是还有一个没用么,就下面这个已知条件.
①
已知世界坐标的w分量恒为1,根据公式①、⑦,在只关注运算过程和结果的w分量的情况下,有下面这个公式:
⑧
那么,clip的w分量就经过公式⑧算出来了。
最终结果
那么,已知clipw,根据公式⑧和公式⑦,得到下面的公式⑨。
⑨
总结
根据公式⑨,就可以知道为什么最后我们还要除于w分量了~~~~~因为下面的分母就是w分量啊。。
【Unity Shader】从NDC(归一化的设备坐标)坐标转换到世界坐标的数学原理的更多相关文章
- Unity Shader学习笔记-1
本篇文章是对Unity Shader入门精要的学习笔记,插图大部分来自冯乐乐女神的github 如果有什么说的不正确的请批评指正 目录 渲染流水线 流程图 Shader作用 屏幕映射 三角形遍历 两大 ...
- 【Unity Shader】(六) ------ 复杂的光照(上)
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Sha ...
- 【Unity Shader】(七) ------ 复杂的光照(下)
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Sha ...
- 《Unity shader入门精要》复习<第13章 关于NDC坐标和深度/法线纹理>
分为三个地方讲解. NDC(Normalize Device Coordinates)归一化的设备坐标 NDC坐标是世界空间坐标通过MVP变换之后再进行归一化得到的坐标.只需要再一步变换就能得到屏幕空 ...
- Unity Shader入门精要学习笔记 - 第4章 学习 Shader 所需的数学基础
摘录自 冯乐乐的<Unity Shader入门精要> 笛卡尔坐标系 1)二维笛卡尔坐标系 在游戏制作中,我们使用的数学绝大部分都是计算位置.距离.角度等变量.而这些计算大部分都是在笛卡尔坐 ...
- Unity Shader入门精要学习笔记 - 第2章 渲染流水线
来源作者:candycat http://blog.csdn.net/candycat1992/article/ 2.1 综述 渲染流水线的最终目的在于生成或者说是渲染一张二维纹理,即我们在电脑屏 ...
- Unity Shader入门精要读书笔记(一)序章
本系列的博文是笔者读<Unity Shader入门精要>的读书笔记,这本书的章节框架是: 第一章:着手准备. 第二章:GPU流水线. 第三章:Shader基本语法. 第四章:Shader数 ...
- Unity Shader入门精要学习笔记 - 第13章 使用深度和法线纹理
线纹理的代码非常简单,但是我们有必要在这之前首先了解它们背后的实现原理. 深度纹理实际上就是一张渲染纹理,只不过它里面存储的像素值不是颜色值而是一个高精度的深度值.由于被存储在一张纹理中,深度纹理里的 ...
- 《Unity Shader入门精要》读书笔记(1)
主要是对第二章的整理 渲染流水线:由一个三维场景出发,生成(渲染)一张二维图像. 渲染流程:应用阶段.几何阶段.光栅化阶段. 应用阶段: 1. 把数据加载到显存中 渲染所需数据从硬盘,到内存,再到显存 ...
随机推荐
- python基础学习14----正则表达式
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑. 在python中正则表达式被封 ...
- ES6+转ES5
npm init //创建package.json文件 下载转换babel库及其100+依赖 npm install babel-cli -D npm install babel-preset-env ...
- MySQL生产环境下的主从复制启动项
MySQL的复制参数除了我们之前搭建主从时遇到的那几个之外,还有以下两个: 1.log-slave-updates 这个参数用来配置从库上是否启动了二进制日志的功能,默认是不开启的,如果开启了那么从库 ...
- Linux运维之——每日小技巧,使用awk命令截取每行的指定列数据
获取/etc/passwd目录下的UID值小于10的数,并输出第一.三列 [root@:vg_adn_tidbCkhsTest:172.31.30.62 ~]#cat /etc/passwd | aw ...
- 常用npm 命令
npm 官方网站:npm的使用说明 安装模块 npm install 安装当前目录package.json文件中配置的dependencies模块 安装本地的模块文件 npm install ...
- BZOJ1011:[HNOI2008]遥远的行星(乱搞)
Description 直线上N颗行星,X=i处有行星i,行星J受到行星I的作用力,当且仅当i<=AJ.此时J受到作用力的大小为 Fi->j=Mi*Mj/(j-i) 其中A为很小的常量, ...
- JAVA框架 Spring 调用jdbcsuport简化开发
一)使用DAO的jdbcsuport来简化开发 首先来清楚一个概念: 我们在进行配置文件来进行依赖注入的时候,主要是通过set方法来进行设置的. 正常我们使用spring的jdbctemplate的时 ...
- 蓝桥杯 历届试题 约数倍数选卡片 (经典数论+DFS)
闲暇时,福尔摩斯和华生玩一个游戏: 在N张卡片上写有N个整数.两人轮流拿走一张卡片.要求下一个人拿的数字一定是前一个人拿的数字的约数或倍数.例如,某次福尔摩斯拿走的卡片上写着数字“6”,则接下来华生可 ...
- Convolutional Networks for Images,Speech,and Time-series
Convolutional Networks for Images,Speech,and Time-series Yann LeCun Yoshua Bengio 1995年的 1引言 多层BP网络 ...
- 【转】深入理解C++的动态绑定和静态绑定 & 不要重定义虚函数中的默认参数
为了支持c++的多态性,才用了动态绑定和静态绑定.理解他们的区别有助于更好的理解多态性,以及在编程的过程中避免犯错误.需要理解四个名词:1.对象的静态类型:对象在声明时采用的类型.是在编译期确定的.2 ...