
Physically Based Rendering Algorithms: A Comprehensive Study In Unity3D - Part 4 - Bringing All The Pieces Together
.png?ixlib=gatsbyFP&auto=compress%2Cformat&fit=max&w=2048&h=2048)
About the Author
Jordan is the founder of mudstack. He's also a writer, engineer and graphics enthusiast. Hacker of all the things, he wishes he was a viking.
Physically-Based Rendering (PBR) has become the industry standard for defining materials within games, animations, and VFX. Unity, Unreal, Frostbite, ThreeJS, and many more engines provide their own implementations of PBR, democratizing access to high fidelity graphics for a variety of studios. In the last 10 years, we've seen a massive shift in rendering pipelines. Realtime rendering, made easy by Unreal and Unity, has enabled even the smallest studios and teams to produce content on a level that used to take legions of artists to produce. Today, it is hard to find artists that are not familiar with the pipeline, but it can still be hard to find engineers and technical artists who are familiar with how the pipeline actually works behind the scenes. We created this study to break down Physically Based Rendering and make it as easy to understand as possible, whether you are a beginner or an expert. There are a lot of moving pieces, but we'll do our best to simplify the concepts and provide you with resources to build your own Physically Based shading models, or improve your understanding of the mechanics under the hood of engines like Unity and Unreal.
Piecing Together Your PBR Shader Pt.4: Bringing It All Together
Combining The Algorithms
Now that we have several versions of the NDF, GSF, and Fresnel Function, we need to combine them together so that we can see the resulting BRDF PBR Shader in action. The format to combine these algorithms is quite simple: multiply the algorithms by one another, then divide that by 4 * NdotL * NdotV.
float3 specularity = (SpecularDistribution * FresnelFunction * GeometricShadow) / (4 * ( NdotL * NdotV));
After you combine the algorithms, you can simply add that value to your diffuse color, and there you have it. PBR in action.
float3 lightingModel = (diffuseColor + specularity); lightingModel *= NdotL; float4 finalDiffuse = float4(lightingModel * attenColor,1); return finalDiffuse;
Getting Unity Lighting Information Involved
In order to sample the environment, you will need to take a few important steps in Unity and in your shader. Firstly, add a reflection probe into your scene and bake it. Then you will want to add this function to your shader:
UnityGI GetUnityGI(float3 lightColor, float3 lightDirection, float3 normalDirection,float3 viewDirection, float3 viewReflectDirection, float attenuation, float roughness, float3 worldPos){ //Unity light Setup :: UnityLight light; light.color = lightColor; light.dir = lightDirection; light.ndotl = max(0.0h,dot( normalDirection, lightDirection)); UnityGIInput d; d.light = light; d.worldPos = worldPos; d.worldViewDir = viewDirection; d.atten = attenuation; d.ambient = 0.0h; d.boxMax[0] = unity_SpecCube0_BoxMax; d.boxMin[0] = unity_SpecCube0_BoxMin; d.probePosition[0] = unity_SpecCube0_ProbePosition; d.probeHDR[0] = unity_SpecCube0_HDR; d.boxMax[1] = unity_SpecCube1_BoxMax; d.boxMin[1] = unity_SpecCube1_BoxMin; d.probePosition[1] = unity_SpecCube1_ProbePosition; d.probeHDR[1] = unity_SpecCube1_HDR; Unity_GlossyEnvironmentData ugls_en_data; ugls_en_data.roughness = roughness; ugls_en_data.reflUVW = viewReflectDirection; UnityGI gi = UnityGlobalIllumination(d, 1.0h, normalDirection, ugls_en_data ); return gi; }
You can call this function from inside of the fragment program, and it will generate the appropriate data that you can use to sample the environment.
UnityGI gi = GetUnityGI(_LightColor0.rgb, lightDirection, normalDirection, viewDirection, viewReflectDirection, attenuation, 1- _Glossiness, i.posWorld.xyz); float3 indirectDiffuse = gi.indirect.diffuse.rgb ; float3 indirectSpecular = gi.indirect.specular.rgb;
To use these values, we will want to add them to our final output like you see below, replacing a line or two that we have already placed in our shader. This will allow us to sample the environment when the properties are set in a way that allows the environment data to be visible.
float grazingTerm = saturate(roughness + _Metallic); float3 unityIndirectSpecularity = indirectSpecular * FresnelLerp(specColor,grazingTerm,NdotV) * max(0.15,_Metallic) * (1-roughness*roughness* roughness); float3 lightingModel = (diffuseColor + specularity + (unityIndirectSpecularity *_UnityLightingContribution));
Debugging Your Algorithms
To debug our algorithms, we can leverage Unity's ability to add toggles to material properties from within the shader.
[Toggle] _ENABLE_NDF ("Normal Distribution Enabled?", Float) = 0 [Toggle] _ENABLE_G ("Geometric Shadow Enabled?", Float) = 0 [Toggle] _ENABLE_F ("Fresnel Enabled?", Float) = 0 [Toggle] _ENABLE_D ("Diffuse Enabled?", Float) = 0
These toggles will enable or disable shader keywords on toggle. For more information, take a look at this page.
#ifdef _ENABLE_NDF_ON return float4(float3(1,1,1)* SpecularDistribution,1); #endif #ifdef _ENABLE_G_ON return float4(float3(1,1,1) * GeometricShadow,1) ; #endif #ifdef _ENABLE_F_ON return float4(float3(1,1,1)* FresnelFunction,1); #endif #ifdef _ENABLE_D_ON return float4(float3(1,1,1)* diffuseColor,1); #endif
This setup will allow you to toggle between the different effects in your shader, so that you can debug the effects by themselves.
Creating A Shader That Allows You To Select The Algorithm
By using the power of Keyword Enums, you can create the ability to easily switch between your algorithms, without all the commenting code maneuvering. Keyword Enums have a limit of 9 Keywords, so I had to get creative in my switching logic.
[KeywordEnum(BlinnPhong,Phong,Beckmann,Gaussian,GGX,TrowbridgeReitz,TrowbridgeReitzAnisotropic, Ward)] _NormalDistModel("Normal Distribution Model;", Float) = 0 [KeywordEnum(AshikhminShirley,AshikhminPremoze,Duer,Neumann,Kelemen,ModifiedKelemen,Cook,Ward,Kurt)]_GeoShadowModel("Geometric Shadow Model;", Float) = 0 [KeywordEnum(None,Walter,Beckman,GGX,Schlick,SchlickBeckman,SchlickGGX, Implicit)]_SmithGeoShadowModel("Smith Geometric Shadow Model; None if above is Used;", Float) = 0 [KeywordEnum(Schlick,SchlickIOR, SphericalGaussian)]_FresnelModel("Normal Distribution Model;", Float) = 0
//Normal Distribution Function/Specular Distribution----------------------------------------------------- #ifdef _NORMALDISTMODEL_BLINNPHONG SpecularDistribution *= BlinnPhongNormalDistribution(NdotH, _Glossiness, max(1,_Glossiness * 40)); #elif _NORMALDISTMODEL_PHONG SpecularDistribution *= PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40)); #elif _NORMALDISTMODEL_BECKMANN SpecularDistribution *= BeckmannNormalDistribution(roughness, NdotH); #elif _NORMALDISTMODEL_GAUSSIAN SpecularDistribution *= GaussianNormalDistribution(roughness, NdotH); #elif _NORMALDISTMODEL_GGX SpecularDistribution *= GGXNormalDistribution(roughness, NdotH); #elif _NORMALDISTMODEL_TROWBRIDGEREITZ SpecularDistribution *= TrowbridgeReitzNormalDistribution(NdotH, roughness); #elif _NORMALDISTMODEL_TROWBRIDGEREITZANISOTROPIC SpecularDistribution *= TrowbridgeReitzAnisotropicNormalDistribution(_Anisotropic,NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection, i.bitangentDir)); #elif _NORMALDISTMODEL_WARD SpecularDistribution *= WardAnisotropicNormalDistribution(_Anisotropic,NdotL, NdotV, NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection, i.bitangentDir)); #else SpecularDistribution *= GGXNormalDistribution(roughness, NdotH); #endif //Geometric Shadowing term---------------------------------------------------------------------------------- #ifdef _SMITHGEOSHADOWMODEL_NONE #ifdef _GEOSHADOWMODEL_ASHIKHMINSHIRLEY GeometricShadow *= AshikhminShirleyGeometricShadowingFunction (NdotL, NdotV, LdotH); #elif _GEOSHADOWMODEL_ASHIKHMINPREMOZE GeometricShadow *= AshikhminPremozeGeometricShadowingFunction (NdotL, NdotV); #elif _GEOSHADOWMODEL_DUER GeometricShadow *= DuerGeometricShadowingFunction (lightDirection, viewDirection, normalDirection, NdotL, NdotV); #elif _GEOSHADOWMODEL_NEUMANN GeometricShadow *= NeumannGeometricShadowingFunction (NdotL, NdotV); #elif _GEOSHADOWMODEL_KELEMAN GeometricShadow *= KelemenGeometricShadowingFunction (NdotL, NdotV, LdotH, VdotH); #elif _GEOSHADOWMODEL_MODIFIEDKELEMEN GeometricShadow *= ModifiedKelemenGeometricShadowingFunction (NdotV, NdotL, roughness); #elif _GEOSHADOWMODEL_COOK GeometricShadow *= CookTorrenceGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH); #elif _GEOSHADOWMODEL_WARD GeometricShadow *= WardGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH); #elif _GEOSHADOWMODEL_KURT GeometricShadow *= KurtGeometricShadowingFunction (NdotL, NdotV, VdotH, roughness); #else GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV); #endif ////SmithModelsBelow ////Gs = F(NdotL) * F(NdotV); #elif _SMITHGEOSHADOWMODEL_WALTER GeometricShadow *= WalterEtAlGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_BECKMAN GeometricShadow *= BeckmanGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_GGX GeometricShadow *= GGXGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_SCHLICK GeometricShadow *= SchlickGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_SCHLICKBECKMAN GeometricShadow *= SchlickBeckmanGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_SCHLICKGGX GeometricShadow *= SchlickGGXGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_IMPLICIT GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV); #else GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV); #endif //Fresnel Function------------------------------------------------------------------------------------------------- #ifdef _FRESNELMODEL_SCHLICK FresnelFunction *= SchlickFresnelFunction(specColor, LdotH); #elif _FRESNELMODEL_SCHLICKIOR FresnelFunction *= SchlickIORFresnelFunction(_Ior, LdotH); #elif _FRESNELMODEL_SPHERICALGAUSSIAN FresnelFunction *= SphericalGaussianFresnelFunction(LdotH, specColor); #else FresnelFunction *= SchlickIORFresnelFunction(_Ior, LdotH); #endif
Now when you select a Keyword from the dropdown in Unity, it will change the active algorithm. With this, your shader should be good to go. I recommend copy pasting the shader code below, so that you can get a full view of the complete shader, in case yours is messy.
The Complete Shader
Shader "Physically-Based-Lighting" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _SpecularColor ("Specular Color", Color) = (1,1,1,1) _SpecularPower("Specular Power", Range(0,1)) = 1 _SpecularRange("Specular Gloss", Range(1,40)) = 0 _Glossiness("Smoothness",Range(0,1)) = 1 _Metallic("Metallicness",Range(0,1)) = 0 _Anisotropic("Anisotropic", Range(-20,1)) = 0 _Ior("Ior", Range(1,4)) = 1.5 _UnityLightingContribution("Unity Reflection Contribution", Range(0,1)) = 1 [KeywordEnum(BlinnPhong,Phong,Beckmann,Gaussian,GGX,TrowbridgeReitz,TrowbridgeReitzAnisotropic, Ward)] _NormalDistModel("Normal Distribution Model;", Float) = 0 [KeywordEnum(AshikhminShirley,AshikhminPremoze,Duer,Neumann,Kelemen,ModifiedKelemen,Cook,Ward,Kurt)]_GeoShadowModel("Geometric Shadow Model;", Float) = 0 [KeywordEnum(None,Walter,Beckman,GGX,Schlick,SchlickBeckman,SchlickGGX, Implicit)]_SmithGeoShadowModel("Smith Geometric Shadow Model; None if above is Used;", Float) = 0 [KeywordEnum(Schlick,SchlickIOR, SphericalGaussian)]_FresnelModel("Normal Distribution Model;", Float) = 0 [Toggle] _ENABLE_NDF ("Normal Distribution Enabled?", Float) = 0 [Toggle] _ENABLE_G ("Geometric Shadow Enabled?", Float) = 0 [Toggle] _ENABLE_F ("Fresnel Enabled?", Float) = 0 [Toggle] _ENABLE_D ("Diffuse Enabled?", Float) = 0 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry" } Pass { Name "FORWARD" Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #define UNITY_PASS_FORWARDBASE #include "UnityCG.cginc" #include "AutoLight.cginc" #include "Lighting.cginc" #pragma multi_compile_fwdbase_fullshadows #pragma multi_compile _NORMALDISTMODEL_BLINNPHONG _NORMALDISTMODEL_PHONG _NORMALDISTMODEL_BECKMANN _NORMALDISTMODEL_GAUSSIAN _NORMALDISTMODEL_GGX _NORMALDISTMODEL_TROWBRIDGEREITZ _NORMALDISTMODEL_TROWBRIDGEREITZANISOTROPIC _NORMALDISTMODEL_WARD #pragma multi_compile _GEOSHADOWMODEL_ASHIKHMINSHIRLEY _GEOSHADOWMODEL_ASHIKHMINPREMOZE _GEOSHADOWMODEL_DUER_GEOSHADOWMODEL_NEUMANN _GEOSHADOWMODEL_KELEMAN _GEOSHADOWMODEL_MODIFIEDKELEMEN _GEOSHADOWMODEL_COOK _GEOSHADOWMODEL_WARD _GEOSHADOWMODEL_KURT #pragma multi_compile _SMITHGEOSHADOWMODEL_NONE _SMITHGEOSHADOWMODEL_WALTER _SMITHGEOSHADOWMODEL_BECKMAN _SMITHGEOSHADOWMODEL_GGX _SMITHGEOSHADOWMODEL_SCHLICK _SMITHGEOSHADOWMODEL_SCHLICKBECKMAN _SMITHGEOSHADOWMODEL_SCHLICKGGX _SMITHGEOSHADOWMODEL_IMPLICIT #pragma multi_compile _FRESNELMODEL_SCHLICK _FRESNELMODEL_SCHLICKIOR _FRESNELMODEL_SPHERICALGAUSSIAN #pragma multi_compile _ENABLE_NDF_OFF _ENABLE_NDF_ON #pragma multi_compile _ENABLE_G_OFF _ENABLE_G_ON #pragma multi_compile _ENABLE_F_OFF _ENABLE_F_ON #pragma multi_compile _ENABLE_D_OFF _ENABLE_D_ON #pragma target 3.0 float4 _Color; float4 _SpecularColor; float _SpecularPower; float _SpecularRange; float _Glossiness; float _Metallic; float _Anisotropic; float _Ior; float _NormalDistModel; float _GeoShadowModel; float _FresnelModel; float _UnityLightingContribution; struct VertexInput { float4 vertex : POSITION; //local vertex position float3 normal : NORMAL; //normal direction float4 tangent : TANGENT; //tangent direction float2 texcoord0 : TEXCOORD0; //uv coordinates float2 texcoord1 : TEXCOORD1; //lightmap uv coordinates }; struct VertexOutput { float4 pos : SV_POSITION; //screen clip space position and depth float2 uv0 : TEXCOORD0; //uv coordinates float2 uv1 : TEXCOORD1; //lightmap uv coordinates //below we create our own variables with the texcoord semantic. float3 normalDir : TEXCOORD3; //normal direction float3 posWorld : TEXCOORD4; //normal direction float3 tangentDir : TEXCOORD5; float3 bitangentDir : TEXCOORD6; LIGHTING_COORDS(7,8) //this initializes the unity lighting and shadow UNITY_FOG_COORDS(9) //this initializes the unity fog }; VertexOutput vert (VertexInput v) { VertexOutput o = (VertexOutput)0; o.uv0 = v.texcoord0; o.uv1 = v.texcoord1; o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz ); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.posWorld = mul(_Object2World, v.vertex); UNITY_TRANSFER_FOG(o,o.pos); TRANSFER_VERTEX_TO_FRAGMENT(o) return o; } UnityGI GetUnityGI(float3 lightColor, float3 lightDirection, float3 normalDirection,float3 viewDirection, float3 viewReflectDirection, float attenuation, float roughness, float3 worldPos){ //Unity light Setup :: UnityLight light; light.color = lightColor; light.dir = lightDirection; light.ndotl = max(0.0h,dot( normalDirection, lightDirection)); UnityGIInput d; d.light = light; d.worldPos = worldPos; d.worldViewDir = viewDirection; d.atten = attenuation; d.ambient = 0.0h; d.boxMax[0] = unity_SpecCube0_BoxMax; d.boxMin[0] = unity_SpecCube0_BoxMin; d.probePosition[0] = unity_SpecCube0_ProbePosition; d.probeHDR[0] = unity_SpecCube0_HDR; d.boxMax[1] = unity_SpecCube1_BoxMax; d.boxMin[1] = unity_SpecCube1_BoxMin; d.probePosition[1] = unity_SpecCube1_ProbePosition; d.probeHDR[1] = unity_SpecCube1_HDR; Unity_GlossyEnvironmentData ugls_en_data; ugls_en_data.roughness = roughness; ugls_en_data.reflUVW = viewReflectDirection; UnityGI gi = UnityGlobalIllumination(d, 1.0h, normalDirection, ugls_en_data ); return gi; } //--------------------------- //helper functions float MixFunction(float i, float j, float x) { return j * x + i * (1.0 - x); } float2 MixFunction(float2 i, float2 j, float x){ return j * x + i * (1.0h - x); } float3 MixFunction(float3 i, float3 j, float x){ return j * x + i * (1.0h - x); } float MixFunction(float4 i, float4 j, float x){ return j * x + i * (1.0h - x); } float sqr(float x){ return x*x; } //------------------------------ //------------------------------------------------ //schlick functions float SchlickFresnel(float i){ float x = clamp(1.0-i, 0.0, 1.0); float x2 = x*x; return x2*x2*x; } float3 FresnelLerp (float3 x, float3 y, float d) { float t = SchlickFresnel(d); return lerp (x, y, t); } float3 SchlickFresnelFunction(float3 SpecularColor,float LdotH){ return SpecularColor + (1 - SpecularColor)* SchlickFresnel(LdotH); } float SchlickIORFresnelFunction(float ior,float LdotH){ float f0 = pow((ior-1)/(ior+1),2); return f0 + (1 - f0) * SchlickFresnel(LdotH); } float SphericalGaussianFresnelFunction(float LdotH,float SpecularColor) { float power = ((-5.55473 * LdotH) - 6.98316) * LdotH; return SpecularColor + (1 - SpecularColor) * pow(2,power); } //----------------------------------------------- //----------------------------------------------- //normal incidence reflection calculation float F0 (float NdotL, float NdotV, float LdotH, float roughness){ // Diffuse fresnel float FresnelLight = SchlickFresnel(NdotL); float FresnelView = SchlickFresnel(NdotV); float FresnelDiffuse90 = 0.5 + 2.0 * LdotH*LdotH * roughness; return MixFunction(1, FresnelDiffuse90, FresnelLight) * MixFunction(1, FresnelDiffuse90, FresnelView); } //----------------------------------------------- //----------------------------------------------- //Normal Distribution Functions float BlinnPhongNormalDistribution(float NdotH, float specularpower, float speculargloss){ float Distribution = pow(NdotH,speculargloss) * specularpower; Distribution *= (2+specularpower) / (2*3.1415926535); return Distribution; } float PhongNormalDistribution(float RdotV, float specularpower, float speculargloss){ float Distribution = pow(RdotV,speculargloss) * specularpower; Distribution *= (2+specularpower) / (2*3.1415926535); return Distribution; } float BeckmannNormalDistribution(float roughness, float NdotH) { float roughnessSqr = roughness*roughness; float NdotHSqr = NdotH*NdotH; return max(0.000001,(1.0 / (3.1415926535*roughnessSqr*NdotHSqr*NdotHSqr))* exp((NdotHSqr-1)/(roughnessSqr*NdotHSqr))); } float GaussianNormalDistribution(float roughness, float NdotH) { float roughnessSqr = roughness*roughness; float thetaH = acos(NdotH); return exp(-thetaH*thetaH/roughnessSqr); } float GGXNormalDistribution(float roughness, float NdotH) { float roughnessSqr = roughness*roughness; float NdotHSqr = NdotH*NdotH; float TanNdotHSqr = (1-NdotHSqr)/NdotHSqr; return (1.0/3.1415926535) * sqr(roughness/(NdotHSqr * (roughnessSqr + TanNdotHSqr))); // float denom = NdotHSqr * (roughnessSqr-1) } float TrowbridgeReitzNormalDistribution(float NdotH, float roughness){ float roughnessSqr = roughness*roughness; float Distribution = NdotH*NdotH * (roughnessSqr-1.0) + 1.0; return roughnessSqr / (3.1415926535 * Distribution*Distribution); } float TrowbridgeReitzAnisotropicNormalDistribution(float anisotropic, float NdotH, float HdotX, float HdotY){ float aspect = sqrt(1.0h-anisotropic * 0.9h); float X = max(.001, sqr(1.0-_Glossiness)/aspect) * 5; float Y = max(.001, sqr(1.0-_Glossiness)*aspect) * 5; return 1.0 / (3.1415926535 * X*Y * sqr(sqr(HdotX/X) + sqr(HdotY/Y) + NdotH*NdotH)); } float WardAnisotropicNormalDistribution(float anisotropic, float NdotL, float NdotV, float NdotH, float HdotX, float HdotY){ float aspect = sqrt(1.0h-anisotropic * 0.9h); float X = max(.001, sqr(1.0-_Glossiness)/aspect) * 5; float Y = max(.001, sqr(1.0-_Glossiness)*aspect) * 5; float exponent = -(sqr(HdotX/X) + sqr(HdotY/Y)) / sqr(NdotH); float Distribution = 1.0 / ( 3.14159265 * X * Y * sqrt(NdotL * NdotV)); Distribution *= exp(exponent); return Distribution; } //-------------------------- //----------------------------------------------- //Geometric Shadowing Functions float ImplicitGeometricShadowingFunction (float NdotL, float NdotV){ float Gs = (NdotL*NdotV); return Gs; } float AshikhminShirleyGeometricShadowingFunction (float NdotL, float NdotV, float LdotH){ float Gs = NdotL*NdotV/(LdotH*max(NdotL,NdotV)); return (Gs); } float AshikhminPremozeGeometricShadowingFunction (float NdotL, float NdotV){ float Gs = NdotL*NdotV/(NdotL+NdotV - NdotL*NdotV); return (Gs); } float DuerGeometricShadowingFunction (float3 lightDirection,float3 viewDirection, float3 normalDirection,float NdotL, float NdotV){ float3 LpV = lightDirection + viewDirection; float Gs = dot(LpV,LpV) * pow(dot(LpV,normalDirection),-4); return (Gs); } float NeumannGeometricShadowingFunction (float NdotL, float NdotV){ float Gs = (NdotL*NdotV)/max(NdotL, NdotV); return (Gs); } float KelemenGeometricShadowingFunction (float NdotL, float NdotV, float LdotH, float VdotH){ // float Gs = (NdotL*NdotV)/ (LdotH * LdotH); //this float Gs = (NdotL*NdotV)/(VdotH * VdotH); //or this? return (Gs); } float ModifiedKelemenGeometricShadowingFunction (float NdotV, float NdotL, float roughness) { float c = 0.797884560802865; // c = sqrt(2 / Pi) float k = roughness * roughness * c; float gH = NdotV * k +(1-k); return (gH * gH * NdotL); } float CookTorrenceGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH){ float Gs = min(1.0, min(2*NdotH*NdotV / VdotH, 2*NdotH*NdotL / VdotH)); return (Gs); } float WardGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH){ float Gs = pow( NdotL * NdotV, 0.5); return (Gs); } float KurtGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float alpha){ float Gs = (VdotH*pow(NdotL*NdotV, alpha))/ NdotL * NdotV; return (Gs); } //SmithModelsBelow //Gs = F(NdotL) * F(NdotV); float WalterEtAlGeometricShadowingFunction (float NdotL, float NdotV, float alpha){ float alphaSqr = alpha*alpha; float NdotLSqr = NdotL*NdotL; float NdotVSqr = NdotV*NdotV; float SmithL = 2/(1 + sqrt(1 + alphaSqr * (1-NdotLSqr)/(NdotLSqr))); float SmithV = 2/(1 + sqrt(1 + alphaSqr * (1-NdotVSqr)/(NdotVSqr))); float Gs = (SmithL * SmithV); return Gs; } float BeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness){ float roughnessSqr = roughness*roughness; float NdotLSqr = NdotL*NdotL; float NdotVSqr = NdotV*NdotV; float calulationL = (NdotL)/(roughnessSqr * sqrt(1- NdotLSqr)); float calulationV = (NdotV)/(roughnessSqr * sqrt(1- NdotVSqr)); float SmithL = calulationL < 1.6 ? (((3.535 * calulationL) + (2.181 * calulationL * calulationL))/(1 + (2.276 * calulationL) + (2.577 * calulationL * calulationL))) : 1.0; float SmithV = calulationV < 1.6 ? (((3.535 * calulationV) + (2.181 * calulationV * calulationV))/(1 + (2.276 * calulationV) + (2.577 * calulationV * calulationV))) : 1.0; float Gs = (SmithL * SmithV); return Gs; } float GGXGeometricShadowingFunction (float NdotL, float NdotV, float roughness){ float roughnessSqr = roughness*roughness; float NdotLSqr = NdotL*NdotL; float NdotVSqr = NdotV*NdotV; float SmithL = (2 * NdotL)/ (NdotL + sqrt(roughnessSqr + ( 1-roughnessSqr) * NdotLSqr)); float SmithV = (2 * NdotV)/ (NdotV + sqrt(roughnessSqr + ( 1-roughnessSqr) * NdotVSqr)); float Gs = (SmithL * SmithV) ; return Gs; } float SchlickGeometricShadowingFunction (float NdotL, float NdotV, float roughness) { float roughnessSqr = roughness*roughness; float SmithL = (NdotL)/(NdotL * (1-roughnessSqr) + roughnessSqr); float SmithV = (NdotV)/(NdotV * (1-roughnessSqr) + roughnessSqr); return (SmithL * SmithV); } float SchlickBeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness){ float roughnessSqr = roughness*roughness; float k = roughnessSqr * 0.797884560802865; float SmithL = (NdotL)/ (NdotL * (1- k) + k); float SmithV = (NdotV)/ (NdotV * (1- k) + k); float Gs = (SmithL * SmithV); return Gs; } float SchlickGGXGeometricShadowingFunction (float NdotL, float NdotV, float roughness){ float k = roughness / 2; float SmithL = (NdotL)/ (NdotL * (1- k) + k); float SmithV = (NdotV)/ (NdotV * (1- k) + k); float Gs = (SmithL * SmithV); return Gs; } //-------------------------- float4 frag(VertexOutput i) : COLOR { //normal direction calculations float3 normalDirection = normalize(i.normalDir); float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz); float shiftAmount = dot(i.normalDir, viewDirection); normalDirection = shiftAmount < 0.0f ? normalDirection + viewDirection * (-shiftAmount + 1e-5f) : normalDirection; //light calculations float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w)); float3 lightReflectDirection = reflect( -lightDirection, normalDirection ); float3 viewReflectDirection = normalize(reflect( -viewDirection, normalDirection )); float NdotL = max(0.0, dot( normalDirection, lightDirection )); float3 halfDirection = normalize(viewDirection+lightDirection); float NdotH = max(0.0,dot( normalDirection, halfDirection)); float NdotV = max(0.0,dot( normalDirection, viewDirection)); float VdotH = max(0.0,dot( viewDirection, halfDirection)); float LdotH = max(0.0,dot(lightDirection, halfDirection)); float LdotV = max(0.0,dot(lightDirection, viewDirection)); float RdotV = max(0.0, dot( lightReflectDirection, viewDirection )); float attenuation = LIGHT_ATTENUATION(i); float3 attenColor = attenuation * _LightColor0.rgb; //get Unity Scene lighting data UnityGI gi = GetUnityGI(_LightColor0.rgb, lightDirection, normalDirection, viewDirection, viewReflectDirection, attenuation, 1- _Glossiness, i.posWorld.xyz); float3 indirectDiffuse = gi.indirect.diffuse.rgb ; float3 indirectSpecular = gi.indirect.specular.rgb; //diffuse color calculations float roughness = 1-(_Glossiness * _Glossiness); roughness = roughness * roughness; float3 diffuseColor = _Color.rgb * (1.0 - _Metallic) ; float f0 = F0(NdotL, NdotV, LdotH, roughness); diffuseColor *= f0; diffuseColor+=indirectDiffuse; //Specular calculations float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5); float3 SpecularDistribution = specColor; float GeometricShadow = 1; float3 FresnelFunction = specColor; //Normal Distribution Function/Specular Distribution----------------------------------------------------- #ifdef _NORMALDISTMODEL_BLINNPHONG SpecularDistribution *= BlinnPhongNormalDistribution(NdotH, _Glossiness, max(1,_Glossiness * 40)); #elif _NORMALDISTMODEL_PHONG SpecularDistribution *= PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40)); #elif _NORMALDISTMODEL_BECKMANN SpecularDistribution *= BeckmannNormalDistribution(roughness, NdotH); #elif _NORMALDISTMODEL_GAUSSIAN SpecularDistribution *= GaussianNormalDistribution(roughness, NdotH); #elif _NORMALDISTMODEL_GGX SpecularDistribution *= GGXNormalDistribution(roughness, NdotH); #elif _NORMALDISTMODEL_TROWBRIDGEREITZ SpecularDistribution *= TrowbridgeReitzNormalDistribution(NdotH, roughness); #elif _NORMALDISTMODEL_TROWBRIDGEREITZANISOTROPIC SpecularDistribution *= TrowbridgeReitzAnisotropicNormalDistribution(_Anisotropic,NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection, i.bitangentDir)); #elif _NORMALDISTMODEL_WARD SpecularDistribution *= WardAnisotropicNormalDistribution(_Anisotropic,NdotL, NdotV, NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection, i.bitangentDir)); #else SpecularDistribution *= GGXNormalDistribution(roughness, NdotH); #endif //Geometric Shadowing term---------------------------------------------------------------------------------- #ifdef _SMITHGEOSHADOWMODEL_NONE #ifdef _GEOSHADOWMODEL_ASHIKHMINSHIRLEY GeometricShadow *= AshikhminShirleyGeometricShadowingFunction (NdotL, NdotV, LdotH); #elif _GEOSHADOWMODEL_ASHIKHMINPREMOZE GeometricShadow *= AshikhminPremozeGeometricShadowingFunction (NdotL, NdotV); #elif _GEOSHADOWMODEL_DUER GeometricShadow *= DuerGeometricShadowingFunction (lightDirection, viewDirection, normalDirection, NdotL, NdotV); #elif _GEOSHADOWMODEL_NEUMANN GeometricShadow *= NeumannGeometricShadowingFunction (NdotL, NdotV); #elif _GEOSHADOWMODEL_KELEMAN GeometricShadow *= KelemenGeometricShadowingFunction (NdotL, NdotV, LdotH, VdotH); #elif _GEOSHADOWMODEL_MODIFIEDKELEMEN GeometricShadow *= ModifiedKelemenGeometricShadowingFunction (NdotV, NdotL, roughness); #elif _GEOSHADOWMODEL_COOK GeometricShadow *= CookTorrenceGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH); #elif _GEOSHADOWMODEL_WARD GeometricShadow *= WardGeometricShadowingFunction (NdotL, NdotV, VdotH, NdotH); #elif _GEOSHADOWMODEL_KURT GeometricShadow *= KurtGeometricShadowingFunction (NdotL, NdotV, VdotH, roughness); #else GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV); #endif ////SmithModelsBelow ////Gs = F(NdotL) * F(NdotV); #elif _SMITHGEOSHADOWMODEL_WALTER GeometricShadow *= WalterEtAlGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_BECKMAN GeometricShadow *= BeckmanGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_GGX GeometricShadow *= GGXGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_SCHLICK GeometricShadow *= SchlickGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_SCHLICKBECKMAN GeometricShadow *= SchlickBeckmanGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_SCHLICKGGX GeometricShadow *= SchlickGGXGeometricShadowingFunction (NdotL, NdotV, roughness); #elif _SMITHGEOSHADOWMODEL_IMPLICIT GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV); #else GeometricShadow *= ImplicitGeometricShadowingFunction (NdotL, NdotV); #endif //Fresnel Function------------------------------------------------------------------------------------------------- #ifdef _FRESNELMODEL_SCHLICK FresnelFunction *= SchlickFresnelFunction(specColor, LdotH); #elif _FRESNELMODEL_SCHLICKIOR FresnelFunction *= SchlickIORFresnelFunction(_Ior, LdotH); #elif _FRESNELMODEL_SPHERICALGAUSSIAN FresnelFunction *= SphericalGaussianFresnelFunction(LdotH, specColor); #else FresnelFunction *= SchlickIORFresnelFunction(_Ior, LdotH); #endif #ifdef _ENABLE_NDF_ON return float4(float3(1,1,1)* SpecularDistribution,1); #endif #ifdef _ENABLE_G_ON return float4(float3(1,1,1) * GeometricShadow,1) ; #endif #ifdef _ENABLE_F_ON return float4(float3(1,1,1)* FresnelFunction,1); #endif #ifdef _ENABLE_D_ON return float4(float3(1,1,1)* diffuseColor,1); #endif //PBR float3 specularity = (SpecularDistribution * FresnelFunction * GeometricShadow) / (4 * ( NdotL * NdotV)); float grazingTerm = saturate(roughness + _Metallic); float3 unityIndirectSpecularity = indirectSpecular * FresnelLerp(specColor,grazingTerm,NdotV) * max(0.15,_Metallic) * (1-roughness*roughness* roughness); float3 lightingModel = ((diffuseColor) + specularity + (unityIndirectSpecularity *_UnityLightingContribution)); lightingModel *= NdotL; float4 finalDiffuse = float4(lightingModel * attenColor,1); UNITY_APPLY_FOG(i.fogCoord, finalDiffuse); return finalDiffuse; } ENDCG } } FallBack "Legacy Shaders/Diffuse" }