【unity】URP的shader开发中支持多光源,_ADDITIONAL_LIGHTS_VERTEX 和 _ADDITIONAL_LIGHTS 区别

项目里有一个其他同事实现的shader,美术那边希望能支持多个光源, 我一看代码里面, frag 函数里已经实现了
 


				#ifdef _ADDITIONAL_LIGHTS
					uint pixelLightCount = GetAdditionalLightsCount();
					for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
					{
						Light light = GetAdditionalLight(lightIndex, i.posWorld.xyz);
						half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
						lightColor += LightingLambert(attenuatedLightColor, light.direction, normalDirection);
					}
				#endif

代码也加了:

            #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS

材质里加了这个keyword还是没起作用,   若宏控制注了有效。  一开始没搞明白……
想到很可能这个关键字是系统本身控制的, 搜索了一下"_ADDITIONAL_LIGHTS",找到ForwardLights.cs里 确实控制了 _ADDITIONAL_LIGHTS 和 _ADDITIONAL_LIGHTS_VERTEX的开关,且2个只能存在一个(所以 multi_compile 是配置在一起的)

最后发现是 UniversalRenderPipelineAsset 这个自定义配置里Additional Lights的配置:

 perVertex (开 _ADDITIONAL_LIGHTS_VERTEX宏)表示 在顶点着色器 计算时,就取其他光源数据计算 光照值, 这样顶点数少 但像素绘制比较多(片元着色器执行次数更多)的情况,就能节省不少计算。

perPixel (开 _ADDITIONAL_LIGHTS) 表示  在片元着色器计算时,对其他光源做采样计算每个光照值 然后叠加。 (光源越多循环次数越多 性能影响也越大。)


一般来说使用 _ADDITIONAL_LIGHTS_VERTEX,可能要优化一些,但是有局限性,平行光还好,针对点光源,面片比较大的模型就很奇怪了,    这次就遇到这个问题,尝试地表接受一个点光源试试发现没作用,还以为代码没写好…………, 最后发现是它面太大了,顶点离点光源太远了 没计算进去……,如果点光跑到顶点附近 则还是有效果的, 但这个面片就整体发亮很奇怪。  

所以一般点光源 直接用lightmap烘培,运行时不要开,除非有特殊需求, 要么就是放到片元着色器里计算其他光。



自己写shader时如何让其支持  _ADDITIONAL_LIGHTS_VERTEX 或 _ADDITIONAL_LIGHTS呢?
可以参考simplelit.shader等 示例
_ADDITIONAL_LIGHTS比较简单
Fragment初始函数里在主光源后加

SimpleLitForwardPass.hlsl 调用的Lighting.hlsl 里的函数, 看 #ifdef _ADDITIONAL_LIGHTS 部分

half4 UniversalFragmentBlinnPhong(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha)
{
    Light mainLight = GetMainLight(inputData.shadowCoord);
    MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));

    half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
    half3 diffuseColor = inputData.bakedGI + LightingLambert(attenuatedLightColor, mainLight.direction, inputData.normalWS);
#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
    half3 specularColor = LightingSpecular(attenuatedLightColor, mainLight.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
#else 
    half3 specularColor = 0;
#endif

#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, inputData.normalWS);
#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
#endif
    }
#endif

#ifdef _ADDITIONAL_LIGHTS_VERTEX
    diffuseColor += inputData.vertexLighting;
#endif

    half3 finalColor = diffuseColor * diffuse + emission;

#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
    finalColor += specularColor;
#endif

    return half4(finalColor, alpha);
}

如果shader用的 BRDFData 数据结构的 类似这样加
 


#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
        color += LightingPhysicallyBased(brdfData, light, inputData.normalWS, inputData.viewDirectionWS);
    }
#endif

#ifdef _ADDITIONAL_LIGHTS_VERTEX
    color += inputData.vertexLighting * brdfData.diffuse;
#endif

我们自己实现的一个shader:
 


				float4 shadowCoord = TransformWorldToShadowCoord(i.posWorld.xyz);
				Light mainLight = GetMainLight(shadowCoord);
				float NDotL = dot(normalDirection, mainLight.direction);
				//float halfLambert = saturate(NDotL * 0.5 + 0.5);
				float Lambert = saturate(NDotL);

				//half3 gi = SampleSH(normalDirection);
				half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
				half3 lightColor = Lambert * attenuatedLightColor;

                half3 bakedGI = SAMPLE_GI(i.lightmapUV, i.vertexSH, normalDirection);
                MixRealtimeAndBakedGI(mainLight, normalDirection, bakedGI, half4(0, 0, 0, 0));
                lightColor += bakedGI;

				#ifdef _ADDITIONAL_LIGHTS
					uint pixelLightCount = GetAdditionalLightsCount();
					for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
					{
						Light light = GetAdditionalLight(lightIndex, i.posWorld.xyz);
						half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
						lightColor += LightingLambert(attenuatedLightColor, light.direction, normalDirection);
					}
				#endif


#ifdef _ADDITIONAL_LIGHTS_VERTEX
                half3 vertexLighting = i.fogFactorAndVertexLight.yzw;
                lightColor += vertexLighting;
#endif

要支持 _ADDITIONAL_LIGHTS_VERTEX , (片元着色器代码 上面有示例了,不贴了)
主要是顶点着色器里的计算 和 怎么传递给片元着色器:
一般需要 结构体 v2f (或者叫 VertexOutput  或者叫 Varyings,反正是自己定义的)

定义一个 
    half4 fogFactorAndVertexLight   : TEXCOORD6; // x: fogFactor, yzw: vertex light
这个是把 unity自带fog的值一起传递了
当然单独加一个也是可以的

#if defined(_ADDITIONAL_LIGHTS_VERTEX) 
    float3 vertexLight                : TEXCOORD7;
#endif

顶点着色器 vert函数里 要加代码
 


#ifdef _ADDITIONAL_LIGHTS_VERTEX
    half3 vertexLight = VertexLighting(vertexInput.positionWS, normalInput.normalWS);
#endif

和fogFactor拼在一起
output.fogFactorAndVertexLight = half4(fogFactor, vertexLight);
传给 片元着色器,   片元着色器计算时 再分别取 它们的值

另一个例子(不和 fogFactor, 单独传递):

#if defined(_ADDITIONAL_LIGHTS_VERTEX) 
    //Pass to fragment shader to apply in Lighting function
    output.vertexLight.rgb = VertexLighting(vertexData.positionWS, vertexData.normalWS);
#endif

以下是Unity Shader使用CG语言实现SAMPLE_DEPTH_TEXTURE的示例代码。 在vertex shader,我们需要将顶点坐标和投影矩阵相乘得到裁剪坐标,然后将裁剪坐标传递给fragment shader。 ``` v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } ``` 在fragment shader,我们首先需要从深度纹理采样得到深度值,然后将其转换为线性深度值。转换方法可以根据具体场景进行调整。接着,我们可以根据深度值计算出该像素的位置,并将该位置作为颜色输出。 ``` half4 frag (v2f i) : SV_Target { float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UNITY_PROJ_COORD(i.vertex)).r); float linearDepth = 2.0 * _ZBufferParams.w / (_ZBufferParams.y + _ZBufferParams.x - depth * (_ZBufferParams.y - _ZBufferParams.x)); float4 clipPos = float4(ComputeGrabScreenPos(i.vertex.xy, linearDepth), linearDepth, 1.0); float4 viewPos = mul(_InvProjMatrix, clipPos); viewPos /= viewPos.w; return float4(viewPos.xyz, 1.0); } ``` 注意,这里我们还需要使用Unity提供的函数Linear01Depth将深度值从非线性转换为线性。同时,我们需要使用_ZBufferParams和_InvProjMatrix这两个Unity内置的变量来进行后续计算。 最后,在主程序,我们需要将深度纹理绑定到对应的纹理单元,并将投影矩阵和_ZBufferParams传递给shader。 ``` Shader "Custom/DepthShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _CameraDepthTexture; float4 _ZBufferParams; float4x4 _InvProjMatrix; struct appdata_base { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } half4 frag (v2f i) : SV_Target { float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UNITY_PROJ_COORD(i.vertex)).r); float linearDepth = 2.0 * _ZBufferParams.w / (_ZBufferParams.y + _ZBufferParams.x - depth * (_ZBufferParams.y - _ZBufferParams.x)); float4 clipPos = float4(ComputeGrabScreenPos(i.vertex.xy, linearDepth), linearDepth, 1.0); float4 viewPos = mul(_InvProjMatrix, clipPos); viewPos /= viewPos.w; return float4(viewPos.xyz, 1.0); } ENDCG } } FallBack "Diffuse" } ``` 注意,我们需要使用Unity内置的宏UNITY_PROJ_COORD来将裁剪坐标转换为纹理坐标。另外,由于_CameraDepthTexture是Unity自动渲染深度纹理的结果,因此无需手动绑定纹理。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值