// Default BRDF to use: #if !defined (UNITY_BRDF_PBS) // allow to explicitly override BRDF in custom shader // still add safe net for low shader models, otherwise we might end up with shaders failing to compile #if SHADER_TARGET < 30 #define UNITY_BRDF_PBS BRDF3_Unity_PBS #elif UNITY_PBS_USE_BRDF3 #define UNITY_BRDF_PBS BRDF3_Unity_PBS #elif UNITY_PBS_USE_BRDF2 #define UNITY_BRDF_PBS BRDF2_Unity_PBS #elif UNITY_PBS_USE_BRDF1 #define UNITY_BRDF_PBS BRDF1_Unity_PBS #elif defined(SHADER_TARGET_SURFACE_ANALYSIS) // we do preprocess pass during shader analysis and we dont actually care about brdf as we need only inputs/outputs #define UNITY_BRDF_PBS BRDF1_Unity_PBS #else #error something broke in auto-choosing BRDF #endif #endif
选个效果最好的BRDF1_Unity_PBS
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, half3 normal, half3 viewDir, UnityLight light, UnityIndirect gi) { half perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness); half3 halfDir = Unity_SafeNormalize (light.dir + viewDir); // NdotV should not be negative for visible pixels, but it can happen due to perspective projection and normal mapping // In this case normal should be modified to become valid (i.e facing camera) and not cause weird artifacts. // but this operation adds few ALU and users may not want it. Alternative is to simply take the abs of NdotV (less correct but works too). // Following define allow to control this. Set it to 0 if ALU is critical on your platform. // This correction is interesting for GGX with SmithJoint visibility function because artifacts are more visible in this case due to highlight edge of rough surface // Edit: Disable this code by default for now as it is not compatible with two sided lighting used in SpeedTree. #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0 #if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV // The amount we shift the normal toward the view vector is defined by the dot product. half shiftAmount = dot(normal, viewDir); normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal; // A re-normalization should be applied here but as the shift is small we don't do it to save ALU. //normal = normalize(normal); half nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here #else half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact #endif half nl = saturate(dot(normal, light.dir)); half nh = saturate(dot(normal, halfDir)); half lv = saturate(dot(light.dir, viewDir)); half lh = saturate(dot(light.dir, halfDir)); // Diffuse term half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl; // Specular term // HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm! // BUT 1) that will make shader look significantly darker than Legacy ones // and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH half roughness = PerceptualRoughnessToRoughness(perceptualRoughness); #if UNITY_BRDF_GGX half V = SmithJointGGXVisibilityTerm (nl, nv, roughness); half D = GGXTerm (nh, roughness); #else // Legacy half V = SmithBeckmannVisibilityTerm (nl, nv, roughness); half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness)); #endif half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later # ifdef UNITY_COLORSPACE_GAMMA specularTerm = sqrt(max(1e-4h, specularTerm)); # endif // specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value specularTerm = max(0, specularTerm * nl); #if defined(_SPECULARHIGHLIGHTS_OFF) specularTerm = 0.0; #endif // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1) half surfaceReduction; # ifdef UNITY_COLORSPACE_GAMMA surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1] # else surfaceReduction = 1.0 / (roughness*roughness + 1.0); // fade \in [0.5;1] # endif // To provide true Lambert lighting, we need to be able to kill specular completely. specularTerm *= any(specColor) ? 1.0 : 0.0; half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) + specularTerm * light.color * FresnelTerm (specColor, lh) + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); return half4(color, 1); }
UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV为0,走下边分支
UNITY_BRDF_GGX 用
UNITY_COLORSPACE_GAMMA,是gamma空间
_SPECULARHIGHLIGHTS_OFF false
去掉分支,代码如下
half perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness); half3 halfDir = Unity_SafeNormalize (light.dir + viewDir); half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact half nl = saturate(dot(normal, light.dir)); half nh = saturate(dot(normal, halfDir)); half lv = saturate(dot(light.dir, viewDir)); half lh = saturate(dot(light.dir, halfDir)); // Diffuse term half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl; half roughness = PerceptualRoughnessToRoughness(perceptualRoughness); half V = SmithJointGGXVisibilityTerm (nl, nv, roughness); half D = GGXTerm (nh, roughness); half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later specularTerm = sqrt(max(1e-4h, specularTerm)); specularTerm = max(0, specularTerm * nl); half surfaceReduction= 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1] specularTerm *= any(specColor) ? 1.0 : 0.0; half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) + specularTerm * light.color * FresnelTerm (specColor, lh) + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); return half4(color, 1);
最后给下完成代码
Shader "Custom/customstandard" { 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 { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "AutoLight.cginc" struct vertexinput { float4 vertex : POSITION; half3 normal : NORMAL; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float2 uv2 : TEXCOORD2; half4 tangent : TANGENT; }; struct v2f { float4 pos : SV_POSITION; float4 tex : TEXCOORD0; half3 eyeVec : TEXCOORD1; half4 tangentToWorldAndParallax[3] : TEXCOORD2; // [3x3:tangentToWorld | 1x3:viewDirForParallax] half4 ambientOrLightmapUV : TEXCOORD5; // SH or Lightmap UV SHADOW_COORDS(6) UNITY_FOG_COORDS(7) float3 posWorld : TEXCOORD8; half3 reflUVW : TEXCOORD9; }; 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; fixed4 _LightColor0; v2f vert(vertexinput v) { v2f o = (v2f)0; float4 posWorld = mul(unity_ObjectToWorld, v.vertex); o.posWorld = posWorld.xyz; o.pos = UnityObjectToClipPos(v.vertex); o.tex.xy= v.uv0; o.eyeVec = posWorld.xyz - _WorldSpaceCameraPos; half3 normalWorld = UnityObjectToWorldNormal(v.normal); float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w); float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w); o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0]; o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1]; o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2]; //We need this for shadow receving TRANSFER_SHADOW(o); o.ambientOrLightmapUV.rgb = SHEvalLinearL2Ex(normalWorld); o.reflUVW = reflect(o.eyeVec, normalWorld); UNITY_TRANSFER_FOG(o, o.pos); return o; } fixed4 frag(v2f i) : SV_Target { UnityLight mainLight; mainLight.color = _LightColor0.rgb; mainLight.dir = _WorldSpaceLightPos0.xyz; mainLight.ndotl = 0; // Not used half atten = SHADOW_ATTENUATION(i); UnityGIInput d; d.light = mainLight; d.worldPos = i.posWorld; half3 worldViewDir = -normalize(i.eyeVec); d.worldViewDir = worldViewDir; d.atten = atten; d.ambient = i.ambientOrLightmapUV.rgb; d.lightmapUV = 0; d.probeHDR[0] = unity_SpecCube0_HDR; d.probeHDR[1] = unity_SpecCube1_HDR; fixed4 channel = tex2D(_ChannelTex, i.tex); fixed metallic = _MetallicMin + channel.g * ( _Metallic - _MetallicMin ); half oneMinusReflectivity; half3 specColor; fixed colorMask = channel.b; fixed4 mainTex = tex2D(_MainTex,i.tex); mainTex *= colorMask * _Skincolor + (1 - colorMask); half3 diffColor = DiffuseAndSpecularFromMetallic (mainTex.rgb, metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity); fixed smoothness = ( _GlossinessMin + channel.r * (_Glossiness-_GlossinessMin) )* 0.99h; half3 normalWorld = PerPixelWorldNormal(UnpackScaleNormal(tex2D (_BumpTex, i.tex.xy), _normalScale), i.tangentToWorldAndParallax ); Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(smoothness, worldViewDir, normalWorld, specColor); // Replace the reflUVW if it has been compute in Vertex shader. Note: the compiler will optimize the calcul in UnityGlossyEnvironmentSetup itself g.reflUVW = i.reflUVW; UnityGI gi = UnityGlobalIllumination (d, 1, normalWorld, g); half4 c = BRDF(diffColor, specColor, oneMinusReflectivity, smoothness, normalWorld, worldViewDir, gi.light, gi.indirect); fixed emimask = tex2D(_EmissiveMap, i.tex).r; fixed3 Emissive = emimask * _EmissiveColor.rgb * _EmissiveIntensity; float3 _Rim = pow(1.0 - max(0, dot(normalWorld, worldViewDir)), _RimArea)*_RimColor.rgb*_RimPower; c.rgb += Emissive + _Rim; UNITY_APPLY_FOG(i.fogCoord, c.rgb); return c; } ENDCG CGINCLUDE #define unity_ColorSpaceDielectricSpec half4(0.220916301, 0.220916301, 0.220916301, 1.0 - 0.220916301) #define UNITY_INV_PI 0.31830988618f struct UnityLight { half3 color; half3 dir; half ndotl; // Deprecated: Ndotl is now calculated on the fly and is no longer stored. Do not used it. }; struct UnityIndirect { half3 diffuse; half3 specular; }; struct UnityGIInput { UnityLight light; // pixel light, sent from the engine float3 worldPos; half3 worldViewDir; half atten; half3 ambient; // interpolated lightmap UVs are passed as full float precision data to fragment shaders // so lightmapUV (which is used as a tmp inside of lightmap fragment shaders) should // also be full float precision to avoid data loss before sampling a texture. float4 lightmapUV; // .xy = static lightmap UV, .zw = dynamic lightmap UV #if UNITY_SPECCUBE_BLENDING || UNITY_SPECCUBE_BOX_PROJECTION float4 boxMin[2]; #endif #if UNITY_SPECCUBE_BOX_PROJECTION float4 boxMax[2]; float4 probePosition[2]; #endif // HDR cubemap properties, use to decompress HDR texture float4 probeHDR[2]; }; struct UnityGI { UnityLight light; #ifdef DIRLIGHTMAP_SEPARATE #ifdef LIGHTMAP_ON UnityLight light2; #endif #ifdef DYNAMICLIGHTMAP_ON UnityLight light3; #endif #endif UnityIndirect indirect; }; struct Unity_GlossyEnvironmentData { // - Deferred case have one cubemap // - Forward case can have two blended cubemap (unusual should be deprecated). // Surface properties use for cubemap integration half roughness; // CAUTION: This is perceptualRoughness but because of compatibility this name can't be change :( half3 reflUVW; }; Unity_GlossyEnvironmentData UnityGlossyEnvironmentSetup(half Smoothness, half3 worldViewDir, half3 Normal, half3 fresnel0) { Unity_GlossyEnvironmentData g; g.roughness /* perceptualRoughness */ = 1-Smoothness; g.reflUVW = reflect(-worldViewDir, Normal); return g; } // normal should be normalized, w=1.0 half3 SHEvalLinearL0L1Ex (half4 normal) { half3 x; // Linear (L1) + constant (L0) polynomial terms x.r = dot(unity_SHAr,normal); x.g = dot(unity_SHAg,normal); x.b = dot(unity_SHAb,normal); return x; } // normal should be normalized, w=1.0 half3 SHEvalLinearL2Ex(half3 normal) { half3 x1, x2; // 4 of the quadratic (L2) polynomials half4 vB = normal.xyzz * normal.yzzx; x1.r = dot(unity_SHBr, vB); x1.g = dot(unity_SHBg, vB); x1.b = dot(unity_SHBb, vB); // Final (5th) quadratic (L2) polynomial half vC = normal.x*normal.x - normal.y*normal.y; x2 = unity_SHC.rgb * vC; return x1 + x2; } inline half3 LinearToGamma (half3 linRGB) { linRGB = max(linRGB, half3(0.h, 0.h, 0.h)); // An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1 return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.h); // Exact version, useful for debugging. //return half3(LinearToGammaSpaceExact(linRGB.r), LinearToGammaSpaceExact(linRGB.g), LinearToGammaSpaceExact(linRGB.b)); } inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn) { UnityGI o_gi; o_gi.light = data.light; o_gi.light.color *= data.atten; o_gi.indirect.diffuse = LinearToGamma( data.ambient + SHEvalLinearL0L1Ex (half4(normalWorld, 1.0))); o_gi.indirect.specular = 0; return o_gi; } inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity) { specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic); oneMinusReflectivity = (1-metallic)*unity_ColorSpaceDielectricSpec.a; return albedo * oneMinusReflectivity; } half3x3 CreateTangentToWorldPerVertex(half3 normal, half3 tangent, half tangentSign) { // For odd-negative scale transforms we need to flip the sign half sign = tangentSign * unity_WorldTransformParams.w; half3 binormal = cross(normal, tangent) * sign; return half3x3(tangent, binormal, normal); } half3 UnpackScaleNormal(half4 packednormal, half bumpScale) { #if defined(UNITY_NO_DXT5nm) return packednormal.xyz * 2 - 1; #else half3 normal; normal.xy = (packednormal.wy * 2 - 1); #if (SHADER_TARGET >= 30) // SM2.0: instruction count limitation // SM2.0: normal scaler is not supported normal.xy *= bumpScale; #endif normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy))); return normal; #endif } half3 PerPixelWorldNormal(half3 normalTangent, half4 tangentToWorld[3]) { half3 tangent = tangentToWorld[0].xyz; half3 binormal = tangentToWorld[1].xyz; half3 normal = tangentToWorld[2].xyz; half3 normalWorld = normalize(tangent * normalTangent.x + binormal * normalTangent.y + normal * normalTangent.z); // @TODO: see if we can squeeze this normalize on SM2.0 as well return normalWorld; } inline half SmoothnessToPerceptualRoughness(half smoothness) { return (1 - smoothness); } inline half PerceptualRoughnessToRoughness(half perceptualRoughness) { return perceptualRoughness * perceptualRoughness; } inline half Pow5 (half x) { return x*x * x*x * x; } inline half3 Unity_SafeNormalize(half3 inVec) { half dp3 = max(0.001f, dot(inVec, inVec)); return inVec * rsqrt(dp3); } // Note: Disney diffuse must be multiply by diffuseAlbedo / PI. This is done outside of this function. 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 half GGXTerm (half NdotH, half roughness) { half a2 = roughness * roughness; half d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad return 0.31830988618f * a2 / (d * d + 1e-7f); // This function is not intended to be running on Mobile, // therefore epsilon is smaller than what can be represented by half } // Ref: http://jcgt.org/published/0003/02/03/paper.pdf inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half roughness) { half a = roughness; half lambdaV = NdotL * (NdotV * (1 - a) + a); half lambdaL = NdotV * (NdotL * (1 - a) + a); return 0.5f / (lambdaV + lambdaL + 1e-5f); } inline half3 FresnelTerm (half3 F0, half cosA) { half t = Pow5 (1 - cosA); // ala Schlick interpoliation return F0 + (1-F0) * t; } inline half3 FresnelLerp (half3 F0, half3 F90, half cosA) { half t = Pow5 (1 - cosA); // ala Schlick interpoliation return lerp (F0, F90, t); } inline half4 BRDF(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,half3 normal, half3 viewDir,UnityLight light, UnityIndirect gi) { half perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness); half3 halfDir = Unity_SafeNormalize (light.dir + viewDir); half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact half nl = saturate(dot(normal, light.dir)); half nh = saturate(dot(normal, halfDir)); half lv = saturate(dot(light.dir, viewDir)); half lh = saturate(dot(light.dir, halfDir)); // Diffuse term half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl; half roughness = PerceptualRoughnessToRoughness(perceptualRoughness); half V = SmithJointGGXVisibilityTerm (nl, nv, roughness); half D = GGXTerm (nh, roughness); half specularTerm = V*D * 3.14159265359f; // Torrance-Sparrow model, Fresnel is applied later specularTerm = sqrt(max(1e-4h, specularTerm)); specularTerm = max(0, specularTerm * nl); half surfaceReduction= 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1] specularTerm *= any(specColor) ? 1.0 : 0.0; half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) + specularTerm * light.color * FresnelTerm (specColor, lh) + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); return half4(color, 1); } ENDCG } } }
不过还有点问题,回头查下