RecursiveFrog 发表于 2022-6-18 19:26

如何在unity的前向渲染路径ForwardBase中同时使用逐顶点 ...

前言:问题来自于《Unity Shader入门精要》中,第195页。书中给出了如何在forwardbase和forwardadd中计算逐像素光照,并没有给出如何同时进行逐顶点的光照,因此当我们把点光源设置为not important时,书中的shader将无法正确渲染此光源。为了解决这个问题我们可以使用9.1.1节中提到的Shade4PointLights函数和一些内置的变量。
<hr/>1.Shade4PointLights()的用法

float3 Shade4PointLights (
    float4 lightPosX, float4 lightPosY, float4 lightPosZ,
    float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
    float4 lightAttenSq,
    float3 pos, float3 normal)
{
    // to light vectors
    float4 toLightX = lightPosX - pos.x;
    float4 toLightY = lightPosY - pos.y;
    float4 toLightZ = lightPosZ - pos.z;
    // squared lengths
    float4 lengthSq = 0;
    lengthSq += toLightX * toLightX;
    lengthSq += toLightY * toLightY;
    lengthSq += toLightZ * toLightZ;
    // don't produce NaNs if some vertex position overlaps with the light
    lengthSq = max(lengthSq, 0.000001);

    // NdotL
    float4 ndotl = 0;
    ndotl += toLightX * normal.x;
    ndotl += toLightY * normal.y;
    ndotl += toLightZ * normal.z;
    // correct NdotL
    float4 corr = rsqrt(lengthSq);
    ndotl = max (float4(0,0,0,0), ndotl * corr);
    // attenuation
    float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
    float4 diff = ndotl * atten;
    // final color
    float3 col = 0;
    col += lightColor0 * diff.x;
    col += lightColor1 * diff.y;
    col += lightColor2 * diff.z;
    col += lightColor3 * diff.w;
    return col;
}以上是Shade4PointLights()函数的完整实现代码,可以看到参数列表有足足10个,这些参数也都是UnityCG.cginc中找到的:
unity_4LightPosX0: float4,Unity内置变量,四个分量分别存储着四个光源位置的X坐标
unity_4LightPosY0: float4,Unity内置变量,四个分量分别存储着四个光源位置的Y坐标
unity_4LightPosZ0: float4,Unity内置变量,四个分量分别存储着四个光源位置的Z坐标
unity_LightColor.rgb: Unity内置变量,储存着第一个非重要光源的颜色
unity_LightColor.rgb: Unity内置变量,储存着第二个非重要光源的颜色
unity_LightColor.rgb: Unity内置变量,储存着第三个非重要光源的颜色
unity_LightColor.rgb: Unity内置变量,储存着第四个非重要光源的颜色
unity_4LightAtten0:float4, Unity内置变量,四个分量分别储存着四个光源的光照衰减因子
pos:顶点世界坐标
normal:顶点法线
关于此函数的更详细内容可以参考https://blog.csdn.net/zengjunjie59/article/details/109654473
2.如何在forwardbase中使用此函数来渲染not important的光源

先给出源码:
Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
        Properties {
                _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
                _Specular ("Specular", Color) = (1, 1, 1, 1)
                _Gloss ("Gloss", Range(8.0, 256)) = 20
        }
        SubShader {
                Tags { "RenderType"="Opaque" }
               
                Pass {
                        // Pass for ambient light & first pixel light (directional light)
                        Tags { "LightMode"="ForwardBase" }

                        //Blend One One
                       
                        CGPROGRAM
                       
                        // Apparently need to add this declaration
                        #pragma multi_compile_fwdbase       
                       
                        #pragma vertex vert
                        #pragma fragment frag
                       
                        #include "Lighting.cginc"
                        #include "UnityCG.cginc"
                       
                        fixed4 _Diffuse;
                        fixed4 _Specular;
                        float _Gloss;
                       
                        struct a2v {
                                float4 vertex : POSITION;
                                float3 normal : NORMAL;
                        };
                       
                        struct v2f {
                                float4 pos : SV_POSITION;
                                fixed4 color : COLOR;
                                float3 worldNormal : TEXCOORD0;
                                float3 worldPos : TEXCOORD1;
                        };
                       
                        v2f vert(a2v v) {
                                v2f o;
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                               
                                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(o.worldPos));
                                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(o.worldPos));

                                //计算逐顶点光照
                                float3 fourPointLightColor = Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor.rgb,
                                        unity_LightColor.rgb, unity_LightColor.rgb, unity_LightColor.rgb, unity_4LightAtten0, o.worldPos, o.worldNormal);

                               
                                o.color = fixed4( fourPointLightColor, 1.0);
                               
                                return o;
                        }
                       
                        fixed4 frag(v2f i) : SV_Target {
                                fixed3 worldNormal = normalize(i.worldNormal);
                                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                               
                                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                               
                               fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

                               fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                               fixed3 halfDir = normalize(worldLightDir + viewDir);
                               fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

                                fixed atten = 1.0;
                               
                                return fixed4(ambient + (diffuse + specular) * atten+i.color, 1.0);
                        }
                       
                        ENDCG
                }
       
                Pass {
                        // Pass for other pixel lights
                        Tags { "LightMode"="ForwardAdd" }
                       
                        Blend One One
               
                        CGPROGRAM
                       
                        // Apparently need to add this declaration
                        #pragma multi_compile_fwdadd
                       
                        #pragma vertex vert
                        #pragma fragment frag
                       
                        #include "Lighting.cginc"
                        #include "AutoLight.cginc"
                       
                        fixed4 _Diffuse;
                        fixed4 _Specular;
                        float _Gloss;
                       
                        struct a2v {
                                float4 vertex : POSITION;
                                float3 normal : NORMAL;
                        };
                       
                        struct v2f {
                                float4 pos : SV_POSITION;
                                float3 worldNormal : TEXCOORD0;
                                float3 worldPos : TEXCOORD1;
                        };
                       
                        v2f vert(a2v v) {
                                v2f o;
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                               
                                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                               
                                return o;
                        }
                       
                        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
                               
                                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
                               
                                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                                fixed3 halfDir = normalize(worldLightDir + viewDir);
                                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
                               
                                #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
                }
        }
        FallBack "Specular"
}
可以对比书中给出的shader,需要修改的部分只有forwardbase的vert而已(注意要在v2f中声明fixed4 color:COLOR):
v2f vert(a2v v) {
                                v2f o;
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                               
                                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(o.worldPos));
                                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(o.worldPos));

                                //计算逐顶点光照
                                float3 fourPointLightColor = Shade4PointLights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor.rgb,
                                        unity_LightColor.rgb, unity_LightColor.rgb, unity_LightColor.rgb, unity_4LightAtten0, o.worldPos, o.worldNormal);

                               
                                o.color = fixed4( fourPointLightColor, 1.0);
                               
                                return o;
                        }
保存shader之后,把光源的Render Mode设置为Not Important,就可以看到效果了:


可以在frameDebugger中看到,只调用了一次draw mesh capsule,4个点光源都在forwardbase这个通道中就完成了渲染:


3.存在的问题

第一个很显然的问题是,这个函数计算出的光照结果只有漫反射效果,也就是说没有specular项,对比一下在additional中逐像素的计算结果就可以清晰的看出来:


<hr/>总结一下:似乎unity现在的版本不太提倡在vert中进行光照计算了,能做的也仅仅是计算点光源的漫反射而已(高光的逐顶点效果实在太差了,内置的模型精度太低)。因此在渲染多光源场景的时候,最好在additional中进行逐像素计算,或者可以使用延迟渲染路径。
如果有错误,欢迎指出,有任何建议的也欢迎进行交流学习,栓Q~
页: [1]
查看完整版本: 如何在unity的前向渲染路径ForwardBase中同时使用逐顶点 ...