zoukankan      html  css  js  c++  java
  • [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光照模型。

  • 相关阅读:
    浅谈算法和数据结构: 二 基本排序算法
    ExecuteScalar()方法的使用
    as和强制类型转换的区别
    duilib入门简明教程 -- 自绘标题栏(5)
    duilib入门简明教程 -- 响应按钮事件(4)
    duilib入门简明教程 -- 第一个程序 Hello World(3)
    duilib入门简明教程 -- VS环境配置(2)
    duilib入门简明教程 -- 前言(1)
    linux 查看 PHP 的默认版本。
    mui
  • 原文地址:https://www.cnblogs.com/p0e0o0p0l0e0/p/7308084.html
Copyright © 2011-2022 走看看