Unity Shader学习笔记(13)混合光源、光的衰减

发表于2017-12-29
评论0 4.2k浏览
对于混合光源的渲染以及光的衰减可能有些人还没接触过,碰到这类需求可能都有人不知道怎么开始,那本篇文章对混合光源和光的衰减的介绍肯定可以给你答案。

混合光源的前向渲染

一个BasePass处理平行光、四个点光源调用四次Additional Pass,四个点光源的顺序是依靠重要度排序的(光源强度、颜色、距离远近)。注意:只处理了逐像素光照,即光源渲染模式如果为Not Imoportant,则不会对物体产生影响。 
 

使用Phong光照模型。定义了Base Pass 和 Additional Pass来处理多个光源。

Base Pass 代码如下。Unity会把最亮的平行光给BasePass处理,其他平行光交给AdditionalPass处理。

Pass {
    Tags { "LightMode"="ForwardBase" }
    CGPROGRAM
    #pragma multi_compile_fwdbase       // 保证使用光照衰减等光照变量可以正确被赋值
    ...
    fixed4 frag(v2f i) : SV_Target {
        ...
        // 环境光,只在BasePass处理一次够了。
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;  
        // 漫反射,_LightColor0是平行光颜色和强度相乘后的结果。
        fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
        ...
        // 镜面高光。
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
        // 衰减值,平行光为1.0。
        fixed atten = 1.0;
        return fixed4(ambient + (diffuse + specular) * atten, 1.0);
    }
    ...
}

Additional Pass代码如下。处理其他光源。

Pass {
    Tags { "LightMode"="ForwardAdd" }
    // 开启光照混合,与在帧缓存中的光照叠加
    Blend One One                   
    CGPROGRAM
    // 获取正确光照变量
    #pragma multi_compile_fwdadd    
    ...
    fixed4 frag(v2f i) : SV_Target {
        fixed3 worldNormal = normalize(i.worldNormal);
        #ifdef USING_DIRECTIONAL_LIGHT
            // 如果是平行光,直接获取光源方向
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
        #else
            // 点光或聚光,光源方向要依赖视角方向
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);    
        #endif
        ...
        // 漫反射和高光反射
        // 计算衰减值。平行光为1。其他光照较复杂。
        #ifdef USING_DIRECTIONAL_LIGHT
            fixed atten = 1.0;
        #else
            #if defined (POINT)
                float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
                fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
            #elif defined (SPOT)
                float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
                fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
            #else
                fixed atten = 1.0;
            #endif
        #endif
        return fixed4((diffuse + specular) * atten, 1.0);
    }
    ENDCG
}

光照衰减
UNITY_ATTEN_CHANNEL:可以得到衰减纹理中衰减值所在分量。

光照衰减纹理

  需要预处理得到采样纹理,纹理的大小会影响衰减精度。类似渐变纹理,(0, 0)表示与光源重合时的衰减值,(1, 1)为离光源空间最大距离的衰减值。 
  Unity内部使用_LightTexture0的纹理来计算光源衰减。利用_LightMatrix0变换矩阵来计算点在光源空间的位置。

// 计算衰减纹理坐标值
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;
// 根据坐标对纹理采样,用dot点乘就是直接做距离的平方,`.rr`的意思就是取得到rgb值的r作为一个新的二维坐标即(r, r)。
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

数学公式计算衰减

使用的是线性衰减:

flaot distance = length(_WorldSpaceLightPos0.xyz) - i.worldPosition.xyz);
atten = 1.0 / distance;

Unity Shader学习笔记系列教程:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引