From 7e51dc8b4ea8e39357263a7bd70746aad2f5b7f3 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Thu, 5 Jan 2023 16:35:11 -0800 Subject: Let the scene loader compile shader computations. --- gfx/include/gfx/util/scene.h | 4 +- gfx/include/gfx/util/shader.h | 12 +++++- gfx/shaders/cook_torrance.frag | 41 +++++++++++++++----- gfx/shaders/cook_torrance.vert | 17 +++++++-- gfx/src/util/scene.c | 59 ++++++++++++++++++++++++++--- gfx/src/util/shader.c | 86 ++++++++++++++++++++++++++---------------- gltfview/src/game.c | 12 +++--- 7 files changed, 173 insertions(+), 58 deletions(-) diff --git a/gfx/include/gfx/util/scene.h b/gfx/include/gfx/util/scene.h index 7340d1d..0aad3d3 100644 --- a/gfx/include/gfx/util/scene.h +++ b/gfx/include/gfx/util/scene.h @@ -19,6 +19,7 @@ typedef struct LoadSceneCmd { size_t size_bytes; }; }; + ShaderProgram* shader; } LoadSceneCmd; /// Load a scene. @@ -30,5 +31,4 @@ typedef struct LoadSceneCmd { /// characteristics (presence of normals, tangents, etc) is assigned. /// /// Currently only supports the GLTF format. -bool gfx_load_scene( - Gfx*, SceneNode* root_node, ShaderProgram* shader, const LoadSceneCmd*); +bool gfx_load_scene(Gfx*, SceneNode* root_node, const LoadSceneCmd*); diff --git a/gfx/include/gfx/util/shader.h b/gfx/include/gfx/util/shader.h index d801153..cadca55 100644 --- a/gfx/include/gfx/util/shader.h +++ b/gfx/include/gfx/util/shader.h @@ -1,8 +1,11 @@ /// A variety of shaders included for convenience. #pragma once -typedef struct RenderBackend RenderBackend; -typedef struct ShaderProgram ShaderProgram; +#include + +typedef struct RenderBackend RenderBackend; +typedef struct ShaderCompilerDefine ShaderCompilerDefine; +typedef struct ShaderProgram ShaderProgram; /// Create a BRDF integration map shader. ShaderProgram* gfx_make_brdf_integration_map_shader(RenderBackend*); @@ -10,6 +13,11 @@ ShaderProgram* gfx_make_brdf_integration_map_shader(RenderBackend*); /// Create a Cook-Torrance shader. ShaderProgram* gfx_make_cook_torrance_shader(RenderBackend*); +/// Create a Cook-Torrance shader with additional shader compiler defines. +/// This function can be used to create shader permutations. +ShaderProgram* gfx_make_cook_torrance_shader_perm( + RenderBackend*, const ShaderCompilerDefine*, size_t num_defines); + /// Create a 3D debugging shader. ShaderProgram* gfx_make_debug3d_shader(RenderBackend*); diff --git a/gfx/shaders/cook_torrance.frag b/gfx/shaders/cook_torrance.frag index b2bfd7d..18d9d18 100644 --- a/gfx/shaders/cook_torrance.frag +++ b/gfx/shaders/cook_torrance.frag @@ -7,9 +7,15 @@ uniform vec3 EmissiveFactor; uniform sampler2D BaseColorTexture; uniform sampler2D MetallicRoughnessTexture; +#ifdef HAS_EMISSIVE_MAP uniform sampler2D EmissiveTexture; +#endif +#ifdef HAS_OCCLUSION_MAP uniform sampler2D AmbientOcclusionTexture; +#endif +#ifdef HAS_NORMAL_MAP uniform sampler2D NormalMap; +#endif // TODO: Handle case in which there is no sky. Pass a boolean. uniform samplerCube Sky; @@ -22,19 +28,28 @@ uniform vec3 CameraPosition; // World space. // World-space position and normal. in vec3 Position; +#ifdef HAS_NORMALS in vec3 Normal; +#endif #ifdef HAS_TANGENTS in vec4 Tangent; -#endif // HAS_TANGENTS +#endif +#ifdef HAS_TEXCOORDS in vec2 Texcoord; +#endif layout (location = 0) out vec4 Colour; #define PI 3.1415926535897932384626433832795 #define INV_PI 0.3183098861837907 -// TODO: Move to "normal.h" -vec3 get_vs_normal(vec3 normalWs, vec3 normalMapSample) { +/// Transform a normal map sample into world space. +/// +/// |normalWs| is the surface normal in world space. +/// |normalMapSample| is the normal map sample, not necessarily normalized. +/// +/// TODO: Move to "normal.h" +vec3 get_ws_normal(vec3 normalWs, vec3 normalMapSample) { vec3 N = normalize(Normal); #ifdef HAS_TANGENTS //vec3 T = normalize(tangent.xyz - dot(tangent.xyz, N) * N); @@ -127,12 +142,12 @@ void main() // TODO: Also use the specular F0 map from the model, and emissive. Make sure // to use all maps. // https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4 - // TODO: Normal map samples should not be gamma-corrected when loaded, they - // are already authored as linear. - vec3 normalMapSample = normalize(texture(NormalMap, Texcoord).xyz * 2.0 - 1.0); - //vec3 normalMapSample = texture(NormalMap, Texcoord).xyz; - //normalMapSample = normalize(pow(normalMapSample, vec3(1.0 / 2.2)) * 2.0 - 1.0); - vec3 N = get_vs_normal(Normal, normalMapSample); +#ifdef HAS_NORMAL_MAP + vec3 normalMapSample = texture(NormalMap, Texcoord).xyz * 2.0 - 1.0; + vec3 N = get_ws_normal(Normal, normalMapSample); +#elif HAS_NORMALS + vec3 N = normalize(Normal); +#endif vec3 V = normalize(CameraPosition - Position); vec3 R = reflect(-V, N); // Not needed for IBL. @@ -150,8 +165,16 @@ void main() // TODO: Other factors. vec3 albedo = vec3(BaseColorFactor) * texture(BaseColorTexture, Texcoord).rgb; vec3 metal_roughness = texture(MetallicRoughnessTexture, Texcoord).rgb; +#ifdef HAS_EMISSIVE_MAP vec3 emissive = texture(EmissiveTexture, Texcoord).rgb; +#else + vec3 emissive = vec3(0.0); +#endif +#ifdef HAS_OCCLUSION_MAP float occlusion = texture(AmbientOcclusionTexture, Texcoord).r; +#else + float occlusion = 0.0; +#endif float metallic = MetallicFactor * metal_roughness.b; float roughness = metal_roughness.g; diff --git a/gfx/shaders/cook_torrance.vert b/gfx/shaders/cook_torrance.vert index 56dfeda..6425565 100644 --- a/gfx/shaders/cook_torrance.vert +++ b/gfx/shaders/cook_torrance.vert @@ -4,29 +4,40 @@ uniform mat4 ModelMatrix; uniform mat4 MVP; layout (location = 0) in vec3 vPosition; -// TODO: Add HAS_NORMALS +#ifdef HAS_NORMALS layout (location = 1) in vec3 vNormal; +#endif #ifdef HAS_TANGENTS layout (location = 2) in vec4 vTangent; #endif // HAS_TANGENTS +#ifdef HAS_TEXCOORDS layout (location = 3) in vec2 vTexcoord; +#endif // World-space position and normal. out vec3 Position; +#ifdef HAS_NORMALS out vec3 Normal; +#endif #ifdef HAS_TANGENTS out vec4 Tangent; -#endif // HAS_TANGENTS +#endif +#ifdef HAS_TEXCOORDS out vec2 Texcoord; +#endif void main() { Position = vec3(ModelMatrix * vec4(vPosition, 1.0)); +#ifdef HAS_NORMALS Normal = mat3(ModelMatrix) * vNormal; //Normal = normalize(ModelMatrix * vec4(vNormal, 0.0)).xyz; +#endif #ifdef HAS_TANGENTS Tangent = vec4(mat3(ModelMatrix) * vTangent.xyz, vTangent.w); -#endif // HAS_TANGENTS +#endif +#ifdef HAS_TEXCOORDS Texcoord = vTexcoord; +#endif gl_Position = MVP * vec4(vPosition, 1.0); } diff --git a/gfx/src/util/scene.c b/gfx/src/util/scene.c index 706e518..52812e9 100644 --- a/gfx/src/util/scene.c +++ b/gfx/src/util/scene.c @@ -90,6 +90,8 @@ #include #include #include +#include +#include #include #include @@ -127,6 +129,14 @@ #define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture" #define UNIFORM_NORMAL_MAP "NormalMap" +// Shader compiler defines. Must match the names in shaders. +#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS" +#define DEFINE_HAS_NORMALS "HAS_NORMALS" +#define DEFINE_HAS_TANGENTS "HAS_TANGENTS" +#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" +#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" +#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" + typedef enum TextureType { BaseColorTexture, MetallicRoughnessTexture, @@ -153,6 +163,45 @@ typedef struct MeshPermutation { }; } MeshPermutation; +/// Build shader compiler defines from a mesh permutation. +static size_t make_defines( + MeshPermutation perm, ShaderCompilerDefine* defines) { + static const char* str_true = "1"; + size_t next = 0; + +#define check(field, define) \ + if (perm.field) { \ + defines[next].name = sstring_make(define); \ + defines[next].value = sstring_make(str_true); \ + next++; \ + } + + check(has_texcoords, DEFINE_HAS_TEXCOORDS); + check(has_normals, DEFINE_HAS_NORMALS); + check(has_tangents, DEFINE_HAS_TANGENTS); + check(has_normal_map, DEFINE_HAS_NORMAL_MAP); + check(has_occlusion_texture, DEFINE_HAS_OCCLUSION_MAP); + check(has_emissive_texture, DEFINE_HAS_EMISSIVE_MAP); + + return next; +} + +/// Compile a shader permutation. +static ShaderProgram* make_shader_permutation( + RenderBackend* render_backend, MeshPermutation perm) { + LOGD( + "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " + "%d, tangents: %d, normal map: %d, AO map: %d, emissive map: %d", + perm.has_texcoords, perm.has_normals, perm.has_tangents, + perm.has_normal_map, perm.has_occlusion_texture, + perm.has_emissive_texture); + + ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; + const size_t num_defines = make_defines(perm, defines); + return gfx_make_cook_torrance_shader_perm( + render_backend, defines, num_defines); +} + /// Map a texture type to the name of the shader uniform used to access the /// texture. static const char* TextureUniformName(TextureType type) { @@ -502,7 +551,6 @@ static bool load_meshes( assert(gfx); assert(buffers); assert(materials); - assert(shader); assert(geometries); assert(meshes); assert(scene_objects); @@ -687,6 +735,9 @@ static bool load_meshes( // they can share shaders, the renderer can check later whether uniforms // have the same values. Also, changing uniforms is much faster than // swapping shaders, so shader caching is the most important thing here. + if (!shader) { + shader = make_shader_permutation(render_backend, perm); + } assert(shader); meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ @@ -996,9 +1047,7 @@ cleanup: return false; } -bool gfx_load_scene( - Gfx* gfx, SceneNode* root_node, ShaderProgram* shader, - const LoadSceneCmd* cmd) { +bool gfx_load_scene(Gfx* gfx, SceneNode* root_node, const LoadSceneCmd* cmd) { assert(gfx); assert(root_node); assert(cmd); @@ -1036,7 +1085,7 @@ bool gfx_load_scene( &options, data, &tangent_buffers, &num_tangent_buffers); success = load_scene( - data, gfx, root_node, cmd->filepath, shader, tangent_buffers, + data, gfx, root_node, cmd->filepath, cmd->shader, tangent_buffers, num_tangent_buffers); cleanup: diff --git a/gfx/src/util/shader.c b/gfx/src/util/shader.c index a07c66e..fdfb1d3 100644 --- a/gfx/src/util/shader.c +++ b/gfx/src/util/shader.c @@ -22,10 +22,12 @@ #include #include +#include -static ShaderProgram* make_shader_program(RenderBackend* render_backend, - const char* vert_source, - const char* frag_source) { +static ShaderProgram* make_shader_program( + RenderBackend* render_backend, const char* vert_source, + const char* frag_source, const ShaderCompilerDefine* defines, + size_t num_defines) { assert(render_backend); assert(vert_source); assert(frag_source); @@ -33,9 +35,18 @@ static ShaderProgram* make_shader_program(RenderBackend* render_backend, Shader* vert = 0; Shader* frag = 0; - ShaderDesc vertex_shader_desc = {.code = vert_source, .type = VertexShader}; - ShaderDesc fragment_shader_desc = {.code = frag_source, - .type = FragmentShader}; + ShaderDesc vertex_shader_desc = { + .code = vert_source, .type = VertexShader, .num_defines = num_defines}; + ShaderDesc fragment_shader_desc = { + .code = frag_source, .type = FragmentShader, .num_defines = num_defines}; + if (num_defines > 0) { + memcpy( + vertex_shader_desc.defines, defines, + num_defines * sizeof(ShaderCompilerDefine)); + memcpy( + fragment_shader_desc.defines, defines, + num_defines * sizeof(ShaderCompilerDefine)); + } vert = gfx_make_shader(render_backend, &vertex_shader_desc); if (!vert) { goto cleanup; @@ -44,8 +55,9 @@ static ShaderProgram* make_shader_program(RenderBackend* render_backend, if (!frag) { goto cleanup; } - ShaderProgramDesc shader_program_desc = {.vertex_shader = vert, - .fragment_shader = frag}; + + ShaderProgramDesc shader_program_desc = { + .vertex_shader = vert, .fragment_shader = frag}; ShaderProgram* prog = gfx_make_shader_program(render_backend, &shader_program_desc); if (!prog) { @@ -63,53 +75,63 @@ cleanup: return 0; } -ShaderProgram* -gfx_make_brdf_integration_map_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, quad_vert, - brdf_integration_map_frag); +ShaderProgram* gfx_make_brdf_integration_map_shader( + RenderBackend* render_backend) { + return make_shader_program( + render_backend, quad_vert, brdf_integration_map_frag, 0, 0); } ShaderProgram* gfx_make_cook_torrance_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, cook_torrance_vert, - cook_torrance_frag); + return make_shader_program( + render_backend, cook_torrance_vert, cook_torrance_frag, 0, 0); +} + +ShaderProgram* gfx_make_cook_torrance_shader_perm( + RenderBackend* render_backend, const ShaderCompilerDefine* defines, + size_t num_defines) { + return make_shader_program( + render_backend, cook_torrance_vert, cook_torrance_frag, defines, + num_defines); } ShaderProgram* gfx_make_irradiance_map_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, cubemap_filtering_vert, - irradiance_map_frag); + return make_shader_program( + render_backend, cubemap_filtering_vert, irradiance_map_frag, 0, 0); } -ShaderProgram* -gfx_make_prefiltered_environment_map_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, cubemap_filtering_vert, - prefiltered_environment_map_frag); +ShaderProgram* gfx_make_prefiltered_environment_map_shader( + RenderBackend* render_backend) { + return make_shader_program( + render_backend, cubemap_filtering_vert, prefiltered_environment_map_frag, + 0, 0); } ShaderProgram* gfx_make_debug3d_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, debug3d_vert, debug3d_frag); + return make_shader_program(render_backend, debug3d_vert, debug3d_frag, 0, 0); } ShaderProgram* gfx_make_skyquad_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, skyquad_vert, skyquad_frag); + return make_shader_program(render_backend, skyquad_vert, skyquad_frag, 0, 0); } -ShaderProgram* -gfx_make_view_normal_mapped_normals_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, view_normal_mapped_normals_vert, - view_normal_mapped_normals_frag); +ShaderProgram* gfx_make_view_normal_mapped_normals_shader( + RenderBackend* render_backend) { + return make_shader_program( + render_backend, view_normal_mapped_normals_vert, + view_normal_mapped_normals_frag, 0, 0); } ShaderProgram* gfx_make_view_normals_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, view_normals_vert, - view_normals_frag); + return make_shader_program( + render_backend, view_normals_vert, view_normals_frag, 0, 0); } ShaderProgram* gfx_make_view_tangents_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, view_tangents_vert, - view_tangents_frag); + return make_shader_program( + render_backend, view_tangents_vert, view_tangents_frag, 0, 0); } ShaderProgram* gfx_make_view_texture_shader(RenderBackend* render_backend) { - return make_shader_program(render_backend, view_texture_vert, - view_texture_frag); + return make_shader_program( + render_backend, view_texture_vert, view_texture_frag, 0, 0); } diff --git a/gltfview/src/game.c b/gltfview/src/game.c index f2e5a88..bd474d6 100644 --- a/gltfview/src/game.c +++ b/gltfview/src/game.c @@ -37,6 +37,7 @@ static const char* DAMAGED_HELMET = static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; +// TODO: Move this debug rendering to the renderer. static ShaderProgram* load_shader( RenderBackend* render_backend, const char* view_mode) { ShaderProgram* shader = 0; @@ -147,13 +148,14 @@ static bool load_scene( return false; } - ShaderProgram* shader = load_shader(game->render_backend, view_mode); - if (!shader) { - return false; - } + // TODO: Move the debug rendering to the renderer. + // ShaderProgram* shader = load_shader(game->render_backend, view_mode); + // if (!shader) { + // return false; + // } if (!gfx_load_scene( - game->gfx, sky_node, shader, + game->gfx, sky_node, &(LoadSceneCmd){ .origin = SceneFromFile, .filepath = scene_filepath})) { return false; -- cgit v1.2.3