Unity URP Shader 语法细节汇总

| 分类 游戏开发之unity  好好玩游戏  | 标签 游戏  Unity  Shader  着色器  渲染  UPR后处理  BRDF  全局光照  multi_compile  Instance 

multi_compile

《Unity #pragma multi_compile说明》

Unity手册:着色器变体和关键字

在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

《GPUInstance的使用》

这个是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() 方法

全局光照(简述)

【GAMES-104现代游戏引擎】4、引擎渲染基础(渲染基础数据、全局光照、PBR、阴影)

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);
}

参考资料




如果本篇文章对您有所帮助,您可以通过微信(左)或支付宝(右)对作者进行打赏!


上一篇     下一篇