学习第六章Unity内置函数时,由于之前使用mul矩阵乘法时的顺序与书中不一致,导致使用内置函数时出现光照效果不一样,因此引出以下两个问题:

  1 什么时候使用3x3矩阵,什么时候使用4x4矩阵?

  2 法线变换矩阵与坐标变换矩阵不相同

  解答1:

  4.9.1节书中讲述了何时使用3x3和4x4矩阵。因为4x4矩阵是比3x3矩阵多了平移变换,因此对空间坐标进行变换时,通常使用4x4矩阵。而对于切线和法线这两种空间矢量,不存在平移的情况,因此仅使用3x3矩阵即可(是否可以偷懒使用4x4矩阵?是可以的)。

  解答2:

  而对于法线变换,因为对坐标缩放时,有可能不是等比缩放,这会导致法向量使用坐标变换矩阵后不再垂直于表面。有下述推导过程求得法线变换矩阵:

  假设某平面点的模型空间切线为T,法线为N。通过空间转换M,法线转换G后,切线为T',法线为N'。那么有:

  TT·N = 0 ----- (1),

  (T')T·N' = 0 -----(2),

  T' = M·T -----(3),

  N' = G·N -----(4)。

  由式(2)(3)(4)有:

(M·T)T·(G·N) = 0  ==>  TT·MT·G·N = 0 ==>  (矩阵结合律)  (TT·MT·G)·N = 0  -----(5)

  结合式(1)和(5),得到:T= TT·MT·G,由此得到 MT·G = I,则:

G = (MT)-1 =   (M-1)T

  即法线的变换矩阵,是空间变换矩阵的转置的逆。

  

======================Unity Shader中计算注意事项=======================

1  在Unity Shader中,将坐标从模型空间转换到世界空间,使用如下方式:

  1. v2f o;
  2. o.worldPos = mul(_Object2World, v.vertex).xyz;

  UnityShader中的转换矩阵为行矩阵,模型空间坐标v.vertex作为列矩阵,与转换矩阵右乘,得到世界坐标。

  注意,这里不能使用 o.worldPos = UnityObjectToWorldDir(v.vertex); 在UnityCG.cginc中查看UnityObjectToWorldDir的定义:

  1. // Transforms direction from object to world space
  2. inline float3 UnityObjectToWorldDir( in float3 dir )
  3. {
  4. return normalize(mul((float3x3)_Object2World, dir));
  5. }

  这里使用的是3x3矩阵,使得坐标变换丢失了平移参数。

2  将法线从模型空间转换到世界空间,使用如下方式:

  1. o.worldNormal = mul(v.normal, (float3x3)_World2Object);

  在这里,矩阵_World2Object是矩阵_Object2World的逆矩阵。由于是法向量,因此取3x3矩阵进行计算。

  需要注意的是,这里的mul实现的矩阵乘法,当向量在左侧时,此向量相当于行向量,其中数值是与矩阵的列元素进行乘法与加法。相当于如下写法:

  1. o.worldNormal = mul(transpose((float3x3)_World2Object), v.normal);

  即法线的变换是空间变换_Object2World的逆矩阵的转置transpose(_World2Object)。

  这里Unity Shader封装了一个函数来转换法线到世界坐标UnityObjectToWorldNormal(),替代上面的矩阵乘法写法,不容易出错。

  1. o.worldNormal = UnityObjectToWorldNormal(v.normal);

 

  1. // Transforms normal from object to world space
  2. inline float3 UnityObjectToWorldNormal( in float3 norm )
  3. {
  4. // Multiply by transposed inverse matrix, actually using transpose() generates badly optimized code
  5. return normalize(_World2Object[].xyz * norm.x + _World2Object[].xyz * norm.y + _World2Object[].xyz * norm.z);
  6. }

================书中完整示例===============

  1. Shader "Unity Shaders Book/Chapter 6/Blinn-Phong Build-In Function"
  2. {
  3. Properties
  4. {
  5. _Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0)
  6. _Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
  7. _Gloss("Gloss", Range(8.0, 256.0)) = 20.0
  8. }
  9. SubShader
  10. {
  11. Pass
  12. {
  13. Tags { "LightMode"="ForwardBase" }
  14.  
  15. CGPROGRAM
  16.  
  17. #pragma vertex vert
  18. #pragma fragment frag
  19.  
  20. #include "Lighting.cginc"
  21.  
  22. fixed4 _Diffuse;
  23. fixed4 _Specular;
  24. float _Gloss;
  25.  
  26. struct a2v
  27. {
  28. float4 vertex : POSITION;
  29. float3 normal : NORMAL;
  30. };
  31.  
  32. struct v2f
  33. {
  34. float4 pos : SV_POSITION;
  35. fixed3 worldNormal : TEXCOORD0;
  36. float3 worldPos : TEXCOORD1;
  37. };
  38.  
  39. v2f vert (a2v v)
  40. {
  41. v2f o;
  42. o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  43. o.worldNormal = UnityObjectToWorldNormal(v.normal); // 效果与下一行相同,因为是矢量变换,用3x3变换矩阵即可。
  44. // o.worldNormal = mul(v.normal, (float3x3)_World2Object);
  45. // o.worldPos = UnityObjectToWorldDir(v.vertex); // normalize(mul((float3x3)_Object2World, dir));效果与下一行不同
  46. // o.worldPos = mul(v.vertex, transpose(_Object2World)).xyz; // 将坐标左乘,将模型空间坐标转换到世界空间,与下式相等
  47. o.worldPos = mul(_Object2World, v.vertex).xyz; // 将坐标右乘,将模型空间坐标转换到世界空间
  48. return o;
  49. }
  50.  
  51. fixed4 frag(v2f i) : SV_Target
  52. {
  53. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  54. fixed3 worldNormal = normalize(i.worldNormal);
  55. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
  56. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
  57. fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
  58. fixed3 halfDir = normalize(viewDir + worldLightDir);
  59. fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(, dot(halfDir, worldNormal)) , _Gloss);
  60. fixed3 color = ambient + diffuse + specular;
  61. return fixed4(color, 1.0);
  62. }
  63.  
  64. ENDCG
  65. }
  66. }
  67.  
  68. Fallback "Specular"
  69. }

使用Unity5 build-in的函数实现Blinn-Phong光照模型。

[Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数的更多相关文章

  1. Unity3D -- shader语法内置函数

    该篇是Unity Shader中HLSL的内置函数,主要是一些数学方面的计算函数.在写Shader的时候可以直接使用. abs //计算输入值的绝对值. acos //返回输入值反余弦值. all / ...

  2. Unity3D Shader 内置函数

    Intrinsic Functions (DirectX HLSL) The following table lists the intrinsic functions available in HL ...

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

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

  4. 【Unity Shader学习笔记】Unity基础纹理-法线贴图

    1 高度纹理 使用一张纹理改变物体表面法线,为模型提供更多细节. 有两种主要方法: 1.高度映射:使用一张高度纹理(height map)来模拟表面位移(displacement).得到一个修改后的法 ...

  5. 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...

  6. 第四章 开始Unity Shader学习之旅(2)

    目录 1. 强大的援手:Unity提供的内置文件和变量 1.1 内置的包含文件 1.2 内置的变量 2. Unity提供的Cg/HLSL语义 2.1 什么是语义 2.2 Unity支持的语义 2.3 ...

  7. Unity Shader 入门精要学习 (冯乐乐 著)

    第1篇 基础篇 第1章 欢迎来到Shader的世界 第2章 渲染流水线 第3章 Unity Shader 基础 第4章 学习Shader所需的数学基础 第2篇 初级篇 第5章 开始Unity Shad ...

  8. Unity Shader入门精要学习笔记 - 第9章 更复杂的光照

    转载自 冯乐乐的<Unity Shader入门精要> Unity 的渲染路径 在Unity里,渲染路径决定了光照是如何应该到Unity Shader 中的.因此,如果要和光源打交道,我们需 ...

  9. Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅

    一个顶点/片元 着色器的结构大概如下: Shader "MyShaderName" { Properties { //属性 } SubShader { //针对显卡A的SubSha ...

随机推荐

  1. Alpha 冲刺报告(5/10)

    Alpha 冲刺报告(5/10) 队名:洛基小队 峻雄(组长) 已完成:修改角色的移动脚本 明日计划:完善此项脚本 剩余任务:角色的属性脚本 困难:没有时间,代码的编写时间太慢 ----------- ...

  2. 3、Django下载与简介

    第1节:MVC与MTV模型 1.1 MVC Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一 ...

  3. HTML5 canvas画图

    HTML5 canvas画图 HTML5 <canvas> 标签用于绘制图像(通过脚本,通常是 JavaScript).不过,<canvas> 元素本身并没有绘制能力(它仅仅是 ...

  4. 自定义input[type="checkbox"]的样式

    对复选框自定义样式,我们以前一直用的脚本来实现,不过现在可以使用新的伪类 :checkbox 来实现. 如果直接对复选框设置样式,那么这个伪类并不实用,因为没有多少样式能够对复选框起作用.不过,倒是可 ...

  5. PHP中include和require

    1.include语句 使用include语句可以告诉PHP提取特定的文件,并载入它的全部内容 1 <?php 2 inlude "fileinfo.php"; 3 4 // ...

  6. 【openjudge】【递推】例3.6 过河卒(Noip2002)

    [题目描述] 棋盘上A点有一个过河卒,需要走到目标B点.卒行走的规则:可以向下.或者向右.同时在棋盘上的某一点有一个对方的马(如C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点,如图3-1 ...

  7. c++中内存拷贝函数(C++ memcpy)详解

    原型:void*memcpy(void*dest, const void*src,unsigned int count); 功能:由src所指内存区域复制count个字节到dest所指内存区域. 说明 ...

  8. kubernetes 网络模型

    在Kubernetes模型中,每个Docker主机的docker0网桥都是可以路由的. 那就是说,当一个Pod部署后,集群其他主机能够不在物理主机上做端口映射就可以直接访问Pod. 有了这种说法,从网 ...

  9. 设计一个分布式RPC框架

    0 前言 提前先祝大家春节快乐!好了,先简单聊聊. 我从事的是大数据开发相关的工作,主要负责的是大数据计算这块的内容.最近Hive集群跑任务总是会出现Thrift连接HS2相关问题,研究了解了下内部原 ...

  10. Linux学习笔记(第九章)

    压缩概念: gzip和zcat: 先进版bzip2,bzcat bzip -d  已压缩文档名 bzip -z 需压缩文档名 bzcat 解压文档打印到屏幕 tar:打包指令 注意:压缩最好拿掉根目录 ...