[Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数

Posted 珍蜗蜗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数相关的知识,希望对你有一定的参考价值。

  学习第六章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中,将坐标从模型空间转换到世界空间,使用如下方式:

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

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

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

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

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

 

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

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

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

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

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

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

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

o.worldNormal = UnityObjectToWorldNormal(v.normal); 

 

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

 

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

Shader "Unity Shaders Book/Chapter 6/Blinn-Phong Build-In Function"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0)
        _Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
        _Gloss("Gloss", Range(8.0, 256.0)) = 20.0
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };

            v2f vert (a2v v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal); // 效果与下一行相同,因为是矢量变换,用3x3变换矩阵即可。
//                o.worldNormal = mul(v.normal, (float3x3)_World2Object);
//                o.worldPos = UnityObjectToWorldDir(v.vertex); // normalize(mul((float3x3)_Object2World, dir));效果与下一行不同
//                o.worldPos = mul(v.vertex, transpose(_Object2World)).xyz; // 将坐标左乘,将模型空间坐标转换到世界空间,与下式相等
                o.worldPos = mul(_Object2World, v.vertex).xyz; // 将坐标右乘,将模型空间坐标转换到世界空间
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(viewDir + worldLightDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(halfDir, worldNormal)) , _Gloss);
                fixed3 color = ambient + diffuse + specular;
                return fixed4(color, 1.0);
            }

            ENDCG
        }
    }

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

 

 

以上是关于[Unity Shader] 坐标变换与法线变换及Unity5新增加的内置函数的主要内容,如果未能解决你的问题,请参考以下文章

unity shader 学习

Unity Shader入门精要学习笔记 - 第13章 使用深度和法线纹理

Unity Shader 法线贴图的实现

Unity Shader学习笔记坐标变换

[Unity Shader] 切线空间的法线贴图

unity_ObjectToWorld 怎么用