multi_compile
在Unity 的Shader 代码中经常看到很多诸如这样的代码
#pragma only_renderers d3d9 d3d11 vulkan glcore gles3 gles metal xboxone ps4 xboxseries playstation switch
#pragma target 2.0 //targetol
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fog
#pragma multi_compile_instancing
#pragma instancing_options renderinglayer
#pragma multi_compile _ DOTS_INSTANCING_ON
Shader变体,如字面意思,就是说,根据一个Shader,衍生多个版本的Shader,这里多个版本是通过定义的变体生成的,那么是怎么生成的呢?
比如编写这样的Shader
Shader "DC/Shader/ShaderLab/MultiCompile"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[KeywordEnum(R,G,B)] _CL("ColorSelect", Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _CL_R _CL_G _CL_B
// 使用 __ 减少一个编译选项,编译选项最多256个
#pragma multi_compile __ DB_ON
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 只有定义DB_ON 时,才会编译
#if DB_ON
return fixed4(1,1,0,1);
#elif _CL_R
return fixed4(1,0,0,1);
#elif _CL_G
return fixed4(0,1,0,1);
#elif _CL_B
return fixed4(0,0,1,1);
#else
fixed4 col = tex2D(_MainTex, i.uv);
return col;
#endif
}
ENDCG
}
}
}
对应的C# 脚本
using UnityEngine;
namespace DC
{
public class MultiCompile : MonoBehaviour
{
public Material mat;
public void OnEnable()
{
mat.EnableKeyword("DB_ON");
// Shader.EnableKeyword("ON");
}
public void OnDisable()
{
mat.DisableKeyword("DB_ON");
// Shader.DisableKeyword("DB_ON");
}
}
}
一般在一个Shader 中会有很多组功能,通过这个语法可以控制自定义启用某部分功能
GPU Instance
这个是GPU 硬件支持的一种特性,使用少量的渲染调用(DrawCall)渲染同一网格的多个副本。也就是说在渲染时,只需要提交一个网格副本,一个材质球,然后在把这些模型对象中不同的属性(比如位置、大小、旋转、颜色等)提取出来放到一个数组中
比如在渲染草、树木的时候可以使用GPU Instance 提升渲染性能!
Shader "XXX" {
Properties {
...
}
SubShader {
...
Pass {
...
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 第一步 设置处理指令
#pragma multi_compile_instancing
struct a2v {
...
// 第二步 输入结构体中添加宏(以便获取对应顶点uv的id值)
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
...
// 第二步 输出结构体中添加宏(以便获取对应顶点uv的id值转化的屏幕坐标id)
UNITY_VERTEX_INPUT_INSTANCE_ID
};
v2f vert(a2v v) {
v2f o;
// 第三步 获取对应顶点uv的id值
UNITY_SETUP_INSTANCE_ID(v);
// 第三步 给输出结构体赋值转化
UNITY_TRANSFER_INSTANCE_ID(v,o);
...
return o;
}
// 把数据输出到指定SV_Target(render target)上
fixed4 frag(v2f uv):SV_Target {
// 第四步 获取对应转化后的instance
UNITY_SETUP_INSTANCE_ID(uv);
...
}
ENDCG
}
}
//设置默认shader
FallBack"Diffuse"
}
URP后处理
《Unity Shader 入门精要》中讲到的后处理是基于内置渲染管线的,在URP 中是不适用的!
BRDF、GI 相关
在一些URP 的Shader 实现中可能看到这样的代码
BRDFData brdfData;
InitializeBRDFData(albedo, 1.0 - 1.0 / kDieletricSpec.a, 0, 0, alpha, brdfData);
half3 color = GlobalIllumination(brdfData, inputData.bakedGI, 1.0, inputData.normalWS, inputData.viewDirectionWS);
可以粗暴的理解为,获取材质的表面BRDF 属性,然后计算全局光照信息得到颜色信息
全局光照(Global Illumination,简称 GI),作为图形学中比较酷的概念之一,是指既考虑场景中来自光源的直接光照,又考虑经过场景中其他物体反射后的间接光照的一种渲染技术
即可以理解为:全局光照 = 直接光照(Direct Light) + 间接光照(Indirect Light)
InitializeBRDFData() 定义在ShaderLibrary/BRDF.hlsl#InitializeBRDFData() 方法
struct BRDFData
{
half3 albedo;
half3 diffuse;
half3 specular;
half reflectivity;
half perceptualRoughness;
half roughness;
half roughness2;
half grazingTerm;
// We save some light invariant BRDF terms so we don't have to recompute
// them in the light loop. Take a look at DirectBRDF function for detailed explaination.
half normalizationTerm; // roughness * 4.0 + 2.0
half roughness2MinusOne; // roughness^2 - 1.0
};
half ReflectivitySpecular(half3 specular)
{
#if defined(SHADER_API_GLES)
return specular.r; // Red channel - because most metals are either monocrhome or with redish/yellowish tint
#else
return Max3(specular.r, specular.g, specular.b);
#endif
}
inline void InitializeBRDFDataDirect(half3 albedo, half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
outBRDFData = (BRDFData)0;
outBRDFData.albedo = albedo;
outBRDFData.diffuse = diffuse;
outBRDFData.specular = specular;
outBRDFData.reflectivity = reflectivity;
outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
outBRDFData.roughness = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT);
outBRDFData.roughness2 = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN);
outBRDFData.grazingTerm = saturate(smoothness + reflectivity);
outBRDFData.normalizationTerm = outBRDFData.roughness * half(4.0) + half(2.0);
outBRDFData.roughness2MinusOne = outBRDFData.roughness2 - half(1.0);
#ifdef _ALPHAPREMULTIPLY_ON
outBRDFData.diffuse *= alpha;
alpha = alpha * oneMinusReflectivity + reflectivity; // NOTE: alpha modified and propagated up.
#endif
}
// Legacy: do not call, will not correctly initialize albedo property.
inline void InitializeBRDFDataDirect(half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
InitializeBRDFDataDirect(half3(0.0, 0.0, 0.0), diffuse, specular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
}
// Initialize BRDFData for material, managing both specular and metallic setup using shader keyword _SPECULAR_SETUP.
inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
#ifdef _SPECULAR_SETUP
half reflectivity = ReflectivitySpecular(specular);
half oneMinusReflectivity = half(1.0) - reflectivity;
half3 brdfDiffuse = albedo * (half3(1.0, 1.0, 1.0) - specular);
half3 brdfSpecular = specular;
#else
half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);
half reflectivity = half(1.0) - oneMinusReflectivity;
half3 brdfDiffuse = albedo * oneMinusReflectivity;
half3 brdfSpecular = lerp(kDieletricSpec.rgb, albedo, metallic);
#endif
InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
}
inline void InitializeBRDFData(inout SurfaceData surfaceData, out BRDFData brdfData)
{
InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData);
}
GlobalIllumination() 定义在ShaderLibrary/GlobalIllumination.hlsl#GlobalIllumination() 方法
half3 GlobalIllumination(BRDFData brdfData, BRDFData brdfDataClearCoat, float clearCoatMask,
half3 bakedGI, half occlusion, float3 positionWS,
half3 normalWS, half3 viewDirectionWS)
{
half3 reflectVector = reflect(-viewDirectionWS, normalWS);
half NoV = saturate(dot(normalWS, viewDirectionWS));
half fresnelTerm = Pow4(1.0 - NoV);
half3 indirectDiffuse = bakedGI;
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, positionWS, brdfData.perceptualRoughness, 1.0h);
half3 color = EnvironmentBRDF(brdfData, indirectDiffuse, indirectSpecular, fresnelTerm);
if (IsOnlyAOLightingFeatureEnabled())
{
color = half3(1,1,1); // "Base white" for AO debug lighting mode
}
#if defined(_CLEARCOAT) || defined(_CLEARCOATMAP)
half3 coatIndirectSpecular = GlossyEnvironmentReflection(reflectVector, positionWS, brdfDataClearCoat.perceptualRoughness, 1.0h);
// TODO: "grazing term" causes problems on full roughness
half3 coatColor = EnvironmentBRDFClearCoat(brdfDataClearCoat, clearCoatMask, coatIndirectSpecular, fresnelTerm);
// Blend with base layer using khronos glTF recommended way using NoV
// Smooth surface & "ambiguous" lighting
// NOTE: fresnelTerm (above) is pow4 instead of pow5, but should be ok as blend weight.
half coatFresnel = kDielectricSpec.x + kDielectricSpec.a * fresnelTerm;
return (color * (1.0 - coatFresnel * clearCoatMask) + coatColor) * occlusion;
#else
return color * occlusion;
#endif
}
// Backwards compatiblity
half3 GlobalIllumination(BRDFData brdfData, half3 bakedGI, half occlusion, float3 positionWS, half3 normalWS, half3 viewDirectionWS)
{
const BRDFData noClearCoat = (BRDFData)0;
return GlobalIllumination(brdfData, noClearCoat, 0.0, bakedGI, occlusion, positionWS, normalWS, viewDirectionWS);
}
half3 GlobalIllumination(BRDFData brdfData, BRDFData brdfDataClearCoat, float clearCoatMask,
half3 bakedGI, half occlusion,
half3 normalWS, half3 viewDirectionWS)
{
half3 reflectVector = reflect(-viewDirectionWS, normalWS);
half NoV = saturate(dot(normalWS, viewDirectionWS));
half fresnelTerm = Pow4(1.0 - NoV);
half3 indirectDiffuse = bakedGI;
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, brdfData.perceptualRoughness, half(1.0));
half3 color = EnvironmentBRDF(brdfData, indirectDiffuse, indirectSpecular, fresnelTerm);
#if defined(_CLEARCOAT) || defined(_CLEARCOATMAP)
half3 coatIndirectSpecular = GlossyEnvironmentReflection(reflectVector, brdfDataClearCoat.perceptualRoughness, half(1.0));
// TODO: "grazing term" causes problems on full roughness
half3 coatColor = EnvironmentBRDFClearCoat(brdfDataClearCoat, clearCoatMask, coatIndirectSpecular, fresnelTerm);
// Blend with base layer using khronos glTF recommended way using NoV
// Smooth surface & "ambiguous" lighting
// NOTE: fresnelTerm (above) is pow4 instead of pow5, but should be ok as blend weight.
half coatFresnel = kDielectricSpec.x + kDielectricSpec.a * fresnelTerm;
return (color * (1.0 - coatFresnel * clearCoatMask) + coatColor) * occlusion;
#else
return color * occlusion;
#endif
}
half3 GlobalIllumination(BRDFData brdfData, half3 bakedGI, half occlusion, half3 normalWS, half3 viewDirectionWS)
{
const BRDFData noClearCoat = (BRDFData)0;
return GlobalIllumination(brdfData, noClearCoat, 0.0, bakedGI, occlusion, normalWS, viewDirectionWS);
}