PBR探索
阅读原文时间:2023年07月12日阅读:1

原理

根据能量守恒,以及一系列光照原理得出微表面BRDF(Bidirectional Reflectance Distribution Function)公式

//     D(h) F(v,h) G(l,v,h)
//f(l,v) = ---------------------------
//       4(n·l)(n·v)

D;微表面法线分布函数,选取ggx近似

//       alpha^2
//D(h) = -----------------------------------
//      pi*((n·h)^2 *(alpha^2-1)+1)^2
其中 alpha = roughness^2

//G(l,v,h) 微表面遮挡函数,使用smith-Schlick近似
// 1
//G(l,v,h) = --------------------------------------------------
// (nl*(1-k)+k)*(nv*(1-k)+k)
//
// k = (a^2 +1) * (a^2 +1)/8; 抄ue4
// k = roughness^2 //u3d

//F(I,h) schlick菲涅尔近似等式
//F(I,h) = F0+(1-F0)(1-nl)^5
//F0高光反射颜色

对应的shader代码

half3 viewDir  = normalize(i.viewDir);

half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

fixed3 normalTex = UnpackNormal(tex2D(_BumpTex, i.uv)).rgb;
half3 wn = calculateWorldNormal(i.normal, i.tangent, normalTex, _normalScale);

half nl = saturate(dot(wn, lightDir));
half nh = saturate(dot(wn, halfDir));
half nv = saturate(dot(wn, viewDir));
half lh = saturate(dot(lightDir ,halfDir));

inline half BRDFspec(half roughness, half nl , half nv , half nh, half3 specColor )
{
half a2 = roughness*roughness*roughness*roughness;
half d = nh*nh*(a2-1)+1;
half D = UNITY_INV_PI*a2/(d*d);

half k = (a2+1)*(a2+1)/8;
half G = 1/((nl*(1-k)+k)*(nv*(1-k)+k));

half F = specColor+(1-specColor)*Pow5(1-nl);
return F*max( 0, D*G*nl/(4*nl*nv));
}

fixed spec = BRDFspec(roughness,  nl, nv ,  nh ,  specColor);

漫反射部分使用Disney(从unity里抄的)

inline half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
// Two schlick fresnel term
half lightScatter = (1 + (fd90 - 1) * Pow5(1 - NdotL));
half viewScatter = (1 + (fd90 - 1) * Pow5(1 - NdotV));

return lightScatter * viewScatter;
}

half diffColor = DisneyDiffuse(nv, nl, lh, roughness)* nl;

half3 brdf= (spec+ diffColor*mainTex.rgb) * metallic*_LightColor * _LightIntensity

仅仅brdf会出现死黑,所以要加上基本色

half3 diffuse = (UNITY_LIGHTMODEL_AMBIENT.xyz+(0.3+0.7*nl)*LIGHT_ATTENUATION(i) * _LightColor.xyz)*mainTex.rgb*(1-metallic)

应美术要求把半兰伯特模型的0.5改成了0.3

最终color = diffuse + brdf

以下是完整代码:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/PBR"
{
Properties
{
_EmissiveColor("自发光颜色",Color) = (,,,)
_EmissiveIntensity("自发光强度",Float) =

    \_LightColor("光照颜色",Color) = (,,,)  
    \_LightIntensity("光照强度",Range(,)) = 

    \_normalScale("法线强度",Float) =  
    \_environment\_rotation("环境光贴图旋转",Range(,)) =  
    \_RotateSpeed("旋转速度", float) =  
    \_Exposure("环境光曝光值",Float) =  
    \_Skincolor ("Skin Color Custom", Color) = (,,,)

    \_MainTex("颜色贴图", 2D) = "white" {}  
    \_BumpTex("法线贴图", 2D) = "bump" {}

    \_ChannelTex("RGB光滑金属变色", 2D) = "white" {}

    \_EmissiveMap("自发光贴图", 2D) = "black" {}

    \_Cube ("环境光贴图", Cube) = "" {}

    \_Metallic("金属度上限",Range(,))=  
    \_MetallicMin("金属度下限",Range(,))=  
    \_Glossiness ("光滑度上限", Range(,)) =  
    \_GlossinessMin ("光滑度下限", Range(,)) =  
    //Rim  
    \_RimColor("轮廓光颜色", Color) = (, , , )  
    \_RimArea("轮廓光范围", Range(, )) = 3.6  
    \_RimPower("轮廓光强度", Range(, )) = 0.0  
}  

SubShader  
{  
    LOD  
    Lighting Off  
    Tags {"RenderType"="Opaque"}

    // Pass to render object as a shadow caster  
    Pass  
    {  
        Name "ShadowCaster"  
        Tags { "LightMode" = "ShadowCaster" }

        ZWrite On ZTest LEqual Cull Off

        CGPROGRAM  
        #pragma vertex vert  
        #pragma fragment frag  
        #pragma multi\_compile\_shadowcaster  
        #include "UnityCG.cginc"

        struct v2f {  
            V2F\_SHADOW\_CASTER;  
        };

        v2f vert( appdata\_base v )  
        {  
            v2f o;  
            TRANSFER\_SHADOW\_CASTER\_NORMALOFFSET(o)  
            return o;  
        }

        float4 frag( v2f i ) : SV\_Target  
        {  
            SHADOW\_CASTER\_FRAGMENT(i)  
        }  
        ENDCG  
    }    

    Pass  
    {  
        CGPROGRAM  
        #pragma vertex   vert  
        #pragma fragment frag  
        #pragma fragmentoption ARB\_precision\_hint\_fastest  
        #include "UnityCG.cginc"  
        #include "AutoLight.cginc"  
        #include "UnityStandardConfig.cginc"  
        #define INTERNAL\_DATA  
        #define WorldReflectionVector(data,normal) data.worldRefl

        struct v2f  
        {  
            half4 pos      : SV\_POSITION;  
            half2 uv       : TEXCOORD0;  
            float3 viewDir  : TEXCOORD1;  
            half3 normal : TEXCOORD4;  
            half4 tangent : TEXCOORD5;  
        };

        sampler2D \_MainTex;  
        sampler2D \_BumpTex;  
        sampler2D \_ChannelTex;  
        sampler2D \_EmissiveMap;  
        samplerCUBE \_Cube;  
        half4 \_Cube\_HDR;

        fixed  \_Metallic;  
        fixed \_MetallicMin;  
        fixed \_Glossiness;  
        fixed \_GlossinessMin;  
        half \_environment\_rotation;  
        half \_RotateSpeed;  
        half \_Exposure;

        half \_normalScale;  
        fixed4 \_LightColor;  
        half \_LightIntensity;

        fixed4 \_DLightColor;  
        half3 \_DLightDir;  
        half \_DLightIntensity;

        half \_EmissiveIntensity;  
        fixed4 \_EmissiveColor;

        fixed4 \_Skincolor;  
        float \_RimPower;  
        fixed4 \_RimColor;  
        float \_RimArea;  
        v2f vert(appdata\_full v)  
        {  
            v2f o;  
            o.pos = UnityObjectToClipPos(v.vertex);  
            o.uv  = v.texcoord;

            o.viewDir = WorldSpaceViewDir(v.vertex);  
            o.normal = v.normal;  
            o.tangent = v.tangent;  
            return o;  
        }

        fixed4 frag(v2f i) : SV\_Target  
        {  
            half3 viewDir  = normalize(i.viewDir);  
            //half3 lightDir = \_DLightDir;  
            half3 lightDir = normalize(\_WorldSpaceLightPos0.xyz);  
            //half3 lightDir = viewDir;  
            fixed4 channel = tex2D(\_ChannelTex, i.uv);

            fixed metallic = \_MetallicMin + channel.g \* ( \_Metallic - \_MetallicMin );  
            fixed glossness = ( \_GlossinessMin + channel.r \* (\_Glossiness-\_GlossinessMin)  )\* .99h;  
            fixed roughness =  - glossness;  
            fixed colorMask = channel.b;

            fixed4 mainTex  = tex2D(\_MainTex,i.uv);  
            mainTex \*= colorMask \* \_Skincolor + ( - colorMask);

            fixed3 normalTex = UnpackNormal(tex2D(\_BumpTex, i.uv)).rgb;  
            half3 wn = calculateWorldNormal(i.normal, i.tangent, normalTex, \_normalScale);

            half3 halfDir = normalize(lightDir + viewDir);

            half nl = saturate(dot(wn, lightDir));  
            half nh = saturate(dot(wn, halfDir));  
            half nv = saturate(dot(wn, viewDir));  
            half lh = saturate(dot(lightDir ,halfDir));

            half3 refDir = reflect(-viewDir, wn);  
            refDir = EnvRotate (\_environment\_rotation + \_Time.y \* \_RotateSpeed \* , refDir);

            float4 c;

            half3 specColor =  lerp (half3(0.04, 0.04, 0.04) , mainTex.rgb , metallic);  
            //DisneyDiffuse(nv, nl, lh, roughness)  
            //c.rgb = DiffuseAndSpecularFromMetallic (mainTex.rgb, metallic, /\*out\*/ specColor);  
            //half lightFalloff = (nl \* 0.5 + 0.5);  
            //half3 diffColor = c.rgb \* lightFalloff;

            half diffColor = DisneyDiffuse(nv, nl, lh, roughness)\* nl;  
            fixed spec = BRDFspec(roughness,  nl, nv ,  nh ,  specColor);

            half mip = roughness \*  ;  
            half3 rgbm = DecodeHDR(texCUBElod(\_Cube, float4(refDir,mip)), \_Cube\_HDR);

            fixed3 refColor = EnvBRDFMobile(specColor, roughness, nv) \* rgbm;  
            refColor = ACESToneMapping(refColor , \_Exposure);  
            refColor += refColor \* metallic;

            fixed emimask = tex2D(\_EmissiveMap, i.uv).r;  
            fixed3 Emissive = emimask \* \_EmissiveColor.rgb \* \_EmissiveIntensity;

            //c.rgb = (refColor + (spec + diffColor) \* \_DLightColor \* \_DLightIntensity)  + Emissive;  
            float3 \_Rim = pow(1.0 - max(, dot(wn, viewDir)), \_RimArea)\*\_RimColor.rgb\*\_RimPower;  
            c.rgb =  (UNITY\_LIGHTMODEL\_AMBIENT.xyz+(0.3+0.7\*nl)\*LIGHT\_ATTENUATION(i) \* \_LightColor.xyz)\*mainTex.rgb\*(-metallic)+ (refColor + (spec+ diffColor\*mainTex.rgb) \* metallic\*\_LightColor \* \_LightIntensity)  + Emissive + \_Rim;  
            c.a=;

            return c;  
        }  
        ENDCG

        CGINCLUDE  
        #include "UnityCG.cginc"

        inline half Pow5 (half x)  
        {  
            return x\*x \* x\*x \* x;  
        }

        inline half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)  
        {  
            half fd90 = 0.5 +  \* LdotH \* LdotH \* perceptualRoughness;  
            // Two schlick fresnel term  
            half lightScatter   = ( + (fd90 - ) \* Pow5( - NdotL));  
            half viewScatter    = ( + (fd90 - ) \* Pow5( - NdotV));

            return lightScatter \* viewScatter;  
        }  
        inline half3 calculateWorldNormal(half3 normal, half4 tangent, fixed3 texnormal, half normalScale)  
        {  
            normal         = normalize(normal);  
            tangent        = normalize(tangent);  
            half3 binormal = cross(normal,tangent.xyz) \* tangent.w;  
            half3x3 TBN = half3x3(tangent.xyz, binormal, normal);

            texnormal.xy \*= normalScale;  
            half3 normalL = texnormal.x \* TBN\[\] +  
                            texnormal.y \* TBN\[\] +  
                            texnormal.z \* TBN\[\];  
            half3 normalW = UnityObjectToWorldNormal(normalL);  
            return normalize(normalW);  
        }  
        inline half RoughnessToSpecPower (fixed roughness, out fixed realRoughness)  
        {  
            realRoughness = max(.01h, roughness \* roughness);        // m is the true academic roughness.

            half n = (2.0 / (realRoughness \* realRoughness)) - 2.0;    // https://dl.dropboxusercontent.com/u/55891920/papers/mm\_brdf.pdf  
                                                                    // prevent possible cases of pow(0,0), which could happen when roughness is 1.0 and NdotH is zero  
            return n;  
        }  
        inline fixed3 FresnelLerpFast (fixed3 F0, fixed3 F90, half cosA)  
        {  
            cosA =  - cosA;  
            half fresnel = cosA \* cosA \* cosA \* cosA;  
            return lerp (F0, F90, fresnel);  
        }  
        inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor)  
        {  
            specColor = lerp (half3(0.04, 0.04, 0.04) , albedo , metallic);

            half oneMinusDielectricSpec =  - 0.04;  
            half o = oneMinusDielectricSpec - metallic \* oneMinusDielectricSpec;  
            return albedo \* o;  
        }  
        inline half3 EnvRotate (half Degrees, half3 refDir)  
        {  
            half rot = Degrees / .296h;  
            half sinrot, cosrot;  
            sincos(rot, sinrot, cosrot);  
            half2x2 m = half2x2(cosrot, -sinrot, sinrot, cosrot);  
            refDir.xz = mul(m, refDir.xz);  
            refDir = normalize(refDir);  
            return refDir;  
        }

        //微表面BRDF公式  
            //                D(h) F(v,h) G(l,v,h)  
            //f(l,v) = ---------------------------  
            //                4(n·l)(n·v)

            //这个是GGX  
            //                alpha^2  
            //D(h) = -----------------------------------  
            //                pi\*((n·h)^2 \*(alpha^2-1)+1)^2  
            //alpha = roughness^2  
            //G(l,v,h) smith-Schlick  
            //                                            1  
            //G(l,v,h) = --------------------------------------------------  
            //                (nl\*(1-k)+k)\*(nv\*(1-k)+k)  
            //  
            // k = (a^2 +1) \* (a^2 +1)/8; 抄ue4  
            // k = roughness^2 //u3d  
            //f(l,v)=F(v,h)G(l,n,v)D(h)/4(nl)(nv)  
            //F(I,h) schlick菲涅尔近似等式  
            //F(I,h) = F0+(1-F0)(1-nl)^5  
            //F0高光反射颜色

        inline half BRDFspec(half roughness, half nl , half nv , half nh, half3 specColor )  
        {  
            half a2 = roughness\*roughness\*roughness\*roughness;  
            half d = nh\*nh\*(a2-)+;  
            half D = UNITY\_INV\_PI\*a2/(d\*d);

            half k = (a2+)\*(a2+)/;  
            half G = /((nl\*(-k)+k)\*(nv\*(-k)+k));

            half F = specColor+(-specColor)\*Pow5(-nl);  
            return F\*max( , D\*G\*nl/(\*nl\*nv));  
        }  
        inline half3 EnvBRDFMobile( half3 SpecularColor, half Roughness, half NoV )  
        {  
            const half4 c0 = { -, -0.0275, -0.572, 0.022 };  
            const half4 c1 = { , 0.0425, 1.04, -0.04 };  
            half4 r = Roughness \* c0 + c1;  
            half a004 = min( r.x \* r.x, exp2( -9.28 \* NoV ) ) \* r.x + r.y;  
            half2 AB = half2( -1.04, 1.04 ) \* a004 + r.zw;  
            return (SpecularColor \* AB.x + AB.y);  
        }  
        inline half3 ACESToneMapping(float3 color, float adapted\_lum)  
        {  
            const half A = 2.51;  
            const half B = 0.03;  
            const half C = 2.43;  
            const half D = 0.59;  
            const half E = 0.14;

            color \*= adapted\_lum;  
            return (color \* (A \* color + B)) / (color \* (C \* color + D) + E);  
        }  
        ENDCG  
    }  
}  

}

附上截图