URP源码学习(四)光照

7 篇文章 2 订阅

光照可以分两部分来看,一个是对光源的处理,主要逻辑在C#代码ForwardLights类,一个是shader的计算,核心是Lighting.hlsl文件。

先看看光源的一些设置

在管线设置

主光

  • 2个选项,关闭,逐像素。
  • 只支持平行光,选择亮度最大的一个,在GetMainLightIndex函数获取,没有返回-1。
  • 可选是否产生阴影以及阴影贴图分辨率。

附加光

  • 3个选项,关闭,逐顶点,逐像素。
  • 逐顶点不能产生阴影。
  • 每个物体受附加光影响有数量限制,PC上最大8个。
  • 选逐像素可以产生阴影并设置阴影贴图分辨率。
  • 点光源和平行光没有实时阴影,聚光灯选逐像素有。

UniversalAdditionalLightData,看名字是设置光源的属性,实际影响的参数只有shadowBias和shadowNormalBias,感觉这名字起的有点误导,不需要手动添加,如果在light里对Bias选择Custom,则会自动添加。


ForwardLights源码理解

这个类的主要作用就是处理光源,提交光照数据给GPU使用

首先定义了一个类LightConstantBuffer,将shader变量的id封装了一下,基本上就是坐标,颜色,强度。

public static int _MainLightPosition; 
public static int _MainLightColor;  
public static int _AdditionalLightsCount; 
public static int _AdditionalLightsPosition; 
public static int _AdditionalLightsColor; 
public static int _AdditionalLightsAttenuation; 
public static int _AdditionalLightsSpotDir;  
public static int _AdditionalLightOcclusionProbeChannel; 

这里有一个StructuredBuffer的概念,在7.2版本的URP没有开启,官方的注释原因是有性能问题,vulkan有绑定的问题。以后可以开启了再细看。

核心函数Setup,作用是设置相关shader变量值,这部分看着代码不少,实际就是根据光源类型,设置不同数值。要注意的是现在不支持shadowmask,后续版本应该会有支持。


shader部分

可以说C#只是设置了一些参数,真正的计算都是在shader完成的,URP实现了一个简化版的PBR,基于Minimalist CookTorrance BRDF,算法详细信息在http://www.thetenthplanet.de/archives/255。下面分成几个部分,看看shader代码是怎么实现的。

shader可以分两部分来看,一个是底层运算库(hlsl文件),一个组合这些底层运算,实现不同效果需求。

先看看输入

输入分成几个文件,依次看一下

SurfaceInput.hlsl

  • 看名字就是保存表面颜色的数据,首先定义了base、bump、emission贴图和采样器,URP定义材质的方法有点改变,要分别定义TEXTURE2D和SAMPLER,具体里边咋实现的也没查着,反正按格式写就行了。
  • SurfaceData结构体,只定义了数值没有贴图。
  • Alpha函数,封装了alpha的计算,如果开启ALPHATEST_ON,执行clip,然后返回alpha值。
  • 接着是封装base、bump、emission3个采样函数。

UnityInput.hlsl

  • 上边一些宏定义都是VR相关的,做手游的就不看了。
  • 时间相关的一些参数,在通过管线设置。
  • 相机世界坐标,_ProjectionParams,_ScreenParams,_ZBufferParams,unity_OrthoParams,有些在不同宏定义下,数据不同。
  • camera相关的矩阵,各种坐标变换矩阵。这部分会在Input.hlsl封装成宏定义。
  • 一些全局值
real4 glstate_lightmodel_ambient; 
real4 unity_AmbientSky; 
real4 unity_AmbientEquator; 
real4 unity_AmbientGround; 
real4 unity_IndirectSpecColor; 
float4 unity_FogParams; 
real4  unity_FogColor; 
real4 unity_ShadowColor; 
  • 运行时用到的贴图声明
// Unity specific
TEXTURECUBE(unity_SpecCube0);
SAMPLER(samplerunity_SpecCube0);

// Main lightmap
TEXTURE2D(unity_Lightmap);
SAMPLER(samplerunity_Lightmap);
// Dual or directional lightmap (always used with unity_Lightmap, so can share sampler)
TEXTURE2D(unity_LightmapInd);

// We can have shadowMask only if we have lightmap, so no sampler
TEXTURE2D(unity_ShadowMask);
  • 其他代码就不贴了,具体用到再回来查。

Input.hlsl

  • 上边是一堆宏定义,作用就是定义最大可见光数量,分为SSBO和UBO,由于硬件限制,SSBO短期应该不能应用在是手机上,所以现在可以简单理解为可见光就是32个,反正就是个数量,以后是会越来越多的 。
  • InputData结构体,这是像素shader用到的数据,要注意的是这并不是顶点shader返回的那个数据,URP用到的几个地方,都是先进行了一下数据的处理,比如LitForwardPass里的InitializeInputData函数。
全局的颜色
half4 _GlossyEnvironmentColor;
half4 _SubtractiveShadowColor;

float4x4 _InvCameraViewProj;
float4 _ScaledScreenParams;
  • 之后定义了主光和附加光源的各种属性,通过ForwardLights设置。

Lighting.hlsl,实现光照计算

如果没定义LIGHTMAP_ON,GI通过球谐函数计算,在顶点处理以减轻shader ALU压力。

PBR的核心函数是UniversalFragmentPBR,下面详细看一下这个函数的流程。

BRDFData brdfData;
InitializeBRDFData(albedo, metallic, specular, smoothness, alpha, brdfData);
  • 首先填充BRDFData结构体,包含brdf需要的参数,漫反射、高光、光滑度等值。
    • InitializeBRDFData这个函数根据工作流,设置高光和漫反射值。
Light mainLight = GetMainLight(inputData.shadowCoord); 
  • 然后通过GetMainLight获取主光的数据
    • 主要是方向和颜色。
    • distanceAttenuation默认是unity_LightData.z,unity_LightData.z值如果被裁剪是1,否则是0,如果开启lightmap或在subtractive模式下需要乘上unity_ProbesOcclusion.x。
    • shadowAttenuation用于计算阴影,默认是1,MainLightRealtimeShadow函数用来计算实时阴影的,具体逻辑在阴影部分细看。
MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));
void MixRealtimeAndBakedGI(inout Light light, half3 normalWS, inout half3 bakedGI, half4 shadowMask)
#if defined(_MIXED_LIGHTING_SUBTRACTIVE) && defined(LIGHTMAP_ON)
    bakedGI = SubtractDirectMainLightFromLightmap(light, normalWS, bakedGI);
#endif
  • 这个函数的作用是计算bakedGI的值,在定义了Subtractive同时开启lightmap时生效,原因是这一个点的颜色在烘焙时已经计算了主光源的光照和阴影值,避免和实时光再叠加一次,选择bake和实时光更暗的那个值作为最终值。
half3 color = GlobalIllumination(brdfData, inputData.bakedGI, occlusion, inputData.normalWS, inputData.viewDirectionWS); 
  • 根据brdfData、烘焙GI值、遮蔽值、法线、观察方向,计算GI,也就是所有的间接光颜色。计算分为以下几个部分:
    • 先计算菲涅尔值。间接反射= bakedGI * occlusion
    • GlossyEnvironmentReflection函数计算间接高光,如果开启了环境反射,就采样unity_SpecCube0,然后处理HDR,返回最终值。unity_SpecCube0保存了环境的立体贴图(天空盒和后面的反射探针形成的立体贴图,都算在这里),这个贴图有mipmap。
    • ps:unity_SpecCube1,存储的离物体最近的反射探针的数据,这里没用到。
    • EnvironmentBRDF:看名字是计算环境的BRDF,具体算法也没看懂,不知道啥原理,可能是个经验算法吧,反正能算出个颜色值就对了。
color += LightingPhysicallyBased(brdfData, mainLight, inputData.normalWS, inputData.viewDirectionWS); 
  • 这个函数是计算主光的brdf值,调用DirectBDRF函数,算法基于Minimalist CookTorrance BRDF。反正BRDF也就是DNF那3个函数,具体细节吧,相信unity就对了,反正我是看不懂~。
for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
{
    Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
    color += LightingPhysicallyBased(brdfData, light, inputData.normalWS, inputData.viewDirectionWS);
}
  • 接着是计算附加光的颜色值,在一个for循环处理,也就是之前说的单pass渲染,不像内置管线那样有forward add pass了。
  • 计算和主光差不多,但是没有间接光的计算,
#ifdef _ADDITIONAL_LIGHTS_VERTEX
    color += inputData.vertexLighting * brdfData.diffuse;
#endif
color += emission;
  • 最后一步是加上顶点光照,和自发光颜色。

到这PBR的光照计算就结束了,有很多细节略过了,以后有更深的理解再补上。

最后一步,LitForwardPass.hlsl

这个文件实现了顶点和像素shader,定义了输入输出结构体,PBR的输入参数大致都差不多,就不贴代码了。

LitPassFragment也比较简单,首先定义一些数据,并初始化,调用UniversalFragmentPBR做光照计算,然后加上雾的颜色,就是最终颜色。


光照相关的代码和shader大致看了一遍,核心内容就是光照计算的流程,URP实现的是一个简化版的计算,这部分项目中实际要改的应该不多,设置好各种宏就可以了,光照算法也不是我这个水平能改的了的,会用就行~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值