原文链接
原文写的不错,但是格式和代码主题我不太喜欢,所以我转载过来并将一些重点进行标注,并调整了一下格式
正文
前言
接触 Shader 这些东西也有个一两年了,跟着知乎大佬们学到了很多,也是时候写一些自己的东西了,以作为自己的笔记以及参考资料。
新人上路,大家多多关照,本系列着重编码(主要是怕水平不够理论部分讲不清楚),如有写错的地方感谢指出。
本系列在 Unity2020.3,URP10.5 下编写,兼容URP大部分功能,着色与贴图规则参考的是 Unity HDRP,金属流程,即 M_AO_D_SMap
- R:Metallic,
- G:AO,
- B:DetialMask,
- A:Smoothness
整体结构
float4 HDRPLitFragment(InputCoord inputData, half3 albedo, half metallic, half smoothness, half3 normalTS, half3 emission, half occlusion, half alpha)
{
Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, inputData.shadowMask);
// ------------------------------------------------------------- BRDF --------------------------------------------------
BRDF brdfData;
InitializationBRDFData(brdfData, albedo, metallic, smoothness);
DirectLighting directLighting;
PhysicallyBaseDirectLighting(brdfData, mainLight, directLighting, inputData.viewDirectionWS, normalTS);
IndirectLighting indirectLighting;
ImageBaseInDirectLighting(inputData, brdfData, mainLight, indirectLighting);
half3 diffuseLighting = CombineDiffuseLighting(directLighting, indirectLighting);
half3 specularLighting = CombineSpecularLighting(directLighting, indirectLighting);
// ----------------------------------------------------------- End BRDF -------------------------------------------------
// ------------------------------------------------------- Additional Lights --------------------------------------------
PhysicallyBaseDirectAdditionalLighting(inputData, brdfData, diffuseLighting, specularLighting);
// ----------------------------------------------------- End Additional Lights ------------------------------------------
// -------------------------------------------------------------- AO ----------------------------------------------------
ApplyAmbientOcclusion(inputData, occlusion, diffuseLighting, specularLighting);
// ------------------------------------------------------------ End AO --------------------------------------------------
half3 color = diffuseLighting + specularLighting + emission;
return float4(color, alpha);
}
InputCoord
在函数外面计算好,其中的坐标基本都与兼容 Unity 的功能有关,比如烘培,shadowmask,ssao 等。
struct InputCoord
{
float3 positionWS;
float3 viewDirectionWS;
float3 lightDirectionWS;
float3 normalWS;
float3 tangentWS;
float3 bitangentWS;
float4 shadowCoord;
float4 shadowMask;
float2 normalizedScreenSpaceUV;
float2 lightmapUV;
};
BRDF
是自定义的结构体,与 URP 本身的 BRDFData
的差别是增加了 ior
与 fresnel0
,这些数值会在后面起到作用。
struct BRDF
{
half3 diffuse;
half3 specular;
half3 fresnel0;
half3 diffuseReflectivity;
half3 reflectivity;
half perceptualRoughness;
half roughness;
half roughness2;
half3 grazingTerm;
half3 ior;
};
inline void InitializationBRDFData(out BRDF brdfData, half3 albedo, half metallic, half smoothness)
{
brdfData.ior = 1.5;
brdfData.fresnel0 = FRESNEL0;
brdfData.diffuseReflectivity = (1 - brdfData.fresnel0) * (1 - metallic);
brdfData.diffuse = albedo * brdfData.diffuseReflectivity;
brdfData.specular = lerp(brdfData.fresnel0, albedo, metallic);
brdfData.reflectivity = 1 - brdfData.diffuseReflectivity;
brdfData.perceptualRoughness = 1 - smoothness;
brdfData.roughness = brdfData.perceptualRoughness * brdfData.perceptualRoughness;
brdfData.roughness2 = brdfData.roughness * brdfData.roughness;
brdfData.grazingTerm = saturate(smoothness + brdfData.reflectivity);
}
进入 BRDF 的计算
- BRDF 方程镇楼
直接光的 BRDF
我们先计算灯光的阴影及距离衰减等,然后计算 BRDF,这里用的是 BSDF.hlsl 中的结构体,但在此处并 没有考虑 材质的透射,所以 diffT
与 specT
没有用到,计算的结果的 直接光漫反射 部分储存在 directLighting.diffuseLighting
中,直接光高光 储存在 directLighting.specularLighting
。
struct DirectLighting
{
float3 diffuseLighting;
float3 specularLighting;
};
CBSDF EvaluateBRDF(BRDF brdfData, Light light, float3 viewDirectionWS, float3 normalWS)
{
CBSDF cbsdf;
float3 H = normalize(viewDirectionWS + light.direction);
float NdotL = dot(normalWS, light.direction);
float NdotV = dot(normalWS, viewDirectionWS);
float clampNdotL = max(saturate(NdotL), 0.00001);
float clampNdotV = ClampNdotV(NdotV);
float NdotH = saturate(dot(normalWS, H));
float LdotH = dot(light.direction, H);
float LdotV = dot(light.direction, viewDirectionWS);
cbsdf.diffR = DirectionBRDFDiffTerm(brdfData, NdotL, clampNdotV, LdotV) * clampNdotL;
cbsdf.specR = DirectionBRDFSpecTerm(brdfData, NdotL, clampNdotV, NdotH, LdotH) * clampNdotL;
cbsdf.diffT = 0;
cbsdf.specT = 0;
return cbsdf;
}
void PhysicallyBaseDirectLighting(BRDF brdfData, Light light, out DirectLighting directLighting, float3 viewDirectionWS, float3 normalWS)
{
half3 lighting = ComputeLight(light);
CBSDF cbrdf = EvaluateBRDF(brdfData, light, viewDirectionWS, normalWS);
directLighting.diffuseLighting = cbrdf.diffR * brdfData.diffuse * lighting;
directLighting.specularLighting = cbrdf.specR * lighting;
}
直接光漫反射部分
采用了 迪士尼的漫反射项,当然也可以选择 Lambert:
float3 DirectionBRDFDiffTerm(BRDF brdfData, float NdotL, float clampNdotV, float LdotV)
{
#ifdef USE_DIFFUSE_LAMBERT_BRDF
float diffTerm = Lambert();
#else
float diffTerm = DisneyDiffuse(clampNdotV, abs(NdotL), LdotV, brdfData.perceptualRoughness);
#endif
return diffTerm.xxx;
}
直接光高光部分
SmithJointGGX
,源码在 BSDF.hlsl 中,这里用到的 roughness
是 perceptualRoughness^2
,
float3 DirectionBRDFSpecTerm(BRDF brdfData, float NdotL, float clampNdotV, float NdotH, float LdotH)
{
float partLambdaV = GetSmithJointGGXPartLambdaV(clampNdotV, brdfData.roughness);
float3 F = F_Schlick(brdfData.specular, LdotH);
float DV = DV_SmithJointGGX(NdotH, abs(NdotL), clampNdotV, brdfData.roughness, partLambdaV);
return DV * F;
}
间接光的 BRDF
间接光的漫反射部分
兼容 Unity 的 Lighting 面板,球谐函数与 Lightmap:
half3 GetIlluminationDiffuse(InputCoord inputData, Light light)
{
half3 diffuseGI = half3(0, 0, 0);
#ifdef LIGHTMAP_ON
diffuseGI = SampleLightmap(inputData.lightmapUV, inputData.normalWS);
#else
diffuseGI = SampleSH(inputData.normalWS);
#endif
#if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)
diffuseGI = SubtractDirectMainLightFromLightmap(light, inputData.normalWS, diffuseGI);
#endif
return diffuseGI;
}
间接光的镜面反射部分
基于图像的照明,采样一张 LOD 的 CubeMap:
float3 IBL_EnvironmentReflection(float3 viewDirWS, float3 normalWS, float roughness)
{
float3 reflectDirectionWS = reflect(-viewDirWS, normalWS);
float square_roughness = roughness * (1.7 - 0.7 * roughness);
float Midlevel = square_roughness * 6;
float4 specularColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirectionWS, Midlevel);
#if !defined(UNITY_USE_NATIVE_HDR)
return DecodeHDREnvironment(specularColor, unity_SpecCube0_HDR).rgb;
#else
return specularColor.xyz;
#endif
}
环境光照的 BRDF
除此之外,我们还需要环境光照的 BRDF,可以计算拟合曲线,也可以选择采样 BRDF LUT 图,这里我选择的是采样 BRDF LUT,只是这里用到的 LUT 图是这样的:
- HDRP PreIntegratedFGDGGXAndDisneyDiffuse
这张图是从 HDRP 的渲染管线中拿到的,
z
通道中存放着DisneyDiffuse
的影响因子。
TEXTURE2D(_PreIntegratedFGD_GGXDisneyDiffuse);
SAMPLER(s_linear_clamp_sampler);
void GetPreIntegratedFGDGGXAndDisneyDiffuse(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float3 diffuseFGD, out float3 reflectivity)
{
// We want the LUT to contain the entire [0, 1] range, without losing half a texel at each side.
float2 coordLUT = Remap01ToHalfTexelCoord(float2(sqrt(NdotV), perceptualRoughness), FGDTEXTURE_RESOLUTION);
float3 preFGD = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD_GGXDisneyDiffuse, s_linear_clamp_sampler, coordLUT, 0).xyz;
// Pre-integrate GGX FGD
// Integral{BSDF * <N,L> dw} =
// Integral{(F0 + (1 - F0) * (1 - <V,H>)^5) * (BSDF / F) * <N,L> dw} =
// (1 - F0) * Integral{(1 - <V,H>)^5 * (BSDF / F) * <N,L> dw} + F0 * Integral{(BSDF / F) * <N,L> dw}=
// (1 - F0) * x + F0 * y = lerp(x, y, F0)
specularFGD = lerp(preFGD.xxx, preFGD.yyy, fresnel0);
// Pre integrate DisneyDiffuse FGD:
// z = DisneyDiffuse
// Remap from the [0, 1] to the [0.5, 1.5] range.
diffuseFGD = preFGD.z + 0.5;
reflectivity = preFGD.yyy;
}
最终计算结果
struct IndirectIlluminationData
{
float3 diffuseGI;
float3 diffuseFGD;
float3 specularGI;
float3 specularFGD;
};
struct IndirectLighting
{
float3 diffuseLighting;
float3 specularLighting;
};
void ImageBaseInDirectLighting(BRDF brdfData, IndirectIlluminationData giData, out IndirectLighting indirectLighting)
{
indirectLighting.diffuseLighting = giData.diffuseGI * brdfData.diffuse * giData.diffuseFGD;
indirectLighting.specularLighting = giData.specularGI * giData.specularFGD;
}
void ImageBaseInDirectLighting(InputCoord inputData, BRDF brdfData, Light light, out IndirectLighting indirectLighting)
{
float NdotV = max(saturate(dot(inputData.normalWS, inputData.viewDirectionWS)), 0.00001);
IndirectIlluminationData giData;
giData.diffuseGI = GetIlluminationDiffuse(inputData, light);
giData.specularGI = IBL_EnvironmentReflection(inputData.viewDirectionWS, inputData.normalWS, brdfData.perceptualRoughness);
GetPreIntegratedFGDGGXAndDisneyDiffuse(NdotV, brdfData.perceptualRoughness, brdfData.specular, giData.specularFGD, giData.diffuseFGD, brdfData.reflectivity);
ImageBaseInDirectLighting(brdfData, giData, indirectLighting);
}
至此,BRDF 计算完毕了。
Addtional Lighting
接下来是 Addtional Lighting,这里只支持了 逐像素 的附加灯,灯光数量 在 URP 的 Render Asset 上可以设置。
这里将附加灯的 BRDF 也完整的计算了,也可以根据性能需求删减一些计算
void PhysicallyBaseDirectAdditionalLighting(InputCoord inputData, BRDF brdfData, inout half3 diffuse, inout half3 specular)
{
#ifdef _ADDITIONAL_LIGHTS
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, inputData.positionWS, inputData.shadowMask);
DirectLighting additionalLighting;
PhysicallyBaseDirectLighting(brdfData, light, additionalLighting, inputData.viewDirectionWS, inputData.normalWS);
diffuse += additionalLighting.diffuseLighting;
specular += additionalLighting.specularLighting;
}
#endif
}
AO
之后是 AO,将 AO 贴图与 SSAO 进行混合:
void ApplyAmbientOcclusion(InputCoord inputData, half occlusion, inout half3 diffuseLighting, inout half3 specularLighting)
{
#if defined(_SCREEN_SPACE_OCCLUSION)
AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(inputData.normalizedScreenSpaceUV);
diffuseLighting *= aoFactor.directAmbientOcclusion;
specularLighting *= min(occlusion, aoFactor.indirectAmbientOcclusion);
#else
diffuseLighting *= occlusion;
specularLighting *= occlusion;
#endif
}
最终将所有结果相加,可以得到以下效果。
下一章预告
彩虹色(Iridescence)