[UnityShader入门精要读书笔记]15.渐变纹理

尽管一开始,我们在渲染中使用纹理是为了定义一个物体的颜色,但后来人们发现,纹理其实可以用于存储任何表面属性。一种常见的用法就是使用渐变纹理来控制漫反射的光照的结果。在之前计算漫反射光照时,我们都是使用表面发现和光照方向的点积结果与材质的反射率相乘来得到表面的漫反射光照。但有时,我们需要更加灵活的控制光照结果。这种技术最初由Gooch等人在1998年他们发表的一篇著名的论文中被提出,在这篇论文中,作者提出了一种基于冷到暖色调的着色技术,用来得到一种插画风格的渲染效果。使用这种技术,可以保证物体的轮廓线相比于之前使用的传统漫反射光照更加明显,而且能够提供多种色调变化。[UnityShader入门精要读书笔记]15.渐变纹理

Shader "Ramp/Texture"{

    properties{

        _Color ("Color Tint", Color) = (1,1,1,1)

        _RampTex ("Ramp Tex", 2D) = "white" {}

        _Specular ("Specular", Color) = (1,1,1,1)

        _Gloss ("Gloss", Range(8.0,256)) = 20

    }

    SubShader{

        Pass{

            Tags { "LightMode"="ForwardBase" }

        }

        #pragma vertex vertex

        #pragma fragment frag

        #include "Lighting.cginc"

 

        fixed4 _Color;

        sampler2D _RampTex;

        float4 _RampTex_ST;

        fixed4 _Specular;

        float _Gloss;

 

        struct a2v{

            float4 vertex : POSITION;

            float3 normal : NORMAL;

            float4 texcoord : TEXCOORD0;

        };

 

        struct v2f{

            float4 pos : SV_POSITION;

            float3 worldNormal : TEXCOORD0;

            float3 worldPos : TEXCOORD1;

            float2 uv : TEXCOORD2;

        };

 

        v2f vert(a2v v){

            v2f o;

            o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

            o.worldNormal = UnityObjectToWorldNormal(v.normal);

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

            //使用Unity内置的TRANSFORM_TEX宏计算经过平铺和偏移后的纹理坐标。

            o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);

            return o;

        }

 

        fixed4 frag(v2f i) : SV_Target{

            fixed3 worldNormal = normalize(i.worldNormal);

            fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

 

            fixed halfLambert  = 0.5 * dot(worldNormal,worldLightDir) + 0.5;

            fixed3 diffuseColor = tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb * _Color.rgb;

 

            fixed3 diffuse = _LightColor0.rgb * diffuseColor;

            fixed3 viewDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

            fixed3 halfDir = normalize(worldLightDir + viewDir);

            fixed3 Specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);

 

            return fixed4(ambient + diffuse + Specular, 1.0);

        }

    }

    Fallback "Specular"

}

我们使用了半兰伯特模型,通过对法线方向和光照方向的点积做一次0.5倍的缩放以及一个0.5大小的偏移来计算半兰伯特部分halfLambert。这样,我们得到的halfLambert的范围被映射到了[0,1]之间。之后,我们使用halfLambert来构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样。由于_RampTex实际上就是一个一维纹理(它在纵轴方向上颜色不变),因此纹理坐标的u和v方向我们都是用了halfLambert。然后,把从渐变纹理采样得到的颜色和材质颜色_Color相乘,得到最终的漫反射颜色。剩下的代码就是计算高光反射和环境光,并把他们的结果相加。

需要注意的是,我们需要把渐变纹理的Wrap Mode设为Clamp模式,以防止对纹理进行采样时由于浮点数精度而造成的问题。使用Repeat模式时,当我们使用fixed2(halfLambert,halfLambert)对渐变纹理进行采样时,虽然理论上halfLambert的值在[0,1]之间,但是可能会有1.00001这样的值出现。如果使用Repeat模式,此时就会舍弃整数部分,只保留小数部分。就会出现高光区域有黑点的情况。