pbr: parallax occlusion mapping & better env lighting

This commit is contained in:
Andrei Alexeyev 2021-07-18 10:20:50 +03:00
parent 8bb376cbd5
commit fce0f97fe1
No known key found for this signature in database
GPG key ID: 72D26128040B9690
10 changed files with 165 additions and 24 deletions

Binary file not shown.

View file

@ -0,0 +1,6 @@
filter_min = linear
filter_mag = linear
wrap_s = clamp
wrap_t = clamp

View file

@ -9,6 +9,7 @@
#define PBR_FEATURE_AMBIENT_MAP 4
#define PBR_FEATURE_ROUGHNESS_MAP 8
#define PBR_FEATURE_ENVIRONMENT_MAP 16
#define PBR_FEATURE_DEPTH_MAP 32
UNIFORM(1) int features_mask;
@ -17,16 +18,19 @@ UNIFORM(3) sampler2D normal_map;
UNIFORM(4) sampler2D ambient_map;
UNIFORM(5) sampler2D roughness_map;
UNIFORM(6) samplerCube environment_map;
UNIFORM(7) sampler2D depth_map;
UNIFORM(8) sampler2D ibl_brdf_lut;
// These either modulate their respective maps, or are used instead of them
UNIFORM(7) vec4 diffuseRGB_metallicA;
UNIFORM(8) vec4 ambientRGB_roughnessA;
UNIFORM(9) vec4 diffuseRGB_metallicA;
UNIFORM(10) vec4 ambientRGB_roughnessA;
UNIFORM(11) float depth_scale;
UNIFORM(11) mat4 inv_camera_transform; // only used if PBR_FEATURE_ENVIRONMENT_MAP is set
UNIFORM(12) mat4 inv_camera_transform; // only used if PBR_FEATURE_ENVIRONMENT_MAP is set
UNIFORM(12) int light_count;
UNIFORM(13) vec3 light_positions[PBR_MAX_LIGHTS];
UNIFORM(19) vec3 light_colors[PBR_MAX_LIGHTS]; // layout-id also depends on PBR_MAX_LIGHTS
UNIFORM(13) int light_count;
UNIFORM(14) vec3 light_positions[PBR_MAX_LIGHTS];
UNIFORM(20) vec3 light_colors[PBR_MAX_LIGHTS]; // layout-id also depends on PBR_MAX_LIGHTS
VARYING(3) vec3 pos;
VARYING(4) vec3 tangent;

View file

@ -0,0 +1,48 @@
// Based on https://learnopengl.com/Advanced-Lighting/Parallax-Mapping
#ifndef PARALLAX_MIN_LAYERS
#define PARALLAX_MIN_LAYERS 16.0
#endif
#ifndef PARALLAX_MAX_LAYERS
#define PARALLAX_MAX_LAYERS 64.0
#endif
vec2 parallaxOcclusionMap(sampler2D depthMap, float depthScale, vec2 texCoord, vec3 viewDir) {
float layersWeight = max(dot(vec3(0.0, 0.0, 1.0), viewDir), 0.0);
float numLayers = mix(PARALLAX_MAX_LAYERS, PARALLAX_MIN_LAYERS, layersWeight);
float layerDepth = 1.0 / numLayers;
float currentLayerDepth = 0.0;
// the amount to shift the texture coordinates per layer (from vector P)
vec2 P = viewDir.xy * depthScale;
vec2 deltaTexCoords = P / numLayers;
vec2 currentTexCoords = texCoord;
float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
float prevDepth = currentDepthMapValue;
while(currentLayerDepth < currentDepthMapValue) {
prevDepth = currentDepthMapValue;
// shift texture coordinates along direction of P
currentTexCoords -= deltaTexCoords;
// get depthmap value at current texture coordinates
currentDepthMapValue = texture(depthMap, currentTexCoords).r;
// get depth of next layer
currentLayerDepth += layerDepth;
}
// get texture coordinates before collision (reverse operations)
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
// get depth after and before collision for linear interpolation
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = prevDepth - currentLayerDepth + layerDepth;
// interpolation of texture coordinates
float weight = afterDepth / (afterDepth - beforeDepth);
return mix(currentTexCoords, prevTexCoords, weight);
}

View file

@ -18,11 +18,12 @@ struct PBRParams {
float roughness;
float metallic;
vec3 fragPos;
mat4 inv_camera_transform;
};
struct PBRState {
PBRParams params;
vec3 V; // fragment to view (normalized)
vec3 V; // fragment to view (normalized)
vec3 N; // fragment normal
float NdotV;
vec3 F0;
@ -55,8 +56,11 @@ float PBR_GeometrySchlickGGX(float NdotV, float k) {
}
float PBR_GeometrySmith(float NdotV, float NdotL, float roughness) {
float r = roughness + 1.0;
float k = r * r * 0.125;
// NOTE: if you mess with this function, you should also regenerate the ibl_brdf_lut texture
float k = (roughness * roughness) * 0.5;
// float r = roughness + 1.0;
// float k = r * r * 0.125;
return PBR_GeometrySchlickGGX(NdotL, k) * PBR_GeometrySchlickGGX(NdotV, k);
}
@ -69,6 +73,14 @@ vec3 PBR_FresnelSchlick(float cosTheta, vec3 F0) {
return mix(vec3(x5), vec3(1.0), F0);
}
vec3 PBR_FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
float x = 1.0 - cosTheta;
float x2 = x * x;
float x5 = x2 * x2 * x;
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * x5;
}
vec3 PBR_PointLight(PBRState st, PointLight light) {
vec3 Lo = vec3(0.0);
@ -95,6 +107,51 @@ vec3 PBR_PointLight(PBRState st, PointLight light) {
return (diffuse + specular) * radiance * NdotL;
}
vec3 PBR_ReflectionRay(PBRState st) {
return mat3(st.params.inv_camera_transform) * reflect(st.params.fragPos, st.params.normal);
}
vec3 PBR_EnvironmentLightFake(PBRState st, samplerCube envmap) {
vec3 reflected_ray = PBR_ReflectionRay(st);
vec3 reflection = texture(envmap, fixCubeCoord(reflected_ray)).rgb;
float r = (1 - st.params.roughness);
return (r * r) * reflection * mix(vec3(0.5), st.params.albedo, st.params.metallic);
}
vec3 PBR_EnvironmentLight(
PBRState st,
sampler2D brdf_lut,
// samplerCube irradiance_map,
samplerCube prefiltered_cubemap
) {
// Partial IBL implementation (TODO):
// * No diffuse, specular only
// * "Prefiltered" cubemap is not actually prefiltered - mipmaps are used as usual, and roughness is not taken into account. This makes reflections much sharper than they should be.
vec3 reflected_ray = PBR_ReflectionRay(st);
vec3 R = fixCubeCoord(reflected_ray);
vec3 F = PBR_FresnelSchlickRoughness(st.NdotV, st.F0, st.params.roughness);
// Diffuse
#if 0
vec3 kS = F;
vec3 kD = 1.0 - kS;
kD *= 1.0 - st.params.metallic;
vec3 irradiance = texture(irradiance_map, st.N).rgb;
vec3 diffuse = irradiance * st.params.albedo;
#endif
// Specular
const float MAX_REFLECTION_LOD = 8.0; // TODO pass as uniform or use LOD query extension
// vec3 reflection = textureLod(prefiltered_cubemap, R, st.params.roughness * MAX_REFLECTION_LOD).rgb;
vec3 reflection = texture(prefiltered_cubemap, R).rgb;
vec2 brdf = texture(brdf_lut, vec2(st.NdotV, st.params.roughness)).rg;
vec3 specular = reflection * (F * brdf.x + brdf.y);
// return kD * diffuse + specular;
return specular;
}
vec3 PBR_TonemapReinhard(vec3 color) {
return color / (color + 1.0);
}

View file

@ -1,14 +1,29 @@
#version 330 core
#include "lib/render_context.glslh"
#include "lib/parallaxmap.glslh"
#include "interface/pbr.glslh"
void main(void) {
float roughness = ambientRGB_roughnessA.a;
float alpha;
mat3 tbn = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
vec2 uv = texCoord;
if(bool(features_mask & PBR_FEATURE_DEPTH_MAP)) {
vec3 vdir = normalize(transpose(tbn) * -pos);
uv = parallaxOcclusionMap(depth_map, depth_scale, uv, vdir);
/*
if(uv.x > 1.0 || uv.y > 1.0 || uv.x < 0.0 || uv.y < 0.0) {
discard;
}
*/
}
if(bool(features_mask & PBR_FEATURE_ROUGHNESS_MAP)) {
vec4 roughness_sample = texture(roughness_map, texCoord);
vec4 roughness_sample = texture(roughness_map, uv);
roughness *= roughness_sample.r;
alpha = roughness_sample.a;
@ -24,46 +39,41 @@ void main(void) {
vec3 diffuse = diffuseRGB_metallicA.rgb;
if(bool(features_mask & PBR_FEATURE_DIFFUSE_MAP)) {
diffuse *= texture(diffuse_map, texCoord).rgb;
diffuse *= texture(diffuse_map, uv).rgb;
}
vec3 ambient = ambientRGB_roughnessA.rgb;
if(bool(features_mask & PBR_FEATURE_AMBIENT_MAP)) {
ambient *= texture(ambient_map, texCoord).rgb;
ambient *= texture(ambient_map, uv).rgb;
}
vec3 tbn_normal;
if(bool(features_mask & PBR_FEATURE_NORMAL_MAP)) {
tbn_normal = sample_normalmap(normal_map, texCoord);
tbn_normal = sample_normalmap(normal_map, uv);
} else {
tbn_normal = vec3(0, 0, 1);
}
mat3 tbn = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
PBRParams p;
p.fragPos = pos;
p.albedo = diffuse;
p.roughness = roughness;
p.metallic = metallic;
p.normal = tbn * tbn_normal;
p.inv_camera_transform = inv_camera_transform;
PBRState pbr = PBR(p);
vec3 Lo = vec3(0.0);
vec3 color = ambient;
for(int i = 0; i < light_count; ++i) {
Lo += PBR_PointLight(pbr, PointLight(light_positions[i], light_colors[i]));
color += PBR_PointLight(pbr, PointLight(light_positions[i], light_colors[i]));
}
vec3 color = ambient + Lo;
if(bool(features_mask & PBR_FEATURE_ENVIRONMENT_MAP)) {
vec3 reflected_ray = mat3(inv_camera_transform) * reflect(pos, p.normal);
vec3 reflection = texture(environment_map, fixCubeCoord(reflected_ray)).rgb;
float r = (1 - p.roughness);
color += (r * r) * reflection * mix(vec3(0.5), p.albedo, p.metallic);
color += PBR_EnvironmentLight(pbr, ibl_brdf_lut, environment_map);
}
color = PBR_TonemapReinhard(color);

View file

@ -44,9 +44,10 @@ struct mat_load_data {
char *normal_map;
char *ambient_map;
char *roughness_map;
char *depth_map;
};
char *maps[4];
char *maps[5];
};
};
@ -68,6 +69,7 @@ static void material_load_stage1(ResourceLoadState *st) {
.ambient_color = { 1, 1, 1 },
.roughness_value = 1,
.metallic_value = 0,
.depth_scale = 0,
};
if(!parse_keyvalue_file_with_spec(st->path, (KVSpec[]) {
@ -75,10 +77,13 @@ static void material_load_stage1(ResourceLoadState *st) {
{ "normal_map", .out_str = &ld->normal_map },
{ "ambient_map", .out_str = &ld->ambient_map },
{ "roughness_map", .out_str = &ld->roughness_map },
{ "depth_map", .out_str = &ld->depth_map },
{ "diffuse_color", .callback = kvparser_vec3, .callback_data = ld->mat->diffuse_color },
{ "ambient_color", .callback = kvparser_vec3, .callback_data = ld->mat->ambient_color },
{ "roughness", .out_float = &ld->mat->roughness_value },
{ "metallic", .out_float = &ld->mat->metallic_value },
{ "depth_scale", .out_float = &ld->mat->depth_scale },
{ NULL },
})) {
free_mat_load_data(ld);
log_error("Failed to parse material file '%s'", st->path);
@ -113,6 +118,7 @@ static void material_load_stage2(ResourceLoadState *st) {
LOADMAP(normal);
LOADMAP(ambient);
LOADMAP(roughness);
LOADMAP(depth);
res_load_finished(st, ld->mat);
free_mat_load_data(ld);

View file

@ -17,11 +17,13 @@ typedef struct PBRMaterial {
Texture *normal_map;
Texture *ambient_map;
Texture *roughness_map;
Texture *depth_map;
vec3 diffuse_color;
vec3 ambient_color;
float roughness_value;
float metallic_value;
float depth_scale;
} PBRMaterial;
extern ResourceHandler material_res_handler;

View file

@ -159,7 +159,14 @@ void pbr_set_material_uniforms(const PBRMaterial *m, const PBREnvironment *env)
flags |= PBR_FEATURE_AMBIENT_MAP;
}
if(m->depth_map && m->depth_scale) {
r_uniform_sampler("depth_map", m->depth_map);
r_uniform_float("depth_scale", m->depth_scale);
flags |= PBR_FEATURE_DEPTH_MAP;
}
if(env->environment_map) {
r_uniform_sampler("ibl_brdf_lut", "ibl_brdf_lut");
r_uniform_sampler("environment_map", env->environment_map);
r_uniform_mat4("inv_camera_transform", (vec4*)env->cam_inverse_transform);
flags |= PBR_FEATURE_ENVIRONMENT_MAP;

View file

@ -54,6 +54,7 @@ typedef struct PointLight3D {
#define PBR_FEATURE_AMBIENT_MAP 4
#define PBR_FEATURE_ROUGHNESS_MAP 8
#define PBR_FEATURE_ENVIRONMENT_MAP 16
#define PBR_FEATURE_DEPTH_MAP 32
typedef struct PBREnvironment {
mat4 cam_inverse_transform;