Unity PBS

Created by miccall (转载请注明出处 miccall.tech)

unity PBS 分为 metallic 和 specular 两种

一. Metallic workflow :

fixed3 Albedo; // base (diffuse or specular) color
float3 Normal; // tangent space normal, if written
half3 Emission;
half Metallic; // 0 = 0  non-metal ,  1 = 1 metal 

// Smoothness is the user facing name, it should be perceptual smoothness but user should not have to deal with it.

// Everywhere in the code you meet smoothness it is perceptual smoothness

half Smoothness; // 0=rough, 1=smooth
half Occlusion;  // occlusion (default 1)
fixed Alpha;     // alpha for transparencies

在 这个工作流中 ,untiy 又分为 前向渲染和 延时渲染两种 :

LightingStandard
LightingStandard_Deferred

同时,还有一个计算 GI 的

LightingStandard_GI

Albedo的计算 :


    DiffuseAndSpecularFromMetallic

Normal的计算略

Albedo的 alpha 通道计算 :


    PreMultiplyAlpha

最终结果 = UNITY_BRDF_PBS

延迟渲染不同的是 ,我们计算 albedo 之后 ,
我们要把数据发送给Gbuffer


    UnityStandardDataToGbuffer

从中获取计算数据 。

GI的计算 :


    UnityGlobalIllumination

二. Specular workflow


fixed3 Albedo; // diffuse color
fixed3 Specular; // specular color
float3 Normal; // tangent space normal, if written
half3 Emission;
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies

albedo 的计算 :

    EnergyConservationBetweenDiffuseAndSpecular

Normal的计算
Albedo的 alpha 通道计算 :


    PreMultiplyAlpha

最终结果 =
UNITY_BRDF_PBS

其余基本一样

三. 写一个最简单的 PBS shader

最后 ,根据unity的提高 ,写一个最简单的 PBS shader :

首先 ,定义的两个变量 ,选用metallic 工作流 :


        [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
        _Smoothness ("Smoothness", Range(0, 1)) = 0.1

metallic 前面加 【Gamma】,是因为一个细节是金属滑块本身应该处于伽马空间。
但是,在线性空间中渲染时,Unity不会自动对单个值进行伽马校正。
我们可以使用该Gamma属性告诉Unity它还应该对我们的金属滑块应用伽马校正。

其次 ,我们还有一张 Albedo 贴图 ,以及一个颜色


        _Tint ("Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Albedo", 2D) = "white" {}

在vertshader中计算 clip Pos , wordpos ,normal 和 uv ,然后传入 fragmentshader

fragmentshader 中 ,计算 lightdir ,viewdir 。

然后我们先将颜色计算到 albedo


                float3 specularTint;
                float oneMinusReflectivity;

                albedo = DiffuseAndSpecularFromMetallic(
                    albedo, _Metallic, specularTint, oneMinusReflectivity
                );


然后,我们计算 直接光和间接光 :


                UnityLight light;
                light.color = lightColor;
                light.dir = lightDir;
                light.ndotl = DotClamped(i.normal, lightDir);

                UnityIndirect indirectLight;
                indirectLight.diffuse = 0;
                indirectLight.specular = 0;

最后 ,我们用这些信息 ,计算最后的PBS :


                return UNITY_BRDF_PBS(
                    albedo, specularTint,
                    oneMinusReflectivity, _Smoothness,
                    i.normal, viewDir,
                    light, indirectLight
                );

四. 复杂的 PBS shader

灯光判断
光照衰减
阴影投射和接受
顶点光
球谐光
自发光
Normalmap法线
IBL 环境映射

  1. 属性 :

    Properties {
        _Tint ("Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Albedo", 2D) = "white" {}

        [NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
        _BumpScale ("Bump Scale", Float) = 1

        [NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
        [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
        _Smoothness ("Smoothness", Range(0, 1)) = 0.1

        [NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}
        _Emission ("Emission", Color) = (0, 0, 0)

        _DetailTex ("Detail Albedo", 2D) = "gray" {}
        [NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
        _DetailBumpScale ("Detail Bump Scale", Float) = 1
    }


    float4 _Tint;
    sampler2D _MainTex, _DetailTex;
    float4 _MainTex_ST, _DetailTex_ST;

    sampler2D _NormalMap, _DetailNormalMap;
    float _BumpScale, _DetailBumpScale;

    sampler2D _MetallicMap;
    float _Metallic;
    float _Smoothness;

    sampler2D _EmissionMap;
    float3 _Emission;

  1. Interpolators

struct Interpolators {
    float4 pos : SV_POSITION;
    float4 uv : TEXCOORD0;
    float3 normal : TEXCOORD1;

    #if defined( BINORMAL_PER_FRAGMENT )
        float4 tangent : TEXCOORD2;
    #else
        float3 tangent : TEXCOORD2;
        float3 binormal : TEXCOORD3;
    #endif

    float3 worldPos : TEXCOORD4;

    SHADOW_COORDS(5)

    #if defined(VERTEXLIGHT_ON)
        float3 vertexLightColor : TEXCOORD6;
    #endif
};

  1. 直接光

UnityLight CreateLight (Interpolators i) {
    UnityLight light;

    #if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT)
        light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
    #else
        light.dir = _WorldSpaceLightPos0.xyz;
    #endif

    UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);

    light.color = _LightColor0.rgb * attenuation;
    light.ndotl = DotClamped(i.normal, light.dir);
    return light;
}

  1. 顶点光

一个带有四个点光源和六个物体的场景。所有物体都在四个灯的范围内。这需要每个对象五次绘制调用。(一个用于基础传递,加上四个附加传递。)这总共有30次调用 。
要包含Unity支持的所有四个顶点灯,我们必须执行四次相同的顶点灯计算,并将结果一起添加。
我们可以使用UnityCG中Shade4PointLights定义的函数,而不是自己编写所有代码。我们必须给它提供位置矢量,光色,衰减系数,加上顶点位置和法线。


void ComputeVertexLightColor (inout Interpolators i) {
    #if defined(VERTEXLIGHT_ON)
        i.vertexLightColor = Shade4PointLights(
            unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
            unity_LightColor[0].rgb, unity_LightColor[1].rgb,
            unity_LightColor[2].rgb, unity_LightColor[3].rgb,
            unity_4LightAtten0, i.worldPos, i.normal
        );
    #endif
}

  1. 球谐光:
    像顶点灯一样,我们会将球面谐波光数据添加到漫反射间接光中。另外,让我们确保它永远不会产生负面影响。毕竟,这是一个近似值

        #if defined(FORWARD_BASE_PASS)
                indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
        #endif

    // 启用球谐光
    #define FORWARD_BASE_PASS

6.环境映射

    float3  reflectionDir =  reflect(-viewDir,i.normal); 
    float4 envSample = UNITY_SAMPLE_TEXCUBE ( unity_SpecCube0,reflectionDir);
    indirectLight.specular=DecodeHDR(envSample,unity_SpecCube0_HDR);

为了增加模糊度:

         float3   reflectionDir    =     reflect(-viewDir,i.normal);
            Unity_GlossyEnvironmentData     envData ;
            envData.roughness  =  1-_Smoothness;
            envData.reflUVW  =  reflectionDir;
            indirectLight.specular  =   Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE( unity_SpecCube0 )  , unity_SpecCube0_HDR,envData   );

然而

    roughness  =  1-_Smoothness 也不是完全正确的 。
    envData.roughness*=1.7-0.7*envData.roughness;

这个操作在


    Unity_GlossyEnvironment 里面帮我们定义好了 也就不用我们计算了
    BoxProjection:
        float3    BoxProjection( float3  direction,    float3  position,      float4  cubemapPosition,      float3  boxMin,       float3  boxMax
    )
    {
            UNITY_BRANCH
            if(cubemapPosition.w>0){
                    float3  factors=
                    ((direction>0?boxMax:boxMin)-position)/direction;
                    floatscalar=min(min(factors.x,factors.y),factors.z);
                    direction=direction*scalar+(position-cubemapPosition);
            }
            returndirection;
    }


    Unity_GlossyEnvironmentData envData;
    envData.roughness = 1 - _Smoothness;
    envData.reflUVW = BoxProjection(
            reflectionDir, i.worldPos,
            unity_SpecCube0_ProbePosition,
            unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax
    );
    indirectLight.specular = Unity_GlossyEnvironment(
            UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData
    );

  1. 间接光:
UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
    UnityIndirect indirectLight;
    indirectLight.diffuse = 0;
    indirectLight.specular = 0;

    #if defined(VERTEXLIGHT_ON)
        indirectLight.diffuse = i.vertexLightColor;
    #endif

    #if defined(FORWARD_BASE_PASS)
        indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));

        float3 reflectionDir = reflect(-viewDir, i.normal);
        Unity_GlossyEnvironmentData envData;
        envData.roughness = 1 - GetSmoothness(i);
        envData.reflUVW = BoxProjection(
            reflectionDir, i.worldPos,
            unity_SpecCube0_ProbePosition,
            unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax
        );

        float3 probe0 = Unity_GlossyEnvironment(
            UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData
        );

        envData.reflUVW = BoxProjection(
            reflectionDir, i.worldPos,
            unity_SpecCube1_ProbePosition,
            unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax
        );

        #if UNITY_SPECCUBE_BLENDING
            float interpolator = unity_SpecCube0_BoxMin.w;
            UNITY_BRANCH
            if (interpolator < 0.99999) {
                float3 probe1 = Unity_GlossyEnvironment(
                    UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),
                    unity_SpecCube0_HDR, envData
                );
                indirectLight.specular = lerp(probe1, probe0, interpolator);
            }
            else {
                indirectLight.specular = probe0;
            }
        #else
            indirectLight.specular = probe0;
        #endif

    #endif

    return indirectLight;
}


8.获取metallic ,smoothness ,emssion


float GetMetallic (Interpolators i) {
    #if defined(_METALLIC_MAP)
        return tex2D(_MetallicMap, i.uv.xy).r;
    #else
        return _Metallic;
    #endif
}

float GetSmoothness (Interpolators i) {
    float smoothness = 1;
    #if defined(_SMOOTHNESS_ALBEDO)
        smoothness = tex2D(_MainTex, i.uv.xy).a;
    #elif defined(_SMOOTHNESS_METALLIC) && defined(_METALLIC_MAP)
        smoothness = tex2D(_MetallicMap, i.uv.xy).a;
    #endif
    return smoothness * _Smoothness;
}

float3 GetEmission (Interpolators i) {
    #if defined(FORWARD_BASE_PASS)
        #if defined(_EMISSION_MAP)
            return tex2D(_EmissionMap, i.uv.xy) * _Emission;
        #else
            return _Emission;
        #endif
    #else
        return 0 ;
    #endif
}

  1. 顶点和片元着色

Interpolators MyVertexProgram (VertexData v) {
    Interpolators i;
    i.pos = UnityObjectToClipPos(v.vertex);
    i.worldPos = mul(unity_ObjectToWorld, v.vertex);
    i.normal = UnityObjectToWorldNormal(v.normal);

    #if defined( BINORMAL_PER_FRAGMENT )
        i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
    #else
        i.tangent = UnityObjectToWorldDir(v.tangent.xyz);
        i.binormal = CreateBinormal(i.normal, i.tangent, v.tangent.w);
    #endif

    i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
    i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);

    TRANSFER_SHADOW(i);

    ComputeVertexLightColor(i);
    return i;
}


void InitializeFragmentNormal(inout Interpolators i) {
    float3 mainNormal =
        UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
    float3 detailNormal =
        UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
    float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal);

    #if defined(BINORMAL_PER_FRAGMENT)
        float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w);
    #else
        float3 binormal = i.binormal;
    #endif

    i.normal = normalize(
        tangentSpaceNormal.x * i.tangent +
        tangentSpaceNormal.y * binormal +
        tangentSpaceNormal.z * i.normal
    );
}

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
    InitializeFragmentNormal(i);

    float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);

    float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
    albedo *= tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;

    float3 specularTint;
    float oneMinusReflectivity;
    albedo = DiffuseAndSpecularFromMetallic(
        albedo, GetMetallic(i), specularTint, oneMinusReflectivity
    );

    float4 color = UNITY_BRDF_PBS(
        albedo, specularTint,
        oneMinusReflectivity, GetSmoothness(i),
        i.normal, viewDir,
        CreateLight(i), CreateIndirectLight(i, viewDir)
    );
    color.rgb += GetEmission(i);
    return color;
}