Grafikprogrammierung
Bildbasierte Beleuchtung
Thorsten Thormählen
09. Januar 2023
Teil 10, Kapitel 2
Thorsten Thormählen
09. Januar 2023
Teil 10, Kapitel 2
Dies ist die Druck-Ansicht.
Weiterschalten der Folien durch die → Taste oder
durch das Klicken auf den rechten Folienrand.
Das Weiterschalten der Folien kann ebenfalls durch das Klicken auf den rechten bzw. linken Folienrand erfolgen.
Typ | Schriftart | Beispiele |
---|---|---|
Variablen (Skalare) | kursiv | $a, b, x, y$ |
Funktionen | aufrecht | $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$ |
Vektoren | fett, Elemente zeilenweise | $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$ |
Matrizen | Schreibmaschine | $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$ |
Mengen | kalligrafisch | $\mathcal{A}, \{a, b\} \in \mathcal{B}$ |
Zahlenbereiche, Koordinatenräume | doppelt gestrichen | $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$ |
Symbol | Bedeutung |
---|---|
$\Omega$ | Raumwinkel |
$\theta$ | Polarwinkel im Kugelkoordinatensystem |
$\phi$ | Azimutwinkel im Kugelkoordinatensystem |
$\Phi$ | Strahlungsfluss |
$I$ | Strahlstärke |
$E$ | Bestrahlungsstärke |
$L$ | Strahldichte |
$\mathrm{f}_r$ | BRDF (Bidirectional Reflection Distribution Function) |
$\mathrm{f}_d$ | Diffuser Anteil der BRDF |
$\mathrm{f}_s$ | Spekularer Anteil der BRDF |
Symbol | Bedeutung |
---|---|
$\mathbf{n}$ | Oberflächennormale |
$\mathbf{v}$ | Einheitsvektor in Sichtrichtung |
$\mathbf{l}$ | Einheitsvektor in Richtung der Lichtquelle |
$\eta$ | Brechungsindex |
$F$ | Fresnel Reflexionsgrad |
$\mathbf{h}$ | Winkelhalbierende (halfway-vector) zwischen Licht- und Sichtrichtung |
$(\dots)_+$ | Rampenfunktion |
$\langle \mathbf{a}\cdot \mathbf{b}\rangle$ | Skalarprodukt |
WDF / Abbildung | Draufsicht | Seitenansicht |
---|---|---|
Gleichverteilt in Polarwinkeln
$\mathrm{p}(\theta, \phi) = \frac{1}{2\pi} \frac{1}{\pi/2}$
$\phi = 2 \pi \,u$
$\theta = \frac{\pi}{2}\, v$
(entspricht Riemann Summe)
|
![]() |
![]() |
Gleichverteilt auf Halbkugel
$\mathrm{p}(\theta, \phi) = \frac{1}{2\pi} \,\sin(\theta)$
$\phi = 2 \pi \,u$
$\theta = \arccos(1 - v)$
|
![]() |
![]() |
WDF / Abbildung | Draufsicht | Seitenansicht |
---|---|---|
Phong BRDF (Diffuser Anteil)
$\mathrm{p}(\theta, \phi) = \frac{1}{\pi} \, \cos(\theta) \,\sin(\theta)$
$\phi = 2 \pi \,u$
$\theta = \arcsin(\sqrt{v})$
|
![]() |
![]() |
Phong BRDF (Spekularer Anteil)
$\mathrm{p}(\theta, \phi) = \frac{n_s + 1}{2 \pi} \, \cos(\theta)^{n_s} \,\sin(\theta)$
$\phi = 2 \pi \,u$
$\theta = \arccos\left((1-v)^{\frac{1}{n_s+1}}\right)$
|
![]() |
![]() |
WDF / Abbildung | Draufsicht | Seitenansicht |
---|---|---|
Mikrofacetten GGX Verteilung mit $r_p = 0.5$ und $\alpha = r_p^2$
$\mathrm{D}_{\tiny \mbox{GGX}}(\theta) = \frac{\alpha^2}{\pi \left(\cos^2(\theta) (\alpha^2-1)+1\right)^2}$
$\mathrm{p}(\theta, \phi) = \mathrm{D}_{\tiny \mbox{GGX}}(\theta)\cos(\theta)\sin(\theta)$
$\phi = 2 \pi \,u$
$\theta = \arccos\left(\sqrt{\frac{1 - v}{v (\alpha^2-1) + 1} }\right)$
|
![]() |
![]() |
Mikrofacetten GGX Verteilung mit $r_p = 0.25$ |
![]() |
![]() |
Index $n$ | Zahlwert (Basis 2) | Gespiegelt | $h_2(n)$ |
---|---|---|---|
1 | 1 | 0.1 = 1/2 | 0.5 |
2 | 10 | 0.01 = 1/4 | 0.25 |
3 | 11 | 0.11 = 3/4 | 0.75 |
4 | 100 | 0.001 = 1/8 | 0.125 |
5 | 101 | 0.101 = 1/2 + 1/8 | 0.625 |
6 | 110 | 0.011 = 1/4 + 1/8 | 0.375 |
7 | 111 | 0.111 = 1/2 + 1/4 + 1/8 | 0.875 |
Index $n$ | Zahlwert (Basis 3) | Gespiegelt | $h_3(n)$ |
---|---|---|---|
1 | 1 | 0.1 = 1/3 | 0.333 |
2 | 2 | 0.2 = 2/3 | 0.666 |
3 | 10 | 0.01 = 1/9 | 0.111 |
4 | 11 | 0.11 = 1/3 + 1/9 | 0.444 |
5 | 12 | 0.21 = 2/3 + 1/9 | 0.777 |
6 | 20 | 0.02 = 2/9 | 0.222 |
7 | 21 | 0.12 = 1/3 + 2/9 | 0.555 |
8 | 22 | 0.22 = 2/3 + 2/9 | 0.888 |
Important Sampling des diffusen Anteils:
#version 300 es precision highp float; out vec4 outColor; in vec2 tc; // texture coordinate of the output image in range [0.0, 1.0] uniform int samples; // number of samples uniform float envMapLevel; // environment map level uniform sampler2D envMapImage; // environment image const float PI = 3.1415926535897932384626433832795; vec2 directionToSphericalEnvmap(vec3 dir) { float s = 1.0 - mod(1.0 / (2.0*PI) * atan(dir.y, dir.x), 1.0); float t = 1.0 / (PI) * acos(-dir.z); return vec2(s, t); } mat3 getNormalSpace(in vec3 normal) { vec3 someVec = vec3(1.0, 0.0, 0.0); float dd = dot(someVec, normal); vec3 tangent = vec3(0.0, 1.0, 0.0); if(abs(dd) > 1e-8) { tangent = normalize(cross(someVec, normal)); } vec3 bitangent = cross(normal, tangent); return mat3(tangent, bitangent, normal); } // from http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html // Hacker's Delight, Henry S. Warren, 2001 float radicalInverse(uint bits) { bits = (bits << 16u) | (bits >> 16u); bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } vec2 hammersley(uint n, uint N) { return vec2(float(n) / float(N), radicalInverse(n)); } void main() { float thetaN = PI * (1.0 - tc.y); float phiN = 2.0 * PI * (1.0 - tc.x); vec3 normal = vec3(sin(thetaN) * cos(phiN), sin(thetaN) * sin(phiN), cos(thetaN)); mat3 normalSpace = getNormalSpace(normal); vec3 result = vec3(0.0); uint N = uint(samples); float r = random2(tc); for(uint n = 1u; n <= N; n++) { vec2 p = hammersley(n, N); float theta = asin(sqrt(p.y)); float phi = 2.0 * PI * p.x; vec3 pos = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); vec3 posGlob = normalSpace * pos; vec2 uv = directionToSphericalEnvmap(posGlob); vec3 radiance = textureLod(envMapImage, uv, envMapLevel).rgb; result += radiance; } result = result / float(samples); outColor.rgb = result; outColor.a = 1.0; }
Important Sampling des spekularen Anteils:
#version 300 es precision highp float; out vec4 outColor; in vec2 tc; // texture coordinate of the output image in range [0.0, 1.0] uniform int samples; // number of samples uniform float shininess; // specular shininess exponent uniform float envMapLevel; // environment map level uniform sampler2D envMapImage; // environment image const float PI = 3.1415926535897932384626433832795; vec2 directionToSphericalEnvmap(vec3 dir) { float s = 1.0 - mod(1.0 / (2.0*PI) * atan(dir.y, dir.x), 1.0); float t = 1.0 / (PI) * acos(-dir.z); return vec2(s, t); } mat3 getNormalSpace(in vec3 normal) { vec3 someVec = vec3(1.0, 0.0, 0.0); float dd = dot(someVec, normal); vec3 tangent = vec3(0.0, 1.0, 0.0); if(abs(dd) > 1e-8) { tangent = normalize(cross(someVec, normal)); } vec3 bitangent = cross(normal, tangent); return mat3(tangent, bitangent, normal); } // from http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html // Hacker's Delight, Henry S. Warren, 2001 float radicalInverse(uint bits) { bits = (bits << 16u) | (bits >> 16u); bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } vec2 hammersley(uint n, uint N) { return vec2(float(n) / float(N), radicalInverse(n)); } void main() { float thetaN = PI * (1.0 - tc.y); float phiN = 2.0 * PI * (1.0 - tc.x); vec3 normal = vec3(sin(thetaN) * cos(phiN), sin(thetaN) * sin(phiN), cos(thetaN)); mat3 normalSpace = getNormalSpace(normal); vec3 result = vec3(0.0); uint N = uint(samples); float r = random2(tc); for(uint n = 1u; n <= N; n++) { vec2 p = hammersley(n, N); float theta = acos(pow(1.0 - p.y, 1.0/(shininess + 1.0))); float phi = 2.0 * PI * p.x; vec3 pos = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); vec3 posGlob = normalSpace * pos; vec2 uv = directionToSphericalEnvmap(posGlob); vec3 radiance = textureLod(envMapImage, uv, envMapLevel).rgb; result += radiance; } result = result / float(samples) * (shininess + 2.0) / (shininess + 1.0); outColor.rgb = result; outColor.a = 1.0; }
Vertex Shader für das Ergebnisbild:
#version 300 es precision highp float; in vec3 position; // input vertex position from mesh in vec2 texcoord; // input vertex texture coordinate from mesh in vec3 normal; // input vertex normal from mesh uniform mat4 cameraLookAt; //camera look at matrix uniform mat4 cameraProjection; //camera projection matrix uniform mat4 meshTransform0; // mesh0 transformation uniform mat4 meshTransform1; // mesh1 transformation uniform mat4 meshTransform0TransposedInverse; uniform mat4 meshTransform1TransposedInverse; uniform int gsnMeshGroup; out vec2 tc; // output texture coordinate of vertex out vec3 wfn; // output fragment normal of vertex in world space out vec3 vertPos; // output 3D position in world space void main(){ mat4 meshTransform; mat4 meshTransformTransposedInverse; if(gsnMeshGroup == 0) { // transformation of background sphere meshTransform = meshTransform0; meshTransformTransposedInverse = meshTransform0TransposedInverse; } else { // transformation of mesh meshTransform = meshTransform1; meshTransformTransposedInverse = meshTransform1TransposedInverse; } tc = texcoord; wfn = vec3(meshTransformTransposedInverse * vec4(normal, 0.0)); vec4 vertPos4 = meshTransform * vec4(position, 1.0); vertPos = vec3(vertPos4) / vertPos4.w; gl_Position = cameraProjection * cameraLookAt * vertPos4; }
Fragment Shader für das Ergebnisbild:
#version 300 es precision highp float; precision highp int; out vec4 outColor; #define M_PI 3.1415926535897932384626433832795 in vec2 tc; // texture coordinate of pixel (interpolated) in vec3 wfn; // fragment normal of pixel in world space (interpolated) in vec3 vertPos; // fragment vertex position in world space (interpolated) uniform sampler2D envmapBackground; // min_filter="LINEAR" mag_filter="LINEAR" uniform bool showBackground; // defaultval="true" uniform sampler2D envmapDiffuse; // min_filter="LINEAR" mag_filter="LINEAR" uniform sampler2D envmapSpecular; // min_filter="LINEAR" mag_filter="LINEAR" uniform float diffuseMix; // weighting factor of diffuse color uniform vec4 diffuseColor; // diffuse color uniform float specularMix; // weighting factor of specular color uniform vec4 specularColor; // pecularColor uniform vec3 cameraPos; // camera position in global coordinate system uniform int gsnMeshGroup; vec2 directionToSphericalEnvmap(vec3 dir) { float s = 1.0 - mod(1.0 / (2.0*M_PI) * atan(dir.y, dir.x), 1.0); float t = 1.0 / (M_PI) * acos(-dir.z); return vec2(s, t); } void main() { vec3 normal = normalize(wfn.xyz); vec3 viewDir = normalize(cameraPos - vertPos); vec3 rv = reflect(-viewDir, normal); if(gsnMeshGroup == 0) { if(showBackground) { // color of envmap sphere outColor.rgb = texture(envmapBackground, vec2(1.0-tc.x, tc.y)).rgb; outColor.a = 1.0; } else { discard; } } else { vec3 diff = texture(envmapDiffuse, directionToSphericalEnvmap(normal)).rgb; vec3 rd = diffuseMix * pow(diffuseColor.rgb, vec3(2.2)); vec3 rs = specularMix * pow(specularColor.rgb, vec3(2.2)); // shading front-facing vec3 color = rd * diff; float rn = dot(rv, normal); if(rn > 0.0) { vec3 spec = texture(envmapSpecular, directionToSphericalEnvmap(rv)).rgb; color += rs * rn * spec; } // shading back-facing if(dot(viewDir, normal) < -0.1) { color = 0.1 * rs; } outColor.rgb = pow(color, vec3(1.0/2.2)); outColor.a = 1.0; } }
with $\theta^\ast = 2 \,\theta_h^\ast$ and $\phi^\ast = \phi_h^\ast$
Fragment Shader (Vertex Shader, wie bisher):
#version 300 es precision highp float; precision highp int; out vec4 outColor; #define PI 3.1415926535897932384626433832795 in vec2 tc; // texture coordinate of pixel (interpolated) in vec3 wfn; // fragment normal of pixel (interpolated) in vec3 vertPos; // fragment vertex position (interpolated) uniform sampler2D envMapImage; // environment images uniform sampler2D baseColorTexture; // base color uniform sampler2D roughnessTexture; // roughness texture uniform sampler2D metallicTexture; // metallic parameter uniform sampler2D emissionTexture; // emission texture uniform float reflectance; // Fresnel reflectance uniform bool showBackground; uniform int samplesSpec; //number of samples uniform float envLevelSpec; // level for envmap lookup uniform int samplesDiff; // number of samples uniform float envLevelDiff; // level for envmap lookup uniform vec3 cameraPos; // camera position in global coordinate system uniform int gsnMeshGroup; vec2 directionToSphericalEnvmap(vec3 dir) { float s = 1.0 - mod(1.0 / (2.0*PI) * atan(dir.y, dir.x), 1.0); float t = 1.0 / (PI) * acos(-dir.z); return vec2(s, t); } mat3 getNormalSpace(in vec3 normal) { vec3 someVec = vec3(1.0, 0.0, 0.0); float dd = dot(someVec, normal); vec3 tangent = vec3(0.0, 1.0, 0.0); if(abs(dd) > 1e-8) { tangent = normalize(cross(someVec, normal)); } vec3 bitangent = cross(normal, tangent); return mat3(tangent, bitangent, normal); } // from http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html // Hacker's Delight, Henry S. Warren, 2001 float radicalInverse(uint bits) { bits = (bits << 16u) | (bits >> 16u); bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } vec2 hammersley(uint n, uint N) { return vec2(float(n) / float(N), radicalInverse(n)); } float random2(vec2 n) { return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); } float G1_GGX_Schlick(float NdotV, float roughness) { float r = roughness; // original //float r = 0.5 + 0.5 * roughness; // Disney remapping float k = (r * r) / 2.0; float denom = NdotV * (1.0 - k) + k; return NdotV / denom; } float G_Smith(float NoV, float NoL, float roughness) { float g1_l = G1_GGX_Schlick(NoL, roughness); float g1_v = G1_GGX_Schlick(NoV, roughness); return g1_l * g1_v; } vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); } // adapted from "Real Shading in Unreal Engine 4", Brian Karis, Epic Games vec3 specularIBLReference(vec3 F0 , float roughness, vec3 N, vec3 V) { mat3 normalSpace = getNormalSpace(N); vec3 result = vec3(0.0); uint sampleCount = uint(samplesSpec); float r = random2(tc); for(uint n = 1u; n <= sampleCount; n++) { //vec2 p = hammersley(n, N); vec2 p = mod(hammersley(n, sampleCount) + r, 1.0); float a = roughness * roughness; float theta = acos(sqrt((1.0 - p.y) / (1.0 + (a * a - 1.0) * p.y))); float phi = 2.0 * PI * p.x; // sampled h direction in normal space vec3 Hn = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); // sampled h direction in world space vec3 H = normalSpace * Hn; vec3 L = 2.0 * dot(V, H) * H - V; // all required dot products float NoV = clamp(dot(N, V), 0.0, 1.0); float NoL = clamp(dot(N, L), 0.0, 1.0); float NoH = clamp(dot(N, H), 0.0, 1.0); float VoH = clamp(dot(V, H), 0.0, 1.0); if(NoL > 0.0 && NoH > 0.0 && NoV > 0.0 && VoH > 0.0) { // geometry term float G = G_Smith(NoV, NoL, roughness); // Fresnel term vec3 F = fresnelSchlick(VoH, F0); vec2 uv = directionToSphericalEnvmap(L); vec3 radiance = textureLod(envMapImage, uv, envLevelSpec).rgb; result += radiance * F * G * VoH / (NoH * NoV); } } result = result / float(sampleCount); return result; } vec3 diffuseIBLReference(vec3 normal) { mat3 normalSpace = getNormalSpace(normal); vec3 result = vec3(0.0); uint sampleCount = uint(samplesDiff); float r = random2(tc); for(uint n = 1u; n <= sampleCount; n++) { //vec2 p = hammersley(n, N); vec2 p = mod(hammersley(n, sampleCount) + r, 1.0); float theta = asin(sqrt(p.y)); float phi = 2.0 * PI * p.x; vec3 pos = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); vec3 posGlob = normalSpace * pos; vec2 uv = directionToSphericalEnvmap(posGlob); vec3 radiance = textureLod(envMapImage, uv, envLevelDiff).rgb; result += radiance; } result = result / float(sampleCount); return result; } void main() { vec3 normal = normalize(wfn); vec3 viewDir = normalize(cameraPos - vertPos); if(gsnMeshGroup == 0) { if(showBackground) { // color of envmap sphere outColor.rgb = texture(envMapImage, vec2(1.0-tc.x, tc.y)).rgb; outColor.a = 1.0; } else { discard; } } else { vec3 baseColor = pow(texture(baseColorTexture, tc).rgb, vec3(2.2)); vec3 emission = pow(texture(emissionTexture, tc).rgb, vec3(2.2));; float roughness = texture(roughnessTexture, tc).r; float metallic = texture(metallicTexture, tc).r; // F0 for dielectics in range [0.0, 0.16] // default FO is (0.16 * 0.5^2) = 0.04 vec3 f0 = vec3(0.16 * (reflectance * reflectance)); // in case of metals, baseColor contains F0 f0 = mix(f0, baseColor, metallic); // compute diffuse and specular factors vec3 F = fresnelSchlick(max(dot(normal, viewDir), 0.0), f0); vec3 kS = F; vec3 kD = 1.0 - kS; kD *= 1.0 - metallic; vec3 specular = specularIBLReference(f0, roughness, normal, viewDir); vec3 diffuse = diffuseIBLReference(normal); vec3 color = emission + kD * baseColor * diffuse + specular; outColor.rgb = pow(color, vec3(1.0/2.2)); outColor.a = 1.0; } }
// adapted from "Real Shading in Unreal Engine 4", Brian Karis, Epic Games vec3 prefilterEnvMap(float roughness, vec3 R) { vec3 N = R; vec3 V = R; uint sampleCount = uint(samples); float r = random2(tc); mat3 normalSpace = getNormalSpace(N); float totalWeight = 0.0; vec3 result = vec3(0.0); for(uint n = 1u; n <= sampleCount; n++) { //vec2 p = hammersley(n, N); vec2 p = mod(hammersley(n, sampleCount) + r, 1.0); float a = roughness * roughness; float theta = acos(sqrt((1.0 - p.y) / (1.0 + (a * a - 1.0) * p.y))); float phi = 2.0 * PI * p.x; // sampled h direction in normal space vec3 Hn = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); // sampled h direction in world space vec3 H = normalSpace * Hn; vec3 L = 2.0 * dot(V, H) * H - V; float NoL = max(dot(N, L), 0.0); if(NoL > 0.0) { vec2 uv = directionToSphericalEnvmap(L); vec3 radiance = textureLod(envMapImage, uv, envMapLevel).rgb; result += radiance * NoL; totalWeight += NoL; } } result = result / totalWeight; return result; }
// adapted from "Real Shading in Unreal Engine 4", Brian Karis, Epic Games vec2 integrateBRDF(float roughness, float NoV) { vec3 V; V.x = sqrt(1.0 - NoV * NoV); // sin V.y = 0.0; V.z = NoV; // cos vec2 result = vec2(0.0); uint sampleCount = uint(samples); for(uint n = 1u; n <= sampleCount; n++) { vec2 p = hammersley(n, sampleCount); float a = roughness * roughness; float theta = acos(sqrt((1.0 - p.y) / (1.0 + (a * a - 1.0) * p.y))); float phi = 2.0 * PI * p.x; // sampled h direction in normal space vec3 H = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); vec3 L = 2.0 * dot(V, H) * H - V; // because N = vec3(0.0, 0.0, 1.0) follows float NoL = clamp(L.z, 0.0, 1.0); float NoH = clamp(H.z, 0.0, 1.0); float VoH = clamp(dot(V, H), 0.0, 1.0); if(NoL > 0.0) { float G = G_Smith(NoV, NoL, roughness); float G_Vis = G * VoH / (NoH * NoV); float Fc = pow(1.0 - VoH, 5.0); result.x += (1.0 - Fc) * G_Vis; result.y += Fc * G_Vis; } } result = result / float(sampleCount); return result; }
Fragment Shader (Vertex Shader, wie bisher):
#version 300 es precision highp float; precision highp int; out vec4 outColor; #define PI 3.1415926535897932384626433832795 in vec2 tc; // texture coordinate of pixel (interpolated) in vec3 wfn; // fragment normal of pixel (interpolated) in vec3 vertPos; // fragment vertex position (interpolated) uniform sampler2D envmapImage; uniform sampler2D prefilteredEnvmap; uniform sampler2D brdfIntegrationMap; uniform sampler2D diffuseMap; uniform sampler2D baseColorTexture; uniform sampler2D roughnessTexture; // roughness texture uniform sampler2D metallicTexture; // metallic texture uniform sampler2D emissionTexture; // emission texture" uniform float reflectance; // Fresnel reflectance uniform bool showBackground; uniform vec3 cameraPos; // camera position in global coordinate system uniform int mipCount; // number of usable mipmap levels uniform int gsnMeshGroup; vec2 directionToSphericalEnvmap(vec3 dir) { float s = 1.0 - mod(1.0 / (2.0*PI) * atan(dir.y, dir.x), 1.0); float t = 1.0 / (PI) * acos(-dir.z); return vec2(s, t); } // adapted from "Real Shading in Unreal Engine 4", Brian Karis, Epic Games vec3 specularIBL(vec3 F0 , float roughness, vec3 N, vec3 V) { float NoV = clamp(dot(N, V), 0.0, 1.0); vec3 R = reflect(-V, N); vec2 uv = directionToSphericalEnvmap(R); vec3 prefilteredColor = textureLod(prefilteredEnvmap, uv, roughness*float(mipCount)).rgb; vec4 brdfIntegration = texture(brdfIntegrationMap, vec2(NoV, roughness)); return prefilteredColor * ( F0 * brdfIntegration.x + brdfIntegration.y ); } vec3 diffuseIBL(vec3 normal) { vec2 uv = directionToSphericalEnvmap(normal); return texture(diffuseMap, uv).rgb; } vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); } void main() { vec3 normal = normalize(wfn); vec3 viewDir = normalize(cameraPos - vertPos); if(gsnMeshGroup == 0) { if(showBackground) { // color of envmap sphere outColor.rgb = texture(envmapImage, vec2(1.0-tc.x, tc.y)).rgb; outColor.a = 1.0; } else { discard; } } else { vec3 baseColor = pow(texture(baseColorTexture, tc).rgb, vec3(2.2)); vec3 emission = pow(texture(emissionTexture, tc).rgb, vec3(2.2));; float roughness = texture(roughnessTexture, tc).r; float metallic = texture(metallicTexture, tc).r; // F0 for dielectics in range [0.0, 0.16] // default FO is (0.16 * 0.5^2) = 0.04 vec3 f0 = vec3(0.16 * (reflectance * reflectance)); // in case of metals, baseColor contains F0 f0 = mix(f0, baseColor, metallic); // compute diffuse and specular factors vec3 F = fresnelSchlick(max(dot(normal, viewDir), 0.0), f0); vec3 kS = F; vec3 kD = 1.0 - kS; kD *= 1.0 - metallic; vec3 specular = specularIBL(f0, roughness, normal, viewDir); vec3 diffuse = diffuseIBL(normal); vec3 color = emission + kD * baseColor * diffuse + specular; outColor.rgb = pow(color, vec3(1.0/2.2)); outColor.a = 1.0; } }
Anregungen oder Verbesserungsvorschläge können auch gerne per E-mail an mich gesendet werden: Kontakt