forked from vixorien/D3D11Starter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pbr.hlsli
123 lines (106 loc) · 3.8 KB
/
pbr.hlsli
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#ifndef __GGP_PBR_H__
#define __GGP_PBR_H__
// A constant Fresnel value for non-metals (glass and plastic have values of about 0.04)
static const float F0_NON_METAL = 0.04f;
// Minimum roughness for when spec distribution function denominator goes to zero
static const float MIN_ROUGHNESS = 0.0000001f;
static const float PI = 3.14159265359f;
// Lambert diffuse BRDF - Same as the basic lighting diffuse calculation!
// - NOTE: this function assumes the vectors are already NORMALIZED!
float DiffusePBR(float3 normal, float3 dirToLight)
{
return saturate(dot(normal, dirToLight));
}
// Calculates diffuse amount based on energy conservation
//
// diffuse - Diffuse amount
// F - Fresnel result from microfacet BRDF
// metalness - surface metalness amount
float3 DiffuseEnergyConserve(float3 diffuse, float3 F, float metalness)
{
return diffuse * (1 - F) * (1 - metalness);
}
// Normal Distribution Function: GGX (Trowbridge-Reitz)
//
// a - Roughness
// h - Half vector
// n - Normal
//
// D(h, n, a) = a^2 / pi * ((n dot h)^2 * (a^2 - 1) + 1)^2
float D_GGX(float3 n, float3 h, float roughness)
{
// Pre-calculations
float NdotH = saturate(dot(n, h));
float NdotH2 = NdotH * NdotH;
float a = roughness * roughness;
float a2 = max(a * a, MIN_ROUGHNESS); // Applied after remap!
// ((n dot h)^2 * (a^2 - 1) + 1)
// Can go to zero if roughness is 0 and NdotH is 1
float denomToSquare = NdotH2 * (a2 - 1) + 1;
// Final value
return a2 / (PI * denomToSquare * denomToSquare);
}
// Fresnel term - Schlick approx.
//
// v - View vector
// h - Half vector
// f0 - Value when l = n
//
// F(v,h,f0) = f0 + (1-f0)(1 - (v dot h))^5
float3 F_Schlick(float3 v, float3 h, float3 f0)
{
// Pre-calculations
float VdotH = saturate(dot(v, h));
// Final value
return f0 + (1 - f0) * pow(1 - VdotH, 5);
}
// Geometric Shadowing - Schlick-GGX
// - k is remapped to a / 2, roughness remapped to (r+1)/2 before squaring!
//
// n - Normal
// v - View vector
//
// G_Schlick(n,v,a) = (n dot v) / ((n dot v) * (1 - k) * k)
//
// Full G(n,v,l,a) term = G_SchlickGGX(n,v,a) * G_SchlickGGX(n,l,a)
float G_SchlickGGX(float3 n, float3 v, float roughness)
{
// End result of remapping:
float k = pow(roughness + 1, 2) / 8.0f;
float NdotV = saturate(dot(n, v));
// Final value
// Note: Numerator should be NdotV (or NdotL depending on parameters).
// However, these are also in the BRDF's denominator, so they'll cancel!
// We're leaving them out here AND in the BRDF function as the
// dot products can get VERY small and cause rounding errors.
return 1 / (NdotV * (1 - k) + k);
}
// Cook-Torrance Microfacet BRDF (Specular)
//
// f(l,v) = D(h)F(v,h)G(l,v,h) / 4(n dot l)(n dot v)
// - parts of the denominator are canceled out by numerator (see below)
//
// D() - Normal Distribution Function - Trowbridge-Reitz (GGX)
// F() - Fresnel - Schlick approx
// G() - Geometric Shadowing - Schlick-GGX
float3 MicrofacetBRDF(float3 n, float3 l, float3 v, float roughness, float3 f0, out float3 F_out)
{
// Other vectors
float3 h = normalize(v + l);
// Run numerator functions
float D = D_GGX(n, h, roughness);
float3 F = F_Schlick(v, h, f0);
float G = G_SchlickGGX(n, v, roughness) * G_SchlickGGX(n, l, roughness);
// Pass F out of the function for diffuse balance
F_out = F;
// Final specular formula
// Note: The denominator SHOULD contain (NdotV)(NdotL), but they'd be
// canceled out by our G() term. As such, they have been removed
// from BOTH places to prevent floating point rounding errors.
float3 specularResult = (D * F * G) / 4;
// One last non-obvious requirement: According to the rendering equation,
// specular must have the same NdotL applied as diffuse! We'll apply
// that here so that minimal changes are required elsewhere.
return specularResult * max(dot(n, l), 0);
}
#endif