美文网首页视觉效果
《Shader 入门精要》之环境光+漫反射+高光反射

《Shader 入门精要》之环境光+漫反射+高光反射

作者: 烂醉花间dlitf | 来源:发表于2020-10-22 11:34 被阅读0次

    UnityCG.cginc 部分结构体

    struct appdata_base {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        float4 texcoord : TEXCOORD0;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };
    
    struct appdata_tan {
        float4 vertex : POSITION;
        float4 tangent : TANGENT;
        float3 normal : NORMAL;
        float4 texcoord : TEXCOORD0;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };
    
    struct appdata_full {
        float4 vertex : POSITION;
        float4 tangent : TANGENT;
        float3 normal : NORMAL;
        float4 texcoord : TEXCOORD0;
        float4 texcoord1 : TEXCOORD1;
        float4 texcoord2 : TEXCOORD2;
        float4 texcoord3 : TEXCOORD3;
        fixed4 color : COLOR;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };
    

    可视化

    测试

    
    Shader "Unlit/01TestShader"
    {
        Properties
        {
    
        }
        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 color:COLOR0;
                };
    
                v2f vert(appdata_base i) 
                {
                    v2f o;
                    o.position  = UnityObjectToClipPos(i.vertex); // 模型坐标转换到裁剪坐标
                    o.color = fixed3(0.5,1,1); // 每个顶点都是蓝色的
                    return o;
                }
    
                fixed4 frag(v2f i):SV_Target
                {   
                    return fixed4(i.color,1);              
                }
    
                ENDCG
            }
        }
    }
    
    
    运行结果

    UV 可视化

    
    Shader "Unlit/01TestShader"
    {
        Properties
        {
    
        }
        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 color:COLOR0;
                };
    
                v2f vert(appdata_base i) 
                {
                    v2f o;
                    o.position  = UnityObjectToClipPos(i.vertex);
                    o.color = fixed3(i.texcoord.xy,0);
                    return o;
                }
    
                fixed4 frag(v2f i):SV_Target
                {   // r,b 取 uv 坐标,如果超过了 [0,1],那么加蓝调。
                    fixed3 c = frac(i.color); // 取小数部分
                    if(any(saturate(i.color.xy)-i.color.xy)) // saturate,限制在 [0,1] 之间,any,全部为 0 ,返回 false
                    {
                        c.b = 0.5;
                        // return fixed4(0,0,1,1); // 超过了 [0,1],直接变成蓝色。
                    }
                    return fixed4(c,1);              
                }
    
                ENDCG
            }
        }
    }
    
    运行结果

    法线可视化

    
    Shader "Unlit/01TestShader"
    {
        Properties
        {
    
        }
        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 color:COLOR0;
                };
    
                v2f vert(appdata_full  i) 
                {
                    v2f o;
                    o.position  = UnityObjectToClipPos(i.vertex);
                    o.color = i.normal * 0.5 + 0.5; // 把法线映射到 [0,1] 的范围
                    return o;
                }
    
                fixed4 frag(v2f i):SV_Target
                {   
                    return fixed4(i.color,1);
                }
    
                ENDCG
            }
        }
    }
    
    运行效果

    切线可视化

    
    Shader "Unlit/01TestShader"
    {
        Properties
        {
    
        }
        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 color:COLOR0;
                };
    
                v2f vert(appdata_full  i) 
                {
                    v2f o;
                    o.position  = UnityObjectToClipPos(i.vertex);
                    o.color = i.tangent * 0.5 + 0.5; // 把切线映射到 [0,1] 的范围
                    return o;
                }
    
                fixed4 frag(v2f i):SV_Target
                {   
                    return fixed4(i.color,1);
                }
    
                ENDCG
            }
        }
    }
    
    
    运行结果

    副切线可视化

    
    Shader "Unlit/01TestShader"
    {
        Properties
        {
    
        }
        SubShader
        {
            Pass
            {
                Fog { Mode Off }
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
    
               struct appdata {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                };
    
                struct v2f {
                float4 pos : SV_POSITION;
                float3 color : COLOR;
                };
    
                v2f vert(appdata_full  i) 
                {
                    v2f o;
                    o.pos  = UnityObjectToClipPos(i.vertex);
                    float3 bitangent = cross( i.normal, i.tangent.xyz ) * i.tangent.w; // w 为 1 或者 -1,用来确定副切线的方向
                    o.color.xyz = bitangent * 0.5 + 0.5; // 把副切线映射到 [0,1] 的范围
                    // o.color.xyz = fixed3(-1,-1,-1); 
    
                    return o;
                }
    
                fixed4 frag(v2f i):SV_Target
                {   
                    return fixed4(i.color,1);
                }
    
                ENDCG
            }
        }
    }
    
    
    运行结果

    漫反射模型

    逐顶点

    Shader "MyShader/MyDiffuseShader"
    {
        Properties
        {
            _Diffuse("Diffuse",Color)=(1,1,1,1)
        }
        SubShader
        {
            Pass
            {
                Tags{"LightMode" = "ForwardBase"}
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
                fixed4 _Diffuse;
    
                struct appdate
                {
                    float4 position:POSITION;
                    float3 normal:NORMAL;
                };
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 color:COLOR0; 
                };
    
                v2f vert (appdate i)
                {
                    v2f o;
                    o.position = UnityObjectToClipPos(i.position);
    
                    // 环境光
                    fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT;
    
                    // 漫反射光源
                    float3 normal = normalize(mul(i.normal,(float3x3)unity_WorldToObject)); // 计算法线,这种方式避免法线直接通过矩阵变换之后与表面不垂直的情况。
                    float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); // 光照方向
                    o.color = ambient + _LightColor0 * _Diffuse * saturate(dot(normal,lightDirection)); // 颜色 * 就是对应位置相乘
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    return fixed4(i.color,1);
                }
                ENDCG
            }
        }
    }
    
    运行结果

    最后有个颜色相乘,其实就是对应位置相乘,比如说 Diffuse 为 (1,0,0),然后直射光颜色为 (0,1,0)。那么结果就是 (0,0,0),显示出来也就是黑色的。而且不能随着光源的远近有不同亮度的变化,并且应该是被完全遮挡的地方也可能会很亮,比如嘴巴那边,光是从背照过来的。

    逐像素

    Shader "MyShader/MyDiffuseShader"
    {
        Properties
        {
            _Diffuse("Diffuse",Color)=(1,1,1,1)
        }
        SubShader
        {
            Pass
            {
                Tags{"LightMode" = "ForwardBase"}
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
                fixed4 _Diffuse;
    
                struct appdate
                {
                    float4 position:POSITION;
                    float3 normal:NORMAL;
                };
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 normal:COLOR0; 
                };
    
                v2f vert (appdate i)
                {
                    v2f o;
                    o.position = UnityObjectToClipPos(i.position);
                    o.normal = normalize(mul(i.normal,(float3x3)unity_WorldToObject)); // 计算法线
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向
                    fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 计算环境光
                    fixed3 color = ambient + _Diffuse.rgb * lightColor * saturate(dot(i.normal,lightDirection)); // 计算最终的颜色
                    return fixed4(color,1);
                }
                ENDCG
            }
        }
    }
    
    运行结果

    半兰伯特模型

    Shader "MyShader/MyDiffuseShaderHalfLambert"
    {
        Properties
        {
            _Diffuse("Diffuse",Color)=(1,1,1,1)
            _HalfLambert("HalfLambert",float) = 0
        }
        SubShader
        {
            Pass
            {
                Tags{"LightMode" = "ForwardBase"}
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
                fixed4 _Diffuse;
                float _HalfLambert;
    
                struct appdate
                {
                    float4 position:POSITION;
                    float3 normal:NORMAL;
                };
    
                struct v2f
                {
                    float4 position:SV_POSITION;
                    fixed3 normal:COLOR0; 
                };
    
                v2f vert (appdate i)
                {
                    v2f o;
                    o.position = UnityObjectToClipPos(i.position);
                    o.normal = normalize(mul(i.normal,(float3x3)unity_WorldToObject)); // 计算法线
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向
                    fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 计算环境光
                    fixed3 color = ambient + _Diffuse.rgb * lightColor *(_HalfLambert * dot(i.normal,lightDirection) + _HalfLambert); // 使用半兰伯特模型模型
                    return fixed4(color,1);
                }
                ENDCG
            }
        }
    }
    

    半波兰特模型没有物理依据,只是单纯的视觉增强效果,将原来光线方向的单位向量在法线上的投影小于零就使用 0,改成将 [-1,1] 投影到 [0,1] 的范围。

    三种漫反射模型对比

    • 最左:逐顶点,也就是计算每个顶点的值,然后在每个三角面进行插值,好处是顶点少的话,计算量少,坏处是顶点少的话过度比较生硬。
    • 第二个:逐像素,计算每一个像素的颜色,相较于逐顶点,过度会更顺滑,代价是计算量更大一点。但如果模型足够细腻,顶点个数足够多,那从效果和计算量上来说,两者几乎一致。
    • 第三个,两个系数都是 0.5 的半兰伯特模型,背部也能看到颜色了。
    • 第四个,两个系数都是 0.25 的半兰伯特模型,要更亮了一些。


      渲染结果对比

    高光模型

    高光逐顶点

    Shader "Unlit/SpecularVertexLevelMat"
    {
        Properties
        {   
            _DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
            _Spacular("Spacular",Color) = (1,1,1,1)
            _Gloss("Gloss",float) = 0
        }
        SubShader
        {
            Tags { "LightMode"="ForwardBase" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
    
                fixed4 _DiffuseColor;
                fixed4 _Spacular;
                float _Gloss;
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    fixed3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    fixed3 color:COLOR0;
                };
    
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
    
                    // 环境光
                    fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    
                    // 漫反射光
                    float3 normal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 计算法线
                    fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
                    float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向,是从模型出发的,也就是跟场景中看到的光照方向相反
                    fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
    
                    // 计算高光
                    float3 reflectDir = normalize(reflect(-worldLightDir,normal)); // 计算光的反射方向
                    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); // 计算观察的方向
                    fixed3 spacularColor = _Spacular * lightColor * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
                    o.color = ambientColor + diffuseColor + spacularColor;
                    // o.color = reflectDir;
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    return fixed4(i.color,1);
                }
                ENDCG
            }
        }
    }
    
    运行结果

    高光部分会跟随视角的变换改变强度和位置。_Gloss 越大,高光越小。观察点的位置不是摄像机的位置,而是我们在 Scene 人眼的位置。

    高光逐像素

    Shader "Unlit/SpecularVertexLevelMat"
    {
        Properties
        {   
            _DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
            _Spacular("Spacular",Color) = (1,1,1,1)
            _Gloss("Gloss",float) = 0
        }
        SubShader
        {
            Tags { "LightMode"="ForwardBase" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
    
                fixed4 _DiffuseColor;
                fixed4 _Spacular;
                float _Gloss;
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    fixed3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    float3 worldNormal : TEXCOORD0;
                    float3 worldPos : TEXCOORD1;
                };
    
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 计算法线
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    // 环境光
                    fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    
                    // 漫反射光
                    float3 normal = i.worldNormal; // 计算法线
                    fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
                    float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向,是从模型出发的,也就是跟场景中看到的光照方向相反
                    fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
    
                    // 计算高光
                    float3 reflectDir = normalize(reflect(-worldLightDir,normal)); // 计算光的反射方向
                    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos); // 计算观察的方向
                    fixed3 spacularColor = _Spacular * lightColor * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
                    fixed3 color = ambientColor + diffuseColor + spacularColor;
                    return fixed4(color,1);
                }
                ENDCG
            }
        }
    }
    
    
    运行结果
    运行结果

    逐像素 BlinnPhong 模型

    Shader "MyShader/BlinnPhongMat"
    {
        Properties
        {   
            _DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
            _Spacular("Spacular",Color) = (1,1,1,1)
            _Gloss("Gloss",float) = 0
        }
        SubShader
        {
            Tags { "LightMode"="ForwardBase" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
    
                fixed4 _DiffuseColor;
                fixed4 _Spacular;
                float _Gloss;
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    fixed3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    float3 worldNormal : TEXCOORD0;
                    float3 worldPos : TEXCOORD1;
                };
    
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); // 计算法线
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    // 环境光
                    fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    
                    // 漫反射光
                    float3 normal = i.worldNormal; // 计算法线
                    fixed3 lightColor = _LightColor0.rgb; // 光源的颜色
                    float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光照的方向,是从模型出发的,也就是跟场景中看到的光照方向相反
                    fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
    
                    // 使用 BlinnPhone 模型,因为计算简单很多
                    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); // 计算观察的方向
                    float3 h_normal = normalize(viewDir.xyz + worldLightDir.xyz); // 计算视角方向和光源方向的中间方向,用它和法线的夹角来确定光强
                    fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(normal,h_normal)),_Gloss);
                    fixed3 color = ambientColor + diffuseColor + spacularColor;
                    return fixed4(color,1);
                }
                ENDCG
            }
        }
    }
    
    BlinnPhong 模型

    从下面的对比图可以看到相同的 _Gloss ,BlinnPhong 的光圈要更大一点,BlinnPhong 也是一种经验模型,因为可以简化求反射角的计算,而且可能在某些场景更加的真实。

    左:BlinnPhong 右:基本光照模型

    使用 Unity 里面自带的函数

    // File: UnityCG.cginc
    // Transforms direction from object to world space
    inline float3 UnityObjectToWorldDir( in float3 dir )
    {
        return normalize(mul((float3x3)unity_ObjectToWorld, dir));
    }
    
    // Transforms direction from world to object space
    inline float3 UnityWorldToObjectDir( in float3 dir )
    {
        return normalize(mul((float3x3)unity_WorldToObject, dir));
    }
    
    // Transforms normal from object to world space
    inline float3 UnityObjectToWorldNormal( in float3 norm )
    {
    #ifdef UNITY_ASSUME_UNIFORM_SCALING
        return UnityObjectToWorldDir(norm);
    #else
        // mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
        return normalize(mul(norm, (float3x3)unity_WorldToObject));
    #endif
    }
    
    // Computes world space view direction, from object space position
    inline float3 UnityWorldSpaceViewDir( in float3 worldPos )
    {
        return _WorldSpaceCameraPos.xyz - worldPos;
    }
    
    // Computes world space view direction, from object space position
    // *Legacy* Please use UnityWorldSpaceViewDir instead
    inline float3 WorldSpaceViewDir( in float4 localPos ) // 如果这里的 w 为 0,那么跟直接调用 UnityWorldSpaceViewDir 是一样的
    {
        float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
        return UnityWorldSpaceViewDir(worldPos);
    }
    

    修改之后的 BlinnPhone 模型:

    Shader "MyShader/BlinnPhongMat"
    {
        Properties
        {   
            _DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
            _Spacular("Spacular",Color) = (1,1,1,1)
            _Gloss("Gloss",float) = 0
        }
        SubShader
        {
            Tags { "LightMode"="ForwardBase" }
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
                #include "UnityShaderVariables.cginc"
                #include "Lighting.cginc"
    
                fixed4 _DiffuseColor;
                fixed4 _Spacular;
                float _Gloss;
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    fixed3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    float3 worldNormal : TEXCOORD0;
                    float3 worldPos : TEXCOORD1;
                };
    
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.worldNormal = UnityObjectToWorldNormal(v.normal); // 使用 UnityObjectToWorldNormal 计算法线
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    // 环境光
                    fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    
                    // 漫反射光
                    float3 normal = i.worldNormal; 
                    fixed3 lightColor = _LightColor0.rgb; 
                    float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 使用 UnityWorldSpaceLightDir获取光照的方向
                    fixed3 diffuseColor = _DiffuseColor * lightColor * saturate(dot(normal,worldLightDir));
    
                    // 使用 BlinnPhone 模型,因为计算简单很多
                    float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); // 使用 UnityWorldSpaceViewDir获取观察的方向
                    float3 h_normal = normalize(viewDir.xyz + worldLightDir.xyz); 
                    fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(normal,h_normal)),_Gloss);
                    fixed3 color = ambientColor + diffuseColor + spacularColor;
                    return fixed4(color,1);
                }
                ENDCG
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:《Shader 入门精要》之环境光+漫反射+高光反射

        本文链接:https://www.haomeiwen.com/subject/ebnrmktx.html