From ff72ac6bc9bc30d3e3100cbb1e096a4eff429e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Wed, 27 Mar 2024 19:02:07 +0100 Subject: [PATCH] gfx: generate brdf lut --- engine/art/shaders/brdf.glsl | 100 ++++++++++++++++++++++++++ engine/art/shaders/fs_32_4_model.glsl | 21 +----- engine/joint/v4k.h | 66 ++++++++++++++--- engine/split/v4k_main.c | 3 + engine/split/v4k_render.c | 63 +++++++++++++--- engine/v4k.c | 66 ++++++++++++++--- 6 files changed, 271 insertions(+), 48 deletions(-) create mode 100644 engine/art/shaders/brdf.glsl diff --git a/engine/art/shaders/brdf.glsl b/engine/art/shaders/brdf.glsl new file mode 100644 index 0000000..8283524 --- /dev/null +++ b/engine/art/shaders/brdf.glsl @@ -0,0 +1,100 @@ +#version 400 +const float PI = 3.1415926536; + +in vec2 uv; +out vec2 fragcolor; + +float RadicalInverse_VdC(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 i, uint N) { + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} + +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} + +float GeometrySchlickGGX(float NdotV, float roughness) { + float a = roughness; + float k = (a * a) / 2.0; + + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return nom / denom; +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +vec2 GenerateBRDF(float NdotV, float roughness) { + vec3 V; + V.x = sqrt(1.0 - NdotV*NdotV); + V.y = 0.0; + V.z = NdotV; + + float A = 0.0; + float B = 0.0; + + vec3 N = vec3(0.0, 0.0, 1.0); + + const uint SAMPLE_COUNT = 1024u; + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if(NdotL > 0.0) + { + float G = GeometrySmith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + return vec2(A, B); +} + +void main() { + fragcolor = GenerateBRDF(uv.x, uv.y); +} diff --git a/engine/art/shaders/fs_32_4_model.glsl b/engine/art/shaders/fs_32_4_model.glsl index 234a407..1c99103 100644 --- a/engine/art/shaders/fs_32_4_model.glsl +++ b/engine/art/shaders/fs_32_4_model.glsl @@ -227,7 +227,7 @@ float geometry_smith( vec3 N, vec3 V, vec3 L, float roughness ) vec2 sphere_to_polar( vec3 normal ) { normal = normalize( normal ); - return vec2( ( atan( normal.z, normal.x ) + skysphere_rotation ) / PI / 2.0 + 0.5, acos( normal.y ) / PI ); + return vec2( ( atan( normal.z, normal.x ) - PI*0.5f ) / PI / 2.0 + 0.5, acos( normal.y ) / PI ); } // Our vertically GL_CLAMPed textures seem to blend towards black when sampling the half-pixel edge. @@ -285,7 +285,7 @@ vec3 sample_irradiance_fast( vec3 normal, vec3 vertex_tangent ) // Sample the irradiance map if it exists, otherwise fall back to blurred reflection map. if ( has_tex_skyenv ) { - vec2 polar = sphere_to_polar_clamp_y( normal, 180.0 ); + vec2 polar = sphere_to_polar( normal ); return textureLod( tex_skyenv, polar, 0.0 ).rgb * exposure; } else @@ -473,7 +473,7 @@ void main(void) } else if( map_roughness.has_tex ) { //< @r-lyeh, metalness B, roughness G, (@todo: self-shadowing occlusion R; for now, any of R/B are metallic) - metallic = sample_colormap( map_roughness, v_texcoord ).b + sample_colormap( map_roughness, v_texcoord ).r; + metallic = sample_colormap( map_roughness, v_texcoord ).b;// + sample_colormap( map_roughness, v_texcoord ).r; roughness = sample_colormap( map_roughness, v_texcoord ).g; } @@ -708,21 +708,6 @@ void main(void) // Technically this alpha may be too transparent, if there is a lot of reflected light we wouldn't // see the background, maybe we can approximate it well enough by adding a fresnel term fragcolor = vec4( color * shadowing().xyz, alpha ); - - // rimlight - #ifdef RIM - { - vec3 n = normalize(mat3(M) * v_normal_ws); // convert normal to view space - vec3 p = (M * vec4(v_position,1.0)).xyz; // convert position to view space - vec3 v = vec3(0,-1,0); - if (!u_rimambient) { - v = normalize(u_rimpivot-p); - } - float rim = 1.0 - max(dot(v,n), 0.0); - vec3 col = u_rimcolor*(pow(smoothstep(1.0-u_rimrange.x,u_rimrange.y,rim), u_rimrange.z)); - fragcolor += vec4(col, 0.0); - } - #endif } #endif diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 317c0f9..e5c9681 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -371199,6 +371199,9 @@ texture_t texture_compressed_from_mem(const void *data, int len, unsigned flags) if( dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); if( dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); if( dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_REPEAT); + if( flags&TEXTURE_CLAMP && dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + if( flags&TEXTURE_CLAMP && dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if( flags&TEXTURE_CLAMP && dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); if( target == GL_TEXTURE_CUBE_MAP ) target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; @@ -372620,15 +372623,55 @@ int ui_fxs() { static texture_t brdf = {0}; static void brdf_load() { - const char *filename; - filename = "Skyboxes/brdf_lut1k_256x256_32F.ktx"; - filename = "Skyboxes/brdf_lut2k_512x512_32F.ktx"; + // generate texture + unsigned tex; + glGenTextures(1, &tex); - brdf = texture_compressed( filename, - TEXTURE_CLAMP | TEXTURE_NEAREST | TEXTURE_RG | TEXTURE_FLOAT - ); - unsigned texchecker = texture_checker().id; - ASSERT(brdf.id != texchecker, "!Couldn't load BRDF lookup table '%s'!", filename ); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + brdf.id = tex; + brdf.w = 512; + brdf.h = 512; + + // create program and generate BRDF LUT + unsigned lut_fbo = fbo(tex, 0, 0), rbo=0; + fbo_bind(lut_fbo); + + static int program = -1, vao = -1; + if( program < 0 ) { + const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B_flipped.glsl"); + const char* fs = vfs_read("shaders/brdf.glsl"); + + program = shader(vs, fs, "", "fragcolor", NULL); + glGenVertexArrays( 1, (GLuint*)&vao ); + } + + glDisable(GL_BLEND); + + handle old_shader = last_shader; + glUseProgram( program ); + + glViewport(0, 0, 512, 512); + + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + + glBindVertexArray( vao ); + + glDrawArrays( GL_TRIANGLES, 0, 6 ); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + + glBindVertexArray( 0 ); + + glUseProgram( last_shader ); + + fbo_unbind(); + fbo_destroy(lut_fbo); } texture_t brdf_lut() { @@ -372649,7 +372692,7 @@ bool colormap( colormap_t *cm, const char *texture_name, bool load_as_srgb ) { int srgb = load_as_srgb ? TEXTURE_SRGB : 0; int hdr = strendi(texture_name, ".hdr") ? TEXTURE_FLOAT|TEXTURE_RGBA : 0; - texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb); + texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_ANISOTROPY | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb); if( t.id == texture_checker().id ) { cm->texture = NULL; @@ -373028,7 +373071,7 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, shader_bool( "has_tex_skysphere", has_tex_skysphere ); shader_bool( "has_tex_skyenv", has_tex_skyenv ); if( has_tex_skysphere ) { - float mipCount = floor( log2( m.sky_refl.h ) ); + float mipCount = floor( log2( max(m.sky_refl.w, m.sky_refl.h) ) ); shader_texture("tex_skysphere", m.sky_refl); shader_float( "skysphere_mip_count", mipCount ); } @@ -382654,6 +382697,9 @@ static void v4k_post_init(float refresh_rate) { hz = refresh_rate; // t = glfwGetTime(); + + // preload brdf LUT early + (void)brdf_lut(); } // ---------------------------------------------------------------------------- diff --git a/engine/split/v4k_main.c b/engine/split/v4k_main.c index 2e36d84..9d2bfa8 100644 --- a/engine/split/v4k_main.c +++ b/engine/split/v4k_main.c @@ -48,6 +48,9 @@ static void v4k_post_init(float refresh_rate) { hz = refresh_rate; // t = glfwGetTime(); + + // preload brdf LUT early + (void)brdf_lut(); } // ---------------------------------------------------------------------------- diff --git a/engine/split/v4k_render.c b/engine/split/v4k_render.c index 7141fd7..f354475 100644 --- a/engine/split/v4k_render.c +++ b/engine/split/v4k_render.c @@ -1180,6 +1180,9 @@ texture_t texture_compressed_from_mem(const void *data, int len, unsigned flags) if( dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); if( dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); if( dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_REPEAT); + if( flags&TEXTURE_CLAMP && dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + if( flags&TEXTURE_CLAMP && dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if( flags&TEXTURE_CLAMP && dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); if( target == GL_TEXTURE_CUBE_MAP ) target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; @@ -2601,15 +2604,55 @@ int ui_fxs() { static texture_t brdf = {0}; static void brdf_load() { - const char *filename; - filename = "Skyboxes/brdf_lut1k_256x256_32F.ktx"; - filename = "Skyboxes/brdf_lut2k_512x512_32F.ktx"; + // generate texture + unsigned tex; + glGenTextures(1, &tex); - brdf = texture_compressed( filename, - TEXTURE_CLAMP | TEXTURE_NEAREST | TEXTURE_RG | TEXTURE_FLOAT - ); - unsigned texchecker = texture_checker().id; - ASSERT(brdf.id != texchecker, "!Couldn't load BRDF lookup table '%s'!", filename ); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + brdf.id = tex; + brdf.w = 512; + brdf.h = 512; + + // create program and generate BRDF LUT + unsigned lut_fbo = fbo(tex, 0, 0), rbo=0; + fbo_bind(lut_fbo); + + static int program = -1, vao = -1; + if( program < 0 ) { + const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B_flipped.glsl"); + const char* fs = vfs_read("shaders/brdf.glsl"); + + program = shader(vs, fs, "", "fragcolor", NULL); + glGenVertexArrays( 1, (GLuint*)&vao ); + } + + glDisable(GL_BLEND); + + handle old_shader = last_shader; + glUseProgram( program ); + + glViewport(0, 0, 512, 512); + + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + + glBindVertexArray( vao ); + + glDrawArrays( GL_TRIANGLES, 0, 6 ); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + + glBindVertexArray( 0 ); + + glUseProgram( last_shader ); + + fbo_unbind(); + fbo_destroy(lut_fbo); } texture_t brdf_lut() { @@ -2630,7 +2673,7 @@ bool colormap( colormap_t *cm, const char *texture_name, bool load_as_srgb ) { int srgb = load_as_srgb ? TEXTURE_SRGB : 0; int hdr = strendi(texture_name, ".hdr") ? TEXTURE_FLOAT|TEXTURE_RGBA : 0; - texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb); + texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_ANISOTROPY | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb); if( t.id == texture_checker().id ) { cm->texture = NULL; @@ -3009,7 +3052,7 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, shader_bool( "has_tex_skysphere", has_tex_skysphere ); shader_bool( "has_tex_skyenv", has_tex_skyenv ); if( has_tex_skysphere ) { - float mipCount = floor( log2( m.sky_refl.h ) ); + float mipCount = floor( log2( max(m.sky_refl.w, m.sky_refl.h) ) ); shader_texture("tex_skysphere", m.sky_refl); shader_float( "skysphere_mip_count", mipCount ); } diff --git a/engine/v4k.c b/engine/v4k.c index d124de1..4bed833 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -18352,6 +18352,9 @@ texture_t texture_compressed_from_mem(const void *data, int len, unsigned flags) if( dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); if( dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); if( dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_REPEAT); + if( flags&TEXTURE_CLAMP && dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + if( flags&TEXTURE_CLAMP && dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if( flags&TEXTURE_CLAMP && dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); if( target == GL_TEXTURE_CUBE_MAP ) target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; @@ -19773,15 +19776,55 @@ int ui_fxs() { static texture_t brdf = {0}; static void brdf_load() { - const char *filename; - filename = "Skyboxes/brdf_lut1k_256x256_32F.ktx"; - filename = "Skyboxes/brdf_lut2k_512x512_32F.ktx"; + // generate texture + unsigned tex; + glGenTextures(1, &tex); - brdf = texture_compressed( filename, - TEXTURE_CLAMP | TEXTURE_NEAREST | TEXTURE_RG | TEXTURE_FLOAT - ); - unsigned texchecker = texture_checker().id; - ASSERT(brdf.id != texchecker, "!Couldn't load BRDF lookup table '%s'!", filename ); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + brdf.id = tex; + brdf.w = 512; + brdf.h = 512; + + // create program and generate BRDF LUT + unsigned lut_fbo = fbo(tex, 0, 0), rbo=0; + fbo_bind(lut_fbo); + + static int program = -1, vao = -1; + if( program < 0 ) { + const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B_flipped.glsl"); + const char* fs = vfs_read("shaders/brdf.glsl"); + + program = shader(vs, fs, "", "fragcolor", NULL); + glGenVertexArrays( 1, (GLuint*)&vao ); + } + + glDisable(GL_BLEND); + + handle old_shader = last_shader; + glUseProgram( program ); + + glViewport(0, 0, 512, 512); + + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + + glBindVertexArray( vao ); + + glDrawArrays( GL_TRIANGLES, 0, 6 ); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + + glBindVertexArray( 0 ); + + glUseProgram( last_shader ); + + fbo_unbind(); + fbo_destroy(lut_fbo); } texture_t brdf_lut() { @@ -19802,7 +19845,7 @@ bool colormap( colormap_t *cm, const char *texture_name, bool load_as_srgb ) { int srgb = load_as_srgb ? TEXTURE_SRGB : 0; int hdr = strendi(texture_name, ".hdr") ? TEXTURE_FLOAT|TEXTURE_RGBA : 0; - texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb); + texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_ANISOTROPY | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb); if( t.id == texture_checker().id ) { cm->texture = NULL; @@ -20181,7 +20224,7 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, shader_bool( "has_tex_skysphere", has_tex_skysphere ); shader_bool( "has_tex_skyenv", has_tex_skyenv ); if( has_tex_skysphere ) { - float mipCount = floor( log2( m.sky_refl.h ) ); + float mipCount = floor( log2( max(m.sky_refl.w, m.sky_refl.h) ) ); shader_texture("tex_skysphere", m.sky_refl); shader_float( "skysphere_mip_count", mipCount ); } @@ -29807,6 +29850,9 @@ static void v4k_post_init(float refresh_rate) { hz = refresh_rate; // t = glfwGetTime(); + + // preload brdf LUT early + (void)brdf_lut(); } // ----------------------------------------------------------------------------