PBR探索

原理

根据能量守恒,以及一系列光照原理得出微表面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) = (1,1,1,1)
        _EmissiveIntensity("自发光强度",Float) = 1
        
        _LightColor("光照颜色",Color) = (1,1,1,1)
        _LightIntensity("光照强度",Range(0,5)) = 1
        
        _normalScale("法线强度",Float) = 1
        _environment_rotation("环境光贴图旋转",Range(0,360)) = 0
        _RotateSpeed("旋转速度", float) = 1
        _Exposure("环境光曝光值",Float) = 1
        _Skincolor ("Skin Color Custom", Color) = (1,1,1,1)

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

        _ChannelTex("RGB光滑金属变色", 2D) = "white" {}
        
        _EmissiveMap("自发光贴图", 2D) = "black" {}

        _Cube ("环境光贴图", Cube) = "" {}
        
        _Metallic("金属度上限",Range(0,1))= 1
        _MetallicMin("金属度下限",Range(0,1))= 0
        _Glossiness ("光滑度上限", Range(0,1)) = 1
        _GlossinessMin ("光滑度下限", Range(0,1)) = 0
        //Rim
        _RimColor("轮廓光颜色", Color) = (1, 1, 1, 1)
        _RimArea("轮廓光范围", Range(0, 4)) = 3.6
        _RimPower("轮廓光强度", Range(0, 15)) = 0.0
    }  
    
    SubShader 
    {  
        LOD 400
        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)  )* 0.99h;
                fixed roughness = 1 - glossness;
                fixed colorMask = channel.b;
                
                fixed4 mainTex  = tex2D(_MainTex,i.uv);
                mainTex *= colorMask * _Skincolor + (1 - 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 * 36, 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 * 8 ;
                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(0, 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*(1-metallic)+ (refColor + (spec+ diffColor*mainTex.rgb) * metallic*_LightColor * _LightIntensity)  + Emissive + _Rim;
                c.a=1;
                
                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 + 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;
            }
            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[0] +
                                texnormal.y * TBN[1] +
                                texnormal.z * TBN[2];
                half3 normalW = UnityObjectToWorldNormal(normalL);
                return normalize(normalW);
            }
            inline half RoughnessToSpecPower (fixed roughness, out fixed realRoughness)
            {
                realRoughness = max(0.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 = 1 - 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 = 1 - 0.04;
                half o = oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
                return albedo * o;
            }
            inline half3 EnvRotate (half Degrees, half3 refDir)
            {
                half rot = Degrees / 57.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-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));
            }
            inline half3 EnvBRDFMobile( half3 SpecularColor, half Roughness, half NoV )
            {
                const half4 c0 = { -1, -0.0275, -0.572, 0.022 };
                const half4 c1 = { 1, 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
        } 
    }
}

附上截图

 

posted on 2018-08-23 14:45  marcher  阅读(399)  评论(0编辑  收藏  举报

导航