pbr: parallax occlusion mapping & better env lighting
This commit is contained in:
parent
8bb376cbd5
commit
fce0f97fe1
10 changed files with 165 additions and 24 deletions
BIN
resources/00-taisei.pkgdir/gfx/ibl_brdf_lut.basis
Normal file
BIN
resources/00-taisei.pkgdir/gfx/ibl_brdf_lut.basis
Normal file
Binary file not shown.
6
resources/00-taisei.pkgdir/gfx/ibl_brdf_lut.tex
Normal file
6
resources/00-taisei.pkgdir/gfx/ibl_brdf_lut.tex
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
filter_min = linear
|
||||
filter_mag = linear
|
||||
wrap_s = clamp
|
||||
wrap_t = clamp
|
||||
|
|
@ -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;
|
||||
|
|
48
resources/00-taisei.pkgdir/shader/lib/parallaxmap.glslh
Normal file
48
resources/00-taisei.pkgdir/shader/lib/parallaxmap.glslh
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue