本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。

http://blog.csdn.net/lzhq1982/article/details/73747162

前两篇介绍了Unity Shader的主要数学部分,书上还有些相关的数学介绍,将在这篇做最后的总结。

1、法线变换

法线(normal),也被称为法矢量。游戏中,模型的顶点携带的信息中,法线就是其中一种。我们变换一个模型,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理中计算光照等。

从上一篇我们知道,点和大部分方向矢量都可以用同一个变换矩阵在两个空间之间变换。但法线用同一个变换矩阵,可能无法确保维持法线的垂直性。下面介绍一下原因。

先来了解一下另一种方向矢量:切线(tangent),也叫切矢量。也是顶点携带的一种信息。它与法线方向垂直。切线是两个点之间的差值计算得来的,因此可以直接用变换顶点的变换矩阵来变换切线。假设,这个变换顶点也就是变换切线的矩阵是3x3的变换矩阵 (因为是方向矢量,不受平移影响,不用4x4),得到空间变换公式如下:

T表示切线,上面表示切线从空间A到空间B的转换。但如果直接用同一矩阵变换法线,得到的新法线可能就不会与表面垂直了,例如:

那么怎么求法线变换的矩阵呢。答案是用法线和切线垂直的约束公式:。假设我们用矩阵G来变换法线,则有。然后结合,我们得到下面公式:

然后推导可得:

有很多人对第一个等式有疑问,请大家注意第一个等式左边是向量点乘,有个点,等式右边把向量变成了列矩阵,变成了矩阵相乘,中间没点了。其他应该没问题。看最后的等式部分,因为,把T变成列矩阵,所以,所以如果,那么上面的等式就成立了。那我们的结论就是:

如果是正交矩阵,那其逆就是其转置,那么G = ,也就是说我们可以用变换顶点的矩阵变换法线。从上一篇的表格中可以看出,旋转变换是正交矩阵,可以直接用,如果只包含旋转和统一缩放,不包含非统一缩放,则

其他情况,我们就要求的逆转置了。

2、Unity Shader 内置变量(数学)

Unity给我们提供了很多有关变换的内置参数,这些内置变量可以在UnityShaderVariables.cginc文件中找到定义和说明。

1)变换矩阵

注意最后两个,Unity5.5版本中_Object2World已经变成unity_ObjectToWorld,_World2Object也变成了unity_WorldToObject,但由于Unity的向下兼容性,Unity会自动改写它们,不会出错。还有在顶点着色器中,我们往往第一行就会用到UNITY_MATRIX_MVP:mul(UNITY_MATRIX_MVP, v.vertex); 这是把顶点从模型空间转换到裁剪空间,不用我们手动变换空间了,不过这在unity5.6中已经改为:UnityObjectToClipPos(v.vertex); 在UnityShaderUtilities.cginc里,注意5.6以上版本才有这个文件。官方实现如下:

// Tranforms position from object to homogenous space
inline float4 UnityObjectToClipPos(in float3 pos)
{// More efficient than computing M*VP matrix productreturn mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
}

可以看出也是先转到世界空间,再乘以观察和投影矩阵,只不过注释那里很清楚,更高效一些。

2)摄像机和屏幕参数

读者也没有必要记住他们,以后用到了方便查阅就行。用多了就记住了。

3、Cg中矢量和矩阵类型

我在Unity Shader基础里说过Cg,是我们目前主要的着色器编程语言。这里主要说一下Cg中矢量和矩阵的表达方式。Cg中,矩阵是由float3x3、float4x4等关键字定义的,矢量是由float3、float4等关键字定义的,当然,也可以当成是1xn行矩阵或nx1的列矩阵,这取决于运算种类和运算中的位置。如下:

float4 a = float4(1.0, 2.0, 3.0, 4.0);

float4 b = float4(1.0, 2.0, 3.0, 4.0);

点积:float result = dot(a, b);

但在矩阵乘法时,参数位置决定是按行矩阵还是列矩阵进行乘法。Cg中矩阵乘法的函数是mul。

float4 v = float4(1.0, 2.0, 3.0, 4.0);
float4x4 M = float4x4(1.0, 0.0, 0.0, 0.0,0.0, 2.0, 0.0, 0.0,0.0, 0.0, 3.0, 0.0,0.0, 0.0, 0.0, 4.0);
//v当成列矩阵和矩阵M右乘
float4 column_mul = mul(M, v);//v当成行矩阵左乘
float4 row_mul = mul(v, M);//注意:column_mul 不等于 row_mul,而是:
//mul(M, v) = mul(v, tranpose(M));
//mul(v, M) = mul(tranpose(M), v);

从上面可以看出,向量、矩阵的位置会影响结果值。通常在变换顶点时,我们用右乘列矩阵的方式。有时也用左乘,省去矩阵转置的操作。

4、Unity屏幕坐标:ComputeScreenPos/VPOS/WPOS
这块内容是有点超前的,只不过涉及数学计算部分,所以放在这里,请大家记住有这么回事,后面屏幕抓取那里我们会用到ComputeGrabScreenPos,到时候还需要你回来看。好了,进入主题。

在写shader时,我们有时希望获得片元在屏幕上的像素位置。在顶点/片元着色器中,有两种方式获得片元的屏幕坐标。

1)在片元着色器的输入中声明VPOS或WPOS语义(语义以后再讲)。

VPOS是HLSL中对屏幕坐标的语义,WPOS是Cg中对屏幕坐标的语义。两者在Unity Shader中是等价的。我们可以在HLSL/Cg中通过语义的方式定义顶点/片元着色器的默认输入,不用自己定义输入输出的数据结构。如是我们可以在片元着色器中这样写:

fixed4 frag(float4 sp : VPOS) : SV_Target {//用屏幕坐标除以屏幕分辨率_ScreenParams.xy,得到视口空间中的坐标return fixed4(sp.xy/_ScreenParams.xy, 0.0, 1.0);
}

这里是把屏幕坐标转化成颜色值输出了,这是典型的用颜色值验证结果的方法,因为shader没法调试,那用颜色值输出可以直观的验证我们的结论。VPOS/WPOS是一个float4的变量,xy代表了屏幕空间的像素坐标。如果屏幕分辨率是400*300,x的范围是[0.5, 400.5],y的范围是[0.5, 300.5],这里的像素坐标不是整数值,因为OpenGL和DirectX 10以后的版本认为像素中心对应的是0.5。所以sp.xy/_ScreenParams.xy的结果就是(0, 0)到(1, 1),所以左下角是黑色,右上角是黄色,结果如图:

我们用了VPOS/WPOS的xy,那zw呢,在Unity中,它们的z分量范围是[0, 1],摄像机近裁剪面z为0,远裁剪面z为1。w分量取决于投影类型,透视投影w范围是[1/Near, 1/Far],Near和Far对应了Camera组件中设置的近裁剪平面和远裁剪平面距离摄像机的远近。正交投影w值恒为1。

2)通过Unity提供的ComputeScreenPos函数

这个函数在UnityCG.cginc里被定义。直接上代码:

struct vertOut {float4 pos : SV_POSITION;float4 srcPos : TEXCOORD0;
}vertOut vert (appdata_base v) {vertOut o;o.pos = mul (UNITY_MATRIX_MVP, v.vertex);//第一步:把ComputeScreenPos的结果保存在srcPos中o.srcPos = ComputeScreenPos(o.pos);return o;
}fixed4 frag (vertOut i) : SV_Target {//第二步:用srcPos.xy除以srcPos.w得到视口空间中的坐标float2 wcoord = i.srcPos.xy / i.srcPos.w;return fixed4(wcoord, 0.0, 1.0);
}

上面代码的实现效果和第一种效果一致。从上面代码可以看出,我们用了两步获得视口空间的坐标,第一步在顶点着色器中用ComputeScreenPos函数计算的结果存在输出结构体中,第二步在片元着色器中对传过来的值进行了齐次除法得到视口空间的坐标。下面我们分析一下:

上一篇我们看到了如何将裁剪空间中的点映射到屏幕空间中。这里回忆一下,经过齐次除法后,我们把裁剪空间变换到了NDC中,不记得NDC的回头看看,NDC的xy坐标是[-1, 1],而屏幕空间是[0, 1],所以只要经过(x + 1) / 2的操作就可以映射过去了,所以我们得到如下公式:

这里的clip的xy都是裁剪空间的,所以除以w变成NDC下,我们再看一下ComputeScreenPos的实现(unity5.6版本):

inline float4 ComputeNonStereoScreenPos(float4 pos) {float4 o = pos * 0.5f;o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;o.zw = pos.zw;return o;
}inline float4 ComputeScreenPos(float4 pos) {float4 o = ComputeNonStereoScreenPos(pos);
#if defined(UNITY_SINGLE_PASS_STEREO)o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endifreturn o;
}

ComputeScreenPos输入的参数pos是经过MVP变换后的在裁剪空间的顶点坐标。UNITY_SINGLE_PASS_STEREO我们先不考虑,貌似是给vr用的。所以核心部分在ComputeNonStereoScreenPos这里,_ProjectionParams.x默认情况是1(如果使用了一个翻转的投影矩阵的话是-1,很少见)。那这段代码输出值o的各个分量是:
o.x = pos.x / 2 + pos.w / 2;

o.y = pos.y / 2 + pos.w / 2;

o.z = pos.z;

o.w = pos.w;

读者可以看出,o.x和o.y并不是视口空间的坐标,除以pos.w就和上面等式相同了,所以我们看片元着色器中的第二步就是这个操作。但为什么不在顶点着色器里的ComputeScreenPos里直接除却要在片元着色器中除呢,这是因为如果在顶点着色器中除的话,会破坏插值结果。从顶点着色器到片元着色器会有个插值的过程,这点在渲染流水线中说过了。如果我们对x/w,y/w进行插值,结果会不准确。因为投影空间不是线性空间,插值往往是线性的,所以不要在投影空间进行插值。最后我们看输出的zw没变化,还是裁剪空间的zw,所以如果使用透视投影,z范围是[-Near, Far],w范围是[Near, Far](读者忘了可以看上一篇的裁剪空间图)。如果是正交投影,z是[-1, 1],w是1。

最后比书上多说一点是ComputeGrabScreenPos,后面抓取屏幕中会遇到,再过来看看。我们直接看看代码:

inline float4 ComputeGrabScreenPos (float4 pos) {#if UNITY_UV_STARTS_AT_TOPfloat scale = -1.0;#elsefloat scale = 1.0;#endiffloat4 o = pos * 0.5f;o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREOo.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endifo.zw = pos.zw;return o;
}

你会发现除了UNITY_UV_STARTS_AT_TOP这个宏判断,基本没啥变化,这个宏后面会常遇到,OpenGL是左下角为原点,DirectX是左上角为原点,所以如果是左上角为原点,那y要取反,就这点区别。

数学部分的介绍到此结束,但Shader离不开数学运算,书里推荐了扩展阅读,有兴趣的就多多研究吧。

(最后感叹一下女神这个章节的书写,我用三篇分开整理,内容还这么庞大,编辑公式好麻烦啊!!!)

Unity Shader入门精要笔记(五):其他数学相关介绍相关推荐

  1. Unity Shader入门精要学习笔记 - 第14章 非真实感渲染

    Unity Shader入门精要学习笔记 - 第14章 非真实感渲染 本系列为UnityShader入门精要读书笔记总结, 原作者博客链接:http://blog.csdn.net/candycat1 ...

  2. Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

    转自冯乐乐的<Unity Shader入门精要> 通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象. 首先,光线从光源中被发射出来. 然后,光线和场景中的一些物体相交 ...

  3. Unity Shader入门精要学习笔记 - 第7章 基础纹理

    转自 冯乐乐的 <Unity Shader 入门精要> 纹理最初的目的就是使用一张图片来控制模型的外观.使用纹理映射技术,我们可以把一张图"黏"在模型表面,逐纹素地控制 ...

  4. Unity Shader入门精要学习笔记 - 第14章非真实感渲染

    转载自 冯乐乐的 <Unity Shader 入门精要> 尽管游戏渲染一般都是以照相写实主义作为主要目标,但也有许多游戏使用了非真实感渲染(NPR)的方法来渲染游戏画面.非真实感渲染的一个 ...

  5. Games101结合Unity Shader入门精要学习笔记(个人向)

    第四章 3D旋转 绕X轴旋转: 绕Y轴旋转: 绕Z轴旋转: 旋转变换(一)旋转矩阵_csxiaoshui的博客-CSDN博客_旋转矩阵 重点:MVP变换!!! model transformation ...

  6. Unity shader入门精要学习笔记-代码篇6(序列帧动画/滚动背景/流动河流/广告牌/顶点动画的阴影)

    一.序列帧动画 建立一个四边形对着摄像机. 我们需要一张序列帧图像,这里用到8x8的爆炸图. 给四边形上材质和shader,代码如下: Shader "Custom/NewSurfaceSh ...

  7. Unity Shader入门精要笔记(四):矩阵与空间变换

    本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢. http://blog.csdn.net/lzhq1982/article/details/73612170 上一篇我们学习 ...

  8. 《Unity Shader入门精要》笔记02 第1章+第2章

    基础篇 第1章+第2章 --本系列是基于人民邮电出版社<Unity Shader入门精要>(冯乐乐著 )的自学Unity Shader笔记,如果您发现了本文的纰漏,还望不吝指正. 基础篇 ...

  9. 《Unity Shader 入门精要》读书笔记

    <Unity Shader 入门精要>读书笔记 --记录一下自己看书时遇到的一下困惑的地方和自己的一些想法,愿明天的我更加强大 1.要正确获得阴影和光照衰减效果,需要#pragma mul ...

最新文章

  1. Windows 软件推荐2020
  2. Windows Server2008R2 域迁移
  3. jsp里面不能使用${pageContext.request.contextPath}解决方案
  4. 什么是工业级交换机?工业交换机作用有哪些?
  5. web开发者工具,你必须知道的CSS盒模型,架构师必备!
  6. 行!看到抖音上Python程序员晒得工资条,我沉默了......
  7. 深度相机---(4)三种方案对比
  8. 花书+吴恩达深度学习(十一)卷积神经网络 CNN 之池化层
  9. mmh学长的实验器材
  10. paip.mysql fulltext 全文搜索.最佳实践.
  11. QQ输入法新功能设计文档
  12. [张国荣][21CD][1998-2002][APE+CUE][8.00G][115][sqhhj0622#HD2PT]
  13. 导出数据库数据至CSV格式
  14. python re模块的(...),group(),groups()
  15. 电脑只有浏览器不能上网
  16. 图书馆座位预约管理系统毕业设计,图书馆座位管理系统设计与实现,图书馆座位预约系统毕业论文毕设作品参考
  17. snaker工作流审批流程参数详解
  18. node.js+uni计算机毕设项目鲸落图书商城小程序LW(程序+小程序+LW)
  19. vue-cli从2升级到3报错error 404 Not Found: @wry/context@^0.4.0
  20. PHP之支付宝APP支付

热门文章

  1. 蓝魔平板i9s刷机Android,蓝魔i9s从Windows 8.1系统刷回Android系统图文教程
  2. 【MySQL系列】单机热备(主从结构)和双机热备介绍和使用
  3. C# 实现 qq 微信 旺旺 qq群消息群发,同步聊天记录课程
  4. 强制在线带修区间LCM(线段树+质因子状压)
  5. 尝试添加 --skip-broken 来跳过无法安装的软件包 或 --nobest 来不只使用最佳选择的软件包
  6. YJKJ公众号开发流程
  7. 你记得也好,最好你忘掉
  8. 十七.C++网络安全学院之CE扫描和基础
  9. antdesign——layout
  10. Java开发工程师与大数据开发工程师有何区别?