Skip to content

Instantly share code, notes, and snippets.

@binaryfoundry
Last active September 29, 2024 15:32
Show Gist options
  • Save binaryfoundry/a1eeca36922b71fe1942751879ab8ab6 to your computer and use it in GitHub Desktop.
Save binaryfoundry/a1eeca36922b71fe1942751879ab8ab6 to your computer and use it in GitHub Desktop.
Unity PBR Shader
Shader "CustomPBRShader"
{
Properties
{
[NoScaleOffset] _Albedo("Albedo", Color) = (1.0, 1.0, 1.0, 1.0)
[NoScaleOffset] _Metalness("Conductivity", Range(0.0, 1.0)) = 0.0
[NoScaleOffset] _Roughness("Roughness", Range(0.0, 1.0)) = 0.0
[NoScaleOffset][HDR] _RadianceMap("RadianceMap", CUBE) = "" {}
[NoScaleOffset][HDR] _IrradianceMap("IrradianceMap", CUBE) = "" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Geometry" }
LOD 100
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _Albedo;
float _Metalness;
float _Roughness;
samplerCUBE _RadianceMap;
samplerCUBE _IrradianceMap;
static float MipLevels = 8.0;
static float Exposure = 1.5;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : POSITION;
float3 normal : NORMAL;
float4 posWorld : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.normal = normalize(mul(float4(v.normal, 0.0), unity_WorldToObject).xyz);
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
// Tone Mapping
//https://mynameismjp.wordpress.com/2010/04/30/a-closer-look-at-tone-mapping/
//https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl
static const float3x3 ACESInputMat =
{
{0.59719, 0.35458, 0.04823},
{0.07600, 0.90834, 0.01566},
{0.02840, 0.13383, 0.83777}
};
static const float3x3 ACESOutputMat =
{
{ 1.60475, -0.53108, -0.07367},
{-0.10208, 1.10813, -0.00605},
{-0.00327, -0.07276, 1.07602}
};
float3 RRTAndODTFit(float3 v)
{
float3 a = v * (v + 0.0245786f) - 0.000090537f;
float3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
return a / b;
}
// Maps real valued output to a suitable range for display on an
// 8-bits per channel RGB screen.
float3 toneMapACES(float3 color)
{
color = mul(ACESInputMat, color);
color = RRTAndODTFit(color);
color = mul(ACESOutputMat, color);
color = saturate(color);
return color;
}
// Approximates the roughness/Fresnel lookup table generated offline.
// Explained in Filament documentation.
// https://google.github.io/filament/Filament.md.html#table_texturedfg
float3 EnvBRDFApprox(float3 f0, float3 NoV, float roughness)
{
float4 c0 = float4(-1.0, -0.0275, -0.572, 0.022);
float4 c1 = float4(1.0, 0.0425, 1.04, -0.04);
float4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
float2 AB = float2(-1.04, 1.04) * a004 + r.zw;
return f0 * AB.x + AB.y;
}
// Diffuse reflection map.
// https://google.github.io/filament/Filament.md.html#toc5.3.4.1
float3 diffuse_img(float3 n)
{
return _Albedo.rgb * texCUBE(_IrradianceMap, n).rgb;
}
// https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Shaders/Private/ReflectionEnvironmentShared.ush
// Heuristic that maps roughness to mip level
// This is done in a way such that a certain mip level will always have the same roughness, regardless of how many mips are in the texture
// Using more mips in the cubemap just allows sharper reflections to be supported
//#define REFLECTION_CAPTURE_ROUGHEST_MIP 1
//#define REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE 1.2
//half ComputeReflectionCaptureMipFromRoughness(half Roughness, half CubemapMaxMip)
//{
// half LevelFrom1x1 = REFLECTION_CAPTURE_ROUGHEST_MIP - REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE * log2(Roughness);
// return CubemapMaxMip - 1 - LevelFrom1x1;
//}
// Diffuse albedo must be divided by pi to be energy conserving.
// https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/diffuse-lambertian-shading
// Sample offline generated convolved environment map. MipLevels
// should match the number of images output.
// https://google.github.io/filament/Filament.md.html#toc5.3.4.4
// If not using a pre-convolved environment map, but instead one rendered
// at runtime use UE4 heuristic mapping to calculate mip level above. This
// mimics the offline convolution by biasing the mip level.
float3 specular_img(float3 n, float3 v)
{
float3 r = reflect(v, n);
return texCUBElod(_RadianceMap, float4(r, _Roughness * MipLevels)).rgb;
}
fixed4 frag (v2f i) : SV_Target
{
float3 n = normalize(i.normal);
float3 v = normalize(_WorldSpaceCameraPos - i.posWorld.xyz);
float NoV = dot(n, v);
float3 f0 = max(_Albedo.rgb * _Metalness, 0.04);
float3 diffuse = diffuse_img(n) * (1.0 - _Metalness);
float3 specular = specular_img(n, v);
float3 c = lerp(diffuse, specular, EnvBRDFApprox(f0, NoV, _Roughness));
return float4(toneMapACES(c * Exposure), _Albedo.a);
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment