From 42b5f1997cdd5e99645e24dca6cb89cc7b081a09 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 1 Nov 2025 18:23:08 -0700 Subject: Add support for alpha mode --- src/asset/model.c | 75 ++++++++++++++++++----------- src/core/shader_program.c | 119 ++++++++++++++++++++++++++++++---------------- src/render/llr.c | 36 ++++++++++---- src/render/llr_impl.h | 6 +-- src/render/renderer.c | 46 +++++++++++++++--- 5 files changed, 196 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/asset/model.c b/src/asset/model.c index 2ee3cd1..a97d20e 100644 --- a/src/asset/model.c +++ b/src/asset/model.c @@ -138,6 +138,7 @@ #define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" #define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" #define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" +#define DEFINE_HAS_TRANSPARENCY "HAS_TRANSPARENCY" #define DEFINE_HAS_JOINTS "HAS_JOINTS" #define DEFINE_MAX_JOINTS "MAX_JOINTS" @@ -166,6 +167,8 @@ typedef struct MeshPermutation { bool has_normal_map : 1; bool has_occlusion_map : 1; bool has_emissive_map : 1; + // Material. + bool has_transparency : 1; }; int32_t all; }; @@ -192,6 +195,7 @@ static size_t make_defines( check(has_normal_map, DEFINE_HAS_NORMAL_MAP); check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); + check(has_transparency, DEFINE_HAS_TRANSPARENCY); if (perm.has_joints) { defines[next].name = sstring_make(DEFINE_MAX_JOINTS); @@ -208,12 +212,12 @@ static ShaderProgram* make_shader_permutation( LOGD( "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " - "metallic-roughness map: " - "%d, normal " - "map: %d, AO map: %d, emissive map: %d", + "metallic-roughness map: %d, normal map: %d, AO map: %d, emissive map: " + "%d, has transparency: %d", perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, - perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); + perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map, + perm.has_transparency); ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; const size_t num_defines = make_defines(perm, defines); @@ -742,6 +746,19 @@ static bool load_texture_and_uniform( return true; } +static AlphaMode to_gfx_alpha_mode(cgltf_alpha_mode mode) { + switch (mode) { + case cgltf_alpha_mode_opaque: + return Opaque; + case cgltf_alpha_mode_mask: + return Mask; + case cgltf_alpha_mode_blend: + return Blend; + } + FAIL("unhandled alpha mode"); + return Opaque; +} + /// Load all materials from the glTF scene. /// /// Return an array of Materials such that the index of each descriptor matches @@ -770,27 +787,27 @@ static bool load_materials( assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ - .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), - .type = UniformVec4, - .value.vec4 = vec4_from_array(pbr->base_color_factor)}; + .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), + .type = UniformVec4, + .value.uniform_vec4 = vec4_from_array(pbr->base_color_factor)}; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = - (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR), - .type = UniformFloat, - .value.scalar = pbr->metallic_factor}; + (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR), + .type = UniformFloat, + .value.uniform_float = pbr->metallic_factor}; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), .type = UniformFloat, - .value.scalar = pbr->roughness_factor}; + .value.uniform_float = pbr->roughness_factor}; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc.uniforms[next_uniform++] = - (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR), - .type = UniformVec3, - .value.vec3 = vec3_from_array(mat->emissive_factor)}; + desc.uniforms[next_uniform++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), + .type = UniformVec3, + .value.uniform_vec3 = vec3_from_array(mat->emissive_factor)}; if (pbr->base_color_texture.texture) { if (!load_texture_and_uniform( @@ -837,6 +854,9 @@ static bool load_materials( assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.num_uniforms = next_uniform; + desc.alpha_mode = to_gfx_alpha_mode(mat->alpha_mode); + desc.alpha_cutoff = mat->alpha_cutoff; + materials[i] = gfx_make_material(&desc); if (!materials[i]) { return false; @@ -852,27 +872,27 @@ static Material* make_default_material() { assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = - (ShaderUniform){.name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), - .type = UniformVec4, - .value.vec4 = vec4_make(1, 1, 1, 1)}; + (ShaderUniform){.name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), + .type = UniformVec4, + .value.uniform_vec4 = vec4_make(1, 1, 1, 1)}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = - (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR), - .type = UniformFloat, - .value.scalar = 0}; + (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR), + .type = UniformFloat, + .value.uniform_float = 0}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = - (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), - .type = UniformFloat, - .value.scalar = 1}; + (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), + .type = UniformFloat, + .value.uniform_float = 1}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = - (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR), - .type = UniformVec3, - .value.vec3 = vec3_make(0, 0, 0)}; + (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR), + .type = UniformVec3, + .value.uniform_vec3 = vec3_make(0, 0, 0)}; return gfx_make_material(&desc); } @@ -950,6 +970,7 @@ static bool load_meshes( perm.has_normal_map = mat->normal_texture.texture != 0; perm.has_occlusion_map = mat->occlusion_texture.texture != 0; perm.has_emissive_map = mat->emissive_texture.texture != 0; + perm.has_transparency = mat->alpha_mode != cgltf_alpha_mode_opaque; if (mat->has_pbr_metallic_roughness) { const cgltf_pbr_metallic_roughness* pbr = diff --git a/src/core/shader_program.c b/src/core/shader_program.c index 3cbe48d..eeb46f8 100644 --- a/src/core/shader_program.c +++ b/src/core/shader_program.c @@ -72,17 +72,23 @@ void gfx_deactivate_shader_program(const ShaderProgram* prog) { ASSERT_GL; } -static void set_texture_uniform( - GLuint prog, const char* name, int texture_unit, const Texture* texture) { +static void set_int_uniform(GLuint prog, const char* name, int value) { assert(prog != 0); assert(name); - assert(texture); const GLint location = glGetUniformLocation(prog, name); if (location >= 0) { - glActiveTexture(GL_TEXTURE0 + texture_unit); - glBindTexture(texture->target, texture->id); - glUniform1i(location, texture_unit); + glUniform1i(location, value); + } +} + +static void set_float_uniform(GLuint prog, const char* name, float value) { + assert(prog != 0); + assert(name); + + const GLint location = glGetUniformLocation(prog, name); + if (location >= 0) { + glUniform1f(location, value); } } @@ -118,13 +124,17 @@ static void set_vec4_uniform(GLuint prog, const char* name, vec4 value) { } } -static void set_float_uniform(GLuint prog, const char* name, float value) { +static void set_texture_uniform( + GLuint prog, const char* name, int texture_unit, const Texture* texture) { assert(prog != 0); assert(name); + assert(texture); const GLint location = glGetUniformLocation(prog, name); if (location >= 0) { - glUniform1f(location, value); + glActiveTexture(GL_TEXTURE0 + texture_unit); + glBindTexture(texture->target, texture->id); + glUniform1i(location, texture_unit); } } @@ -135,23 +145,30 @@ void gfx_apply_uniforms(const ShaderProgram* prog) { for (int i = 0; i < prog->num_uniforms; ++i) { const ShaderUniform* uniform = &prog->uniforms[i]; switch (uniform->type) { - case UniformTexture: - set_texture_uniform( - prog->id, uniform->name.str, next_texture_unit, - uniform->value.texture); - next_texture_unit++; + case UniformInt: + set_int_uniform(prog->id, uniform->name.str, uniform->value.uniform_int); + break; + case UniformFloat: + set_float_uniform( + prog->id, uniform->name.str, uniform->value.uniform_float); break; case UniformMat4: - set_mat4_uniform(prog->id, uniform->name.str, &uniform->value.mat4, 1); + set_mat4_uniform( + prog->id, uniform->name.str, &uniform->value.uniform_mat4, 1); break; case UniformVec3: - set_vec3_uniform(prog->id, uniform->name.str, uniform->value.vec3); + set_vec3_uniform( + prog->id, uniform->name.str, uniform->value.uniform_vec3); break; case UniformVec4: - set_vec4_uniform(prog->id, uniform->name.str, uniform->value.vec4); + set_vec4_uniform( + prog->id, uniform->name.str, uniform->value.uniform_vec4); break; - case UniformFloat: - set_float_uniform(prog->id, uniform->name.str, uniform->value.scalar); + case UniformTexture: + set_texture_uniform( + prog->id, uniform->name.str, next_texture_unit, + uniform->value.texture); + next_texture_unit++; break; case UniformMat4Array: set_mat4_uniform( @@ -179,8 +196,9 @@ static ShaderUniform* get_or_allocate_uniform( // Create the uniform if it does not exist. if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) { - FAIL("Exceeded the maximum number of uniforms per shader. Please increase " - "this value."); + FAIL( + "Exceeded the maximum number of uniforms per shader. Please increase " + "this value."); return 0; } ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms]; @@ -191,21 +209,38 @@ static ShaderUniform* get_or_allocate_uniform( // The functions below save the value of a uniform in the shader program. If the // uniform does not even exist, then there is no need to store the value. -void gfx_set_texture_uniform( - ShaderProgram* prog, const char* name, const Texture* texture) { +void gfx_set_int_uniform(ShaderProgram* prog, const char* name, int value) { assert(prog); assert(name); - assert(texture); + // No need to store the uniform on our side if it does not exist in the + // program. const GLint location = glGetUniformLocation(prog->id, name); if (location < 0) { return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); assert(uniform); - uniform->name = sstring_make(name); - uniform->type = UniformTexture; - uniform->value.texture = texture; + uniform->name = sstring_make(name); + uniform->type = UniformInt; + uniform->value.uniform_int = value; +} + +void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) { + assert(prog); + assert(name); + + // No need to store the uniform on our side if it does not exist in the + // program. + const GLint location = glGetUniformLocation(prog->id, name); + if (location < 0) { + return; + } + ShaderUniform* uniform = get_or_allocate_uniform(prog, name); + assert(uniform); + uniform->name = sstring_make(name); + uniform->type = UniformFloat; + uniform->value.uniform_float = value; } void gfx_set_mat4_uniform( @@ -220,9 +255,9 @@ void gfx_set_mat4_uniform( } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); assert(uniform); - uniform->name = sstring_make(name); - uniform->type = UniformMat4; - uniform->value.mat4 = *mat; + uniform->name = sstring_make(name); + uniform->type = UniformMat4; + uniform->value.uniform_mat4 = *mat; } void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) { @@ -235,9 +270,9 @@ void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) { } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); assert(uniform); - uniform->name = sstring_make(name); - uniform->type = UniformVec3; - uniform->value.vec3 = value; + uniform->name = sstring_make(name); + uniform->type = UniformVec3; + uniform->value.uniform_vec3 = value; } void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) { @@ -250,26 +285,26 @@ void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) { } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); assert(uniform); - uniform->name = sstring_make(name); - uniform->type = UniformVec4; - uniform->value.vec4 = value; + uniform->name = sstring_make(name); + uniform->type = UniformVec4; + uniform->value.uniform_vec4 = value; } -void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) { +void gfx_set_texture_uniform( + ShaderProgram* prog, const char* name, const Texture* texture) { assert(prog); assert(name); + assert(texture); - // No need to store the uniform on our side if it does not exist in the - // program. const GLint location = glGetUniformLocation(prog->id, name); if (location < 0) { return; } ShaderUniform* uniform = get_or_allocate_uniform(prog, name); assert(uniform); - uniform->name = sstring_make(name); - uniform->type = UniformFloat; - uniform->value.scalar = value; + uniform->name = sstring_make(name); + uniform->type = UniformTexture; + uniform->value.texture = texture; } void gfx_set_mat4_array_uniform( @@ -277,7 +312,7 @@ void gfx_set_mat4_array_uniform( assert(prog); assert(name); assert(mats); - + const GLint location = glGetUniformLocation(prog->id, name); if (location < 0) { return; diff --git a/src/render/llr.c b/src/render/llr.c index 76935f9..c9c6d34 100644 --- a/src/render/llr.c +++ b/src/render/llr.c @@ -54,7 +54,9 @@ static void material_make(Material* material, const MaterialDesc* desc) { assert(material); assert(desc); assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); - material->num_uniforms = desc->num_uniforms; + material->alpha_mode = desc->alpha_mode; + material->alpha_cutoff = desc->alpha_cutoff; + material->num_uniforms = (int8_t)desc->num_uniforms; for (int i = 0; i < desc->num_uniforms; ++i) { material->uniforms[i] = desc->uniforms[i]; } @@ -69,22 +71,27 @@ Material* gfx_make_material(const MaterialDesc* desc) { void gfx_destroy_material(Material** material) { mem_free_material(material); } +// TODO: Move this to core/shader_program. static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) { switch (uniform->type) { - case UniformTexture: - gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture); + case UniformInt: + gfx_set_int_uniform(prog, uniform->name.str, uniform->value.uniform_int); + break; + case UniformFloat: + gfx_set_float_uniform( + prog, uniform->name.str, uniform->value.uniform_float); break; case UniformMat4: - gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.mat4); + gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.uniform_mat4); break; case UniformVec3: - gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.vec3); + gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.uniform_vec3); break; case UniformVec4: - gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.vec4); + gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.uniform_vec4); break; - case UniformFloat: - gfx_set_float_uniform(prog, uniform->name.str, uniform->value.scalar); + case UniformTexture: + gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture); break; case UniformMat4Array: gfx_set_mat4_array_uniform( @@ -104,6 +111,19 @@ static void gfx_material_activate( const ShaderUniform* uniform = &material->uniforms[i]; set_uniform(shader, uniform); } + if (material->alpha_mode != Opaque) { + set_uniform( + shader, &(ShaderUniform){.name = sstring_make("AlphaMode"), + .type = UniformInt, + .value.uniform_int = material->alpha_mode}); + } + if (material->alpha_mode == Mask) { + set_uniform( + shader, + &(ShaderUniform){.name = sstring_make("AlphaCutoff"), + .type = UniformFloat, + .value.uniform_float = material->alpha_cutoff}); + } } static void mesh_make(Mesh* mesh, const MeshDesc* desc) { diff --git a/src/render/llr_impl.h b/src/render/llr_impl.h index c85ad15..3a5455a 100644 --- a/src/render/llr_impl.h +++ b/src/render/llr_impl.h @@ -3,8 +3,6 @@ #include #include -#include "../types.h" - #include #include @@ -37,8 +35,10 @@ typedef struct Light { } Light; typedef struct Material { + AlphaMode alpha_mode; + float alpha_cutoff; + int8_t num_uniforms; ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; - int num_uniforms; } Material; typedef struct Mesh { diff --git a/src/render/renderer.c b/src/render/renderer.c index b513ed4..26b63bc 100644 --- a/src/render/renderer.c +++ b/src/render/renderer.c @@ -86,12 +86,13 @@ static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) { // } typedef struct RenderState { - GfxCore* gfxcore; - LLR* llr; - Renderer* renderer; - ShaderProgram* shader; // Null to use scene shaders. - const Scene* scene; - const Anima* anima; + GfxCore* gfxcore; + LLR* llr; + Renderer* renderer; + ShaderProgram* shader; // Null to use scene shaders. + const Scene* scene; + const Anima* anima; + RenderSceneFilter filter; } RenderState; static void draw_children( @@ -153,6 +154,24 @@ static void draw_recursively( continue; } + // Filter out by material. + const Material* material = mesh->material; + if (material) { + const AlphaMode mode = material->alpha_mode; + switch (state->filter) { + case RenderOpaqueAndAlphaMasked: + if (mode == Blend) { + continue; + } + break; + case RenderTransparent: + if (mode != Blend) { + continue; + } + break; + } + } + // TODO: Here we would frustum-cull the mesh. The AABB would have to be // transformed by the model matrix. Rotation would make the AABB // relatively large, but still, the culling would be conservative. @@ -208,6 +227,20 @@ void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) { gfx_llr_set_camera(renderer->llr, camera); gfx_llr_set_aspect(renderer->llr, aspect); + // TODO: Render Opaque and Mask alpha-mode materials first, then Blend ones. + // TODO: I'm not sure if this belongs to the scene renderer per se, or if it + // is something that should be driven from the outside. Specifically, the + // caller could pass in a filter that determines what objects to render. The + // filter could include alpha mode. + // This caller would be some component that understands render passes and + // potentially renders the scene multiple times as needed. For example, a + // depth-prepass, followed by G-buffer, followed by some post-processing, + // etc. Rename this renderer to scene_renderer? + // Opaque. + state.filter = RenderOpaqueAndAlphaMasked; + draw_recursively(&state, mat4_id(), gfx_get_scene_root(scene)); + // Transparent. + state.filter = RenderTransparent; draw_recursively(&state, mat4_id(), gfx_get_scene_root(scene)); } @@ -236,6 +269,7 @@ static void update_rec(SceneNode* node, const Camera* camera, R t) { } } +// TODO: Move this outside the renderer. void gfx_update(Scene* scene, const Camera* camera, R t) { assert(scene); assert(camera); -- cgit v1.2.3