Unity Shader学习记录(六)

Unity Shader学习记录(六)

  Shader的基本高光模型能给物体渲染上亮眼的反光效果,但在实际使用中这个效果却并不总是需要那么亮的,尤其是在一些粗糙表面上。当一个物体具有粗糙表面并且使用法线贴图进行渲染后,高光模型制造出来的光照效果会显得十分违和;本来粗糙表面就不应该有高光反射,但同时产生高光的光源又一定会在粗糙表面产生一片更亮的区域。
  为了解决这个矛盾现象,一种可行的方案便是使用遮罩(Mask)。
  遮罩技术是一种很常见,应用十分广泛的技术,尤其是在平面设计领域,遮罩无处不见。而在针对3D渲染中粗糙表面的高光效果这个特定情况时,遮罩技术一般就是指一张遮罩贴图。
  遮罩贴图和法线贴图十分相似,都是用来说明物体表面不同点的光照特性的,但与法线贴图记录法线方向不同的是,遮罩贴图往往记录的是“反光强度”,换言之是一个调节高光强度的系数。


高光遮罩贴图

  高光遮罩贴图虽然单独使用也是没有问题的,但比较常见的还是和法线贴图配合使用,因为高光遮罩本身就记录着物体表面的粗糙情况,如果不与法线贴图配合的话虽然也能达到粗糙表面没有强烈反光的效果,但整体光影的变化会违背“平面”的要求;也就是说即便未使用法线贴图,高光遮罩的应用也会在光影变化中赋予模型表面一点“粗糙度”。
  要使用高光遮罩纹理,首先需要准备一张遮罩纹理图,这种图往往都是高度图,也就是使用灰度来表示某个点的“高度”;在高光遮罩中这个高度就表示最后的反光强度系数。
  因此需要如下三张贴图
  Unity_Shader学习记录_figure_3
  漫反射贴图

  Unity_Shader学习记录_figure_4
  法线贴图

  Unity_Shader学习记录_figure_5
  高光遮罩贴图
  有了这样三张贴图后,Shader代码可以编写如下

Shader "Custom/FinalTestShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpTex ("Bump Map", 2D) = "bump" {}
        _BumpScale ("Bump Scale", Float) = 1.0
        _Specular ("Specular", Color) = (1,1,1,1)
        _SpecularMask ("Specluar Mask", 2D) = "white" {}
        _Gloss ("Gloss", Range(1.0,128)) = 8.0
    }
    SubShader {
        Pass {
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            float4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpTex;
            float4 _BumpTex_ST;
            float _BumpScale;
            float4 _Specular;
            sampler2D _SpecularMask;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float4 TtoW0 : TEXCOORD1;
                float4 TtoW1 : TEXCOORD2;
                float4 TtoW2 : TEXCOORD3;
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpTex);
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET {
                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                float3 unpackNormal = UnpackNormal(tex2D(_BumpTex, i.uv.zw));
                unpackNormal.xy *= _BumpScale;
                unpackNormal.z = sqrt(1.0 - saturate(dot(unpackNormal.xy, unpackNormal.xy)));
                unpackNormal = normalize(half3(dot(i.TtoW0.xyz, unpackNormal), dot(i.TtoW1.xyz, unpackNormal), dot(i.TtoW2.xyz, unpackNormal)));
                fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(unpackNormal, lightDir));
                fixed3 halfDir = normalize(lightDir + viewDir);
                fixed3 specluarMask = tex2D(_SpecularMask, i.uv.xy);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(unpackNormal, halfDir)), _Gloss) * specluarMask;
                return fixed4(ambient + diffuse + specular, 1.0);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

  可以看到,代码中增加了一个纹理采样器,对应高光遮罩贴图;之后在片元计算中进行高光颜色计算时将采样结果纳入了计算公式,由此得到一个新的高光结果。
  这样的Shader应用到渲染中后能看到明显的区别
  Unity_Shader学习记录_figure_1
  应用遮罩前
  Unity_Shader学习记录_figure_2
  应用遮罩后
  粗糙感明显增加了许多,前者更像金属材质而后者更像是岩石或者磨砂之类的材质。


复杂光照——多个光源

  对于以前提到的那些简单Shader而言,光源的数量并不是一个需要考虑的问题,仅作为说明的实例,使用Unity提供的场景平行光和环境光颜色即可。但实际开发过程中,照射在模型表面的光源很多时候都不止一个,关于这些光源如何处理,Unity有自己的规则。
  首先是光源数量和权重,Unity中的光源在创建后默认的RenderMode是Auto,也就是说Unity会自动判断这个光源是否应该参与光照运算,以及该如何参与到运算中。而这个自动判断的标准是可以设置的,通过Unity的项目设置页面,设置“逐像素光源”的数量。这个值默认为4,即表示默认情况下Unity会自动将除了场景中最亮的平行光源之外的四个其它光源当做重要的光源处理。
  为了了解这些复杂光源的处理情况,首先必须要有一些基本概念。

  • 渲染路径
  • 光源类型
  • 光照衰减
  • 渲染模式
  • 阴影投射

  所谓渲染路径是Unity用来决定光照如何应用到Shader中的指示器,我们必须为每个Pass指定渲染路径才能让Unity知道这个Pass的光照该怎么处理,而且也必须设置了渲染路径之后才能在Pass中正确地访问到光照相关的一些数据,才能正确地计算光照。Unity中可以设置的渲染路径包括“前向渲染”,“顶点照明渲染”,“延迟渲染”等。
  而光源类型特指Unity编辑器中对一个光源对象的设置,通过在Unity编辑器中创建光源对象可知,光源类型有四种,分别为“平行光”,“点光”,“聚光灯”和“区域光”。通常而言,区域光仅用于光照烘焙,其它几种光源的几何性质各有不同,比如平行光就是常见的用于模拟太阳光的一种光源。
  光照衰减是光源的属性之一,它描述了空间中某一点的光强与该点到光源距离的数学关系,比如点光源周围,距离越远光照强度越低,这便是它的“光照衰减”参数在其作用,通常来说Unity使用一张“光照衰减纹理”来储存空间中每一点的光强系数,该系数所关联的坐标系是光源坐标系,因此使用时需要进行转化计算;比较特别的是,平行光不会衰减。
  渲染模式也是光源的属性之一,它声明了该光源在Unity渲染阶段应该如何处理,取值可以有“Auto”,“Important”和“NotImportant”三种,第一种是交给Unity判断,而Unity则会根据项目设置中的“逐像素光源数量”设置值,选取场景中除了最亮的平行光之外最重要的四个光源并将它们视为“重要光源”进行处理,其它的则不消耗过多性能,仅会进行逐顶点或者SH光照处理。
  阴影投射往往指代一种物体自身产生的阴影向其他物体投射的情况,这种投射需要考虑多种状态,并且在遇到半透明物体时需要更加仔细和小心。
  了解了这些基本知识后,复杂光照的概念就比较清晰了。
  Unity的前向渲染路径是比较常见的处理光照的渲染路径,它有两种Pass,一种是BasePass,使用的LightMode标签为ForwardBase;另一种是AdditionalPass,使用的LightMode标签为ForwardAdd。通常来说在BassPass中可以处理平行光这种没有衰减而且往往只有一个的光源,而AdditionalPass中可以处理其它类型的光源,因为AdditionalPass的运行次数与光源数量有关,在其中开发者也可以获取到其它光源的信息。
  一个处理多个光源的Shader代码如下

Shader "Custom/MultiLightShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _Specular ("Specular", Color) = (1,1,1,1)
        _Gloss ("Gloss", Range(1.0,128)) = 8.0
    }
    SubShader {
        Pass {
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "Lighting.cginc"

            float4 _Color;
            float4 _Specular;
            float _Gloss;

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

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

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

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

            ENDCG
        }

        Pass {
            Tags {"LightMode"="ForwardAdd"}
            Blend One One // 混合模式一定要打开
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdadd
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            float4 _Color;
            float4 _Specular;
            float _Gloss;

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

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

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal, worldLightDir));
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);
                #ifdef USING_DIRECTIONAL_LIGHT
                fixed atten = 1.0;
                #else
                float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
                fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #endif
                return fixed4((ambient + diffuse + specular) * atten, 1.0);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

  将这个Shader应用给物体并且添加多个光源即可看到效果。
  Shader的核心就在于新增的AdditionalPass,在那里计算了其它光源的效果,由于该Pass也能处理平行光源的情况,因此为了保证结果的正确性使用了预编译指令来区分不同的光源。

#ifdef USING_DIRECTIONAL_LIGHT
    fixed atten = 1.0;
#else
    float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif

  通过预编译指令区别了不同的光源后,其它的计算方法都和BasePass没有什么区别,还有需要注意的地方是AdditionalPass的Blend混合模式要打开,设置为何种模式可以自行定义。
  后面解析关于阴影投射的Shader

猜你喜欢

转载自blog.csdn.net/soul900524/article/details/79608747