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 } } }
附上截图