diff --git a/include/reign.h b/include/reign.h index c4326ed..5e56be8 100644 --- a/include/reign.h +++ b/include/reign.h @@ -42,6 +42,7 @@ enum RE_motion_state { }; enum RE_fog_type { + RE_FOG_NONE = 0, RE_FOG_LINEAR = 1, RE_FOG_LIGHT_SCATTERING = 2, }; @@ -151,6 +152,7 @@ struct RE_instance { vec3 globe_diffuse; // Private + bool is_transparent; bool local_transform_needs_update; mat4 local_transform; mat3 normal_transform; diff --git a/shaders/reign.f.glsl b/shaders/reign.f.glsl index f4cb034..6517383 100644 --- a/shaders/reign.f.glsl +++ b/shaders/reign.f.glsl @@ -19,10 +19,15 @@ #define DIFFUSE_EMISSIVE 0 #define DIFFUSE_NORMAL 1 #define DIFFUSE_LIGHT_MAP 2 +#define DIFFUSE_ENV_MAP 3 #define FOG_LINEAR 1 #define FOG_LIGHT_SCATTERING 2 +#define ALPHA_BLEND 0 +#define ALPHA_TEST 1 +#define ALPHA_MAP_BLEND 2 + struct dir_light { vec3 dir; vec3 diffuse; @@ -53,7 +58,7 @@ uniform int fog_type; uniform float fog_near; uniform float fog_far; uniform vec3 fog_color; -uniform bool use_alpha_map; +uniform int alpha_mode; in vec2 tex_coord; in vec2 light_tex_coord; @@ -81,19 +86,25 @@ void main() { vec3 frag_rgb = ambient; // Diffuse lighting - vec4 texel = texture(tex, tex_coord); - if (diffuse_type == DIFFUSE_EMISSIVE) { + vec4 texel; + if (diffuse_type == DIFFUSE_ENV_MAP) { + texel = texture(tex, (vec2(normal.x, -normal.y) + 1.0) / 2.0); frag_rgb = texel.rgb; } else { - vec3 diffuse = vec3(0.0); - for (int i = 0; i < NR_DIR_LIGHTS; i++) { - float half_lambert = dot(norm, -light_dir[i]) * 0.5 + 0.5; - diffuse += mix(dir_lights[i].globe_diffuse, dir_lights[i].diffuse, half_lambert); + texel = texture(tex, tex_coord); + if (diffuse_type == DIFFUSE_EMISSIVE) { + frag_rgb = texel.rgb; + } else { + vec3 diffuse = vec3(0.0); + for (int i = 0; i < NR_DIR_LIGHTS; i++) { + float half_lambert = dot(norm, -light_dir[i]) * 0.5 + 0.5; + diffuse += mix(dir_lights[i].globe_diffuse, dir_lights[i].diffuse, half_lambert); + } + if (diffuse_type == DIFFUSE_LIGHT_MAP) { + diffuse *= texture(light_texture, light_tex_coord).rgb; + } + frag_rgb += texel.rgb * diffuse; } - if (diffuse_type == DIFFUSE_LIGHT_MAP) { - diffuse *= texture(light_texture, light_tex_coord).rgb; - } - frag_rgb += texel.rgb * diffuse; } // Specular lighting @@ -109,7 +120,8 @@ void main() { // Rim lighting if (rim_exponent > 0.0) { // Normal map is not used here. - frag_rgb += pow(1.0 - max(dot(normalize(normal), view_dir), 0.0), rim_exponent) * rim_color; + vec3 n = use_normal_map ? vec3(0.0, 0.0, 1.0) : normalize(normal); + frag_rgb += pow(1.0 - max(dot(n, view_dir), 0.0), rim_exponent) * rim_color; } // Fog @@ -139,7 +151,10 @@ void main() { // Alpha mapping float alpha = texel.a * alpha_mod; - if (use_alpha_map) { + if (alpha_mode == ALPHA_TEST) { + if (alpha < 1.0) + discard; + } else if (alpha_mode == ALPHA_MAP_BLEND) { alpha *= texture(alpha_texture, tex_coord).r; } diff --git a/shaders/reign.v.glsl b/shaders/reign.v.glsl index f1e9d23..f126eb3 100644 --- a/shaders/reign.v.glsl +++ b/shaders/reign.v.glsl @@ -89,8 +89,6 @@ void main() { vec3 tangent = normal_bone_transform * vertex_tangent.xyz; vec3 bitangent = cross(normal, tangent) * vertex_tangent.w; TBN = transpose(mat3(tangent, bitangent, normal)); - // Tangent-space normal vector. - normal = vec3(0.0, 0.0, 1.0); } vec4 world_pos = local_bone_transform * vec4(vertex_pos, 1.0); diff --git a/src/3d/3d_internal.h b/src/3d/3d_internal.h index f4a07ab..85380ca 100644 --- a/src/3d/3d_internal.h +++ b/src/3d/3d_internal.h @@ -44,9 +44,11 @@ struct model { struct hash_table *bone_map; // bone id in POL/MOT -> struct bone * struct hash_table *bone_name_map; // bone name -> (struct bone * | NULL) vec3 aabb[2]; // axis-aligned bounding box + bool is_transparent; }; struct mesh { + uint32_t flags; GLuint vao; GLuint attr_buffer; int nr_vertices; @@ -54,6 +56,7 @@ struct mesh { }; struct material { + uint32_t flags; GLuint color_map; GLuint specular_map; GLuint alpha_map; @@ -151,7 +154,7 @@ struct RE_renderer { GLint ls_light_dir; GLint ls_light_color; GLint ls_sun_color; - GLint use_alpha_map; + GLint alpha_mode; GLint alpha_texture; GLuint billboard_vao; @@ -182,7 +185,7 @@ struct archive_data *RE_get_aar_entry(struct archive *aar, const char *dir, cons struct RE_renderer *RE_renderer_new(struct texture *texture); void RE_renderer_free(struct RE_renderer *r); -bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no); +struct billboard_texture *RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no); struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r, struct model *model); void RE_renderer_free_height_detector(struct height_detector *hd); float RE_renderer_detect_height(struct height_detector *hd, float x, float z); @@ -368,8 +371,13 @@ enum pol_texture_type { MAX_TEXTURE_TYPE }; +enum material_flags { + MATERIAL_SPRITE = 1 << 0, +}; + struct pol_material { char *name; + uint32_t flags; char *textures[MAX_TEXTURE_TYPE]; }; @@ -379,8 +387,14 @@ struct pol_material_group { struct pol_material *children; }; +enum mesh_flags { + MESH_NOLIGHTING = 1 << 0, + MESH_ENVMAP = 1 << 1, +}; + struct pol_mesh { char *name; + uint32_t flags; uint32_t material; uint32_t nr_vertices; struct pol_vertex *vertices; diff --git a/src/3d/debug.c b/src/3d/debug.c index 9473391..a669a2b 100644 --- a/src/3d/debug.c +++ b/src/3d/debug.c @@ -50,6 +50,7 @@ static const char *motion_state_name(enum RE_motion_state state) static const char *fog_type_name(enum RE_fog_type type) { switch (type) { + case RE_FOG_NONE: return "NONE"; case RE_FOG_LINEAR: return "LINEAR"; case RE_FOG_LIGHT_SCATTERING: return "LIGHT_SCATTERING"; } @@ -98,6 +99,7 @@ static void print_instance(struct RE_instance *inst, int index, int indent) indent_printf(indent, "motion_blend_rate = %f,\n", inst->motion_blend_rate); if (inst->effect) indent_printf(indent, "path = \"%s\",\n", inst->effect->path); + indent_printf(indent, "is_transparent = %d,\n", inst->is_transparent); indent--; indent_printf(indent, "},\n"); @@ -135,6 +137,8 @@ void RE_debug_print(struct sact_sprite *sp, int indent) indent_printf(indent, "fog_type = %s,\n", fog_type_name(p->fog_type)); switch (p->fog_type) { + case RE_FOG_NONE: + break; case RE_FOG_LINEAR: indent_printf(indent, "fog_near = %f, fog_far = %f,\n", p->fog_near, p->fog_far); indent_printf(indent, "fog_color = {r=%f, g=%f, b=%f},\n", SPREAD_VEC3(p->fog_color)); diff --git a/src/3d/model.c b/src/3d/model.c index 2bed90e..8f69c0c 100644 --- a/src/3d/model.c +++ b/src/3d/model.c @@ -89,6 +89,7 @@ static GLuint load_texture(struct archive *aar, const char *path, const char *na static bool init_material(struct material *material, const struct pol_material *m, struct amt *amt, struct archive *aar, const char *path) { + material->flags = m->flags; if (!m->textures[COLOR_MAP]) { WARNING("No color texture"); return false; @@ -271,6 +272,7 @@ static void add_mesh(struct model *model, struct pol_mesh *m, int material_index } model->meshes = xrealloc_array(model->meshes, model->nr_meshes, model->nr_meshes + 1, sizeof(struct mesh)); struct mesh *mesh = &model->meshes[model->nr_meshes++]; + mesh->flags = m->flags; mesh->material = material; mesh->nr_vertices = nr_vertices; @@ -439,6 +441,12 @@ struct model *model_load(struct archive *aar, const char *path) &pol->materials[i].children[j], amt, aar, path); } } + for (int i = 0; i < model->nr_materials; i++) { + if (model->materials[i].alpha_map) { + model->is_transparent = true; + break; + } + } // Meshes for (uint32_t i = 0; i < pol->nr_meshes; i++) { diff --git a/src/3d/parser.c b/src/3d/parser.c index c68ca4a..9e5eab0 100644 --- a/src/3d/parser.c +++ b/src/3d/parser.c @@ -59,9 +59,18 @@ static void read_quaternion(struct buffer *r, versor q) glm_quat_normalize(q); } +static uint32_t parse_material_attributes(const char *name) +{ + uint32_t flags = 0; + if (strstr(name, "(sprite)")) + flags |= MATERIAL_SPRITE; + return flags; +} + static void parse_material(struct buffer *r, struct pol_material *m) { m->name = read_cstring(r); + m->flags = parse_material_attributes(m->name); int nr_textures = buffer_read_int32(r); for (int i = 0; i < nr_textures; i++) { @@ -148,6 +157,16 @@ static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle t->material = buffer_read_int32(r); } +static uint32_t parse_mesh_attributes(const char *name) +{ + uint32_t flags = 0; + if (strstr(name, "(nolighting)")) + flags |= MESH_NOLIGHTING; + if (strstr(name, "(env)")) + flags |= MESH_ENVMAP; + return flags; +} + static struct pol_mesh *parse_mesh(struct buffer *r, int pol_version) { int type = buffer_read_int32(r); @@ -158,6 +177,7 @@ static struct pol_mesh *parse_mesh(struct buffer *r, int pol_version) } struct pol_mesh *mesh = xcalloc(1, sizeof(struct pol_mesh)); mesh->name = read_cstring(r); + mesh->flags = parse_mesh_attributes(mesh->name); mesh->material = buffer_read_int32(r); mesh->nr_vertices = buffer_read_int32(r); diff --git a/src/3d/particle.c b/src/3d/particle.c index 9f39009..b9ac076 100644 --- a/src/3d/particle.c +++ b/src/3d/particle.c @@ -741,8 +741,8 @@ void particle_effect_calc_frame_range(struct particle_effect *effect, struct mot if (end_frame < obj_end) end_frame = obj_end; } - motion->frame_begin = begin_frame; - motion->frame_end = end_frame; + motion->frame_begin = motion->loop_frame_begin = begin_frame; + motion->frame_end = motion->loop_frame_end = end_frame; } static void update_camera_quake(struct RE_instance *inst, struct particle_object *po) diff --git a/src/3d/reign.c b/src/3d/reign.c index b3e5d8a..799d161 100644 --- a/src/3d/reign.c +++ b/src/3d/reign.c @@ -188,7 +188,10 @@ struct RE_plugin *RE_plugin_new(void) plugin->aar = aar; for (int i = 0; i < RE_NR_BACK_CGS; i++) RE_back_cg_init(&plugin->back_cg[i]); - plugin->fog_type = RE_FOG_LINEAR; + plugin->fog_type = RE_FOG_NONE; + plugin->fog_near = 1.0; + plugin->fog_far = 10.0; + glm_vec3_one(plugin->fog_color); return plugin; } @@ -346,6 +349,7 @@ bool RE_instance_load(struct RE_instance *instance, const char *name) instance->model = model_load(instance->plugin->aar, name); if (instance->model && instance->model->nr_bones > 0) instance->bone_transforms = xcalloc(instance->model->nr_bones, sizeof(mat4)); + instance->is_transparent = instance->model->is_transparent; return !!instance->model; case RE_ITYPE_PARTICLE_EFFECT: instance->effect = particle_effect_load(instance->plugin->aar, name); @@ -354,6 +358,7 @@ bool RE_instance_load(struct RE_instance *instance, const char *name) instance->motion->instance = instance; particle_effect_calc_frame_range(instance->effect, instance->motion); } + instance->is_transparent = true; return !!instance->effect; default: WARNING("Invalid instance type %d", instance->type); @@ -496,8 +501,11 @@ bool RE_motion_set_frame_range(struct motion *motion, float begin, float end) motion->frame_end = end; if (motion->instance->type == RE_ITYPE_BILLBOARD) { for (int i = begin; i < end; i++) { - if (!RE_renderer_load_billboard_texture(motion->instance->plugin->renderer, i)) + struct billboard_texture *bt = RE_renderer_load_billboard_texture(motion->instance->plugin->renderer, i); + if (!bt) return false; + if (bt->has_alpha) + motion->instance->is_transparent = true; } } return true; @@ -511,8 +519,11 @@ bool RE_motion_set_loop_frame_range(struct motion *motion, float begin, float en motion->loop_frame_end = end; if (motion->instance->type == RE_ITYPE_BILLBOARD) { for (int i = begin; i < end; i++) { - if (!RE_renderer_load_billboard_texture(motion->instance->plugin->renderer, i)) + struct billboard_texture *bt = RE_renderer_load_billboard_texture(motion->instance->plugin->renderer, i); + if (!bt) return false; + if (bt->has_alpha) + motion->instance->is_transparent = true; } } return true; diff --git a/src/3d/renderer.c b/src/3d/renderer.c index 5e157a7..7ffac7e 100644 --- a/src/3d/renderer.c +++ b/src/3d/renderer.c @@ -14,6 +14,7 @@ * along with this program; if not, see . */ +#include #include #include "system4.h" @@ -42,6 +43,13 @@ enum { DIFFUSE_EMISSIVE = 0, DIFFUSE_NORMAL = 1, DIFFUSE_LIGHT_MAP = 2, + DIFFUSE_ENV_MAP = 3, +}; + +enum { + ALPHA_BLEND = 0, + ALPHA_TEST = 1, + ALPHA_MAP_BLEND = 2, }; static GLuint load_shader(const char *vertex_shader_path, const char *fragment_shader_path) @@ -200,7 +208,7 @@ struct RE_renderer *RE_renderer_new(struct texture *texture) r->ls_light_dir = glGetUniformLocation(r->program, "ls_light_dir"); r->ls_light_color = glGetUniformLocation(r->program, "ls_light_color"); r->ls_sun_color = glGetUniformLocation(r->program, "ls_sun_color"); - r->use_alpha_map = glGetUniformLocation(r->program, "use_alpha_map"); + r->alpha_mode = glGetUniformLocation(r->program, "alpha_mode"); r->alpha_texture = glGetUniformLocation(r->program, "alpha_texture"); glGenRenderbuffers(1, &r->depth_buffer); @@ -214,16 +222,17 @@ struct RE_renderer *RE_renderer_new(struct texture *texture) return r; } -bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no) +struct billboard_texture *RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no) { - if (ht_get_int(r->billboard_textures, cg_no, NULL)) - return true; + struct billboard_texture *bt = ht_get_int(r->billboard_textures, cg_no, NULL); + if (bt) + return bt; struct cg *cg = asset_cg_load(cg_no); if (!cg) - return false; + return NULL; - struct billboard_texture *bt = xcalloc(1, sizeof(struct billboard_texture)); + bt = xcalloc(1, sizeof(struct billboard_texture)); glGenTextures(1, &bt->texture); glBindTexture(GL_TEXTURE_2D, bt->texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cg->metrics.w, cg->metrics.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, cg->pixels); @@ -237,7 +246,7 @@ bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no) cg_free(cg); ht_put_int(r->billboard_textures, cg_no, bt); - return true; + return bt; } static void free_billboard_texture(void *value) @@ -301,8 +310,11 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r) struct mesh *mesh = &model->meshes[i]; struct material *material = &model->materials[mesh->material]; + glUniform1i(r->fog_type, (inst->plugin->fog_mode && !(mesh->flags & MESH_NOLIGHTING)) + ? inst->plugin->fog_type : 0); + GLboolean use_specular_map = GL_FALSE; - if (inst->plugin->specular_mode) { + if (inst->plugin->specular_mode && !(mesh->flags & MESH_NOLIGHTING)) { glUniform1f(r->specular_strength, material->specular_strength); glUniform1f(r->specular_shininess, material->specular_shininess); if (material->specular_map) { @@ -324,7 +336,11 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r) glBindTexture(GL_TEXTURE_2D, material->color_map); glUniform1i(r->texture, COLOR_TEXTURE_UNIT); - if (material->light_map && inst->plugin->light_map_mode) { + if (mesh->flags & MESH_ENVMAP) { + glUniform1i(r->diffuse_type, DIFFUSE_ENV_MAP); + } else if (mesh->flags & MESH_NOLIGHTING) { + glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE); + } else if (material->light_map && inst->plugin->light_map_mode) { glUniform1i(r->diffuse_type, DIFFUSE_LIGHT_MAP); glActiveTexture(GL_TEXTURE0 + LIGHT_TEXTURE_UNIT); glBindTexture(GL_TEXTURE_2D, material->light_map); @@ -343,12 +359,14 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r) } if (material->alpha_map) { - glUniform1i(r->use_alpha_map, GL_TRUE); + glUniform1i(r->alpha_mode, ALPHA_MAP_BLEND); glActiveTexture(GL_TEXTURE0 + ALPHA_TEXTURE_UNIT); glBindTexture(GL_TEXTURE_2D, material->alpha_map); glUniform1i(r->alpha_texture, ALPHA_TEXTURE_UNIT); + } else if (material->flags & MATERIAL_SPRITE) { + glUniform1i(r->alpha_mode, ALPHA_TEST); } else { - glUniform1i(r->use_alpha_map, GL_FALSE); + glUniform1i(r->alpha_mode, ALPHA_BLEND); } glBindVertexArray(mesh->vao); @@ -419,7 +437,8 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma glUniform1i(r->diffuse_type, DIFFUSE_NORMAL); glUniform1i(r->use_normal_map, GL_FALSE); glUniform1i(r->use_shadow_map, GL_FALSE); - glUniform1i(r->use_alpha_map, GL_FALSE); + glUniform1i(r->alpha_mode, ALPHA_BLEND); + glUniform1i(r->fog_type, inst->plugin->fog_mode ? inst->plugin->fog_type : 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, bt->texture); @@ -470,8 +489,6 @@ static void render_billboard_particles(struct RE_renderer *r, struct RE_instance glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); - - glUniform1i(r->fog_type, inst->plugin->fog_type); } static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *inst, struct particle_object *po, float frame) @@ -481,6 +498,7 @@ static void render_polygon_particles(struct RE_renderer *r, struct RE_instance * return; glUniform1i(r->diffuse_type, DIFFUSE_NORMAL); + glUniform1i(r->fog_type, inst->plugin->fog_mode ? inst->plugin->fog_type : 0); for (int index = 0; index < po->nr_particles; index++) { struct particle_instance *pi = &po->instances[index]; @@ -529,7 +547,7 @@ static void render_particle_effect(struct RE_instance *inst, struct RE_renderer glUniform1f(r->rim_exponent, 0.0); glUniform1i(r->use_normal_map, GL_FALSE); glUniform1i(r->use_shadow_map, GL_FALSE); - glUniform1i(r->use_alpha_map, GL_FALSE); + glUniform1i(r->alpha_mode, ALPHA_BLEND); glDepthMask(GL_FALSE); @@ -561,6 +579,26 @@ static void render_particle_effect(struct RE_instance *inst, struct RE_renderer glDepthMask(GL_TRUE); } +static void render_instance(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat) +{ + switch (inst->type) { + case RE_ITYPE_STATIC: + render_static_model(inst, r); + break; + case RE_ITYPE_SKINNED: + render_skinned_model(inst, r); + break; + case RE_ITYPE_BILLBOARD: + render_billboard(inst, r, view_mat); + break; + case RE_ITYPE_PARTICLE_EFFECT: + render_particle_effect(inst, r); + break; + default: + break; + } +} + static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest) { // Compute AABB of shadow casters. @@ -704,6 +742,20 @@ static void setup_lights(struct RE_plugin *plugin) } } +static int cmp_instances_by_z(const void *lhs, const void *rhs) +{ + struct RE_instance *l = *(struct RE_instance **)lhs; + struct RE_instance *r = *(struct RE_instance **)rhs; + float lz = l ? l->pos[2] : FLT_MAX; + float rz = r ? r->pos[2] : FLT_MAX; + return (lz > rz) - (lz < rz); // ascending order. +} + +static bool is_transparent(struct RE_instance *inst) +{ + return inst->is_transparent || inst->alpha < 1.0f; +} + void RE_render(struct sact_sprite *sp) { struct RE_plugin *plugin = (struct RE_plugin *)sp->plugin; @@ -753,8 +805,9 @@ void RE_render(struct sact_sprite *sp) glUniformMatrix4fv(r->shadow_transform, 1, GL_FALSE, shadow_transform[0]); glUniform1f(r->shadow_bias, plugin->shadow_bias); if (plugin->fog_mode) { - glUniform1i(r->fog_type, plugin->fog_type); switch (plugin->fog_type) { + case RE_FOG_NONE: + break; case RE_FOG_LINEAR: glUniform1f(r->fog_near, plugin->fog_near); glUniform1f(r->fog_far, plugin->fog_far); @@ -767,8 +820,6 @@ void RE_render(struct sact_sprite *sp) glUniform3fv(r->ls_sun_color, 1, plugin->ls_sun_color); break; } - } else { - glUniform1i(r->fog_type, 0); } glEnable(GL_BLEND); @@ -776,27 +827,27 @@ void RE_render(struct sact_sprite *sp) setup_lights(plugin); - for (int i = 0; i < plugin->nr_instances; i++) { - struct RE_instance *inst = plugin->instances[i]; - if (!inst) + // Sort instances by z-order. + struct RE_instance **sorted_instances = xmalloc(plugin->nr_instances * sizeof(struct RE_instance *)); + memcpy(sorted_instances, plugin->instances, plugin->nr_instances * sizeof(struct RE_instance *)); + qsort(sorted_instances, plugin->nr_instances, sizeof(struct RE_instance *), cmp_instances_by_z); + + // Render opaque instances, from nearest to farthest. + for (int i = plugin->nr_instances - 1; i >= 0; i--) { + struct RE_instance *inst = sorted_instances[i]; + if (!inst || is_transparent(inst)) continue; - switch (inst->type) { - case RE_ITYPE_STATIC: - render_static_model(inst, r); - break; - case RE_ITYPE_SKINNED: - render_skinned_model(inst, r); - break; - case RE_ITYPE_BILLBOARD: - render_billboard(inst, r, view_transform); - break; - case RE_ITYPE_PARTICLE_EFFECT: - render_particle_effect(inst, r); - break; - default: - break; - } + render_instance(inst, r, view_transform); } + // Render transparent instances, from nearest to farthest. + for (int i = 0; i < plugin->nr_instances; i++) { + struct RE_instance *inst = sorted_instances[i]; + if (!inst || !is_transparent(inst)) + continue; + render_instance(inst, r, view_transform); + } + + free(sorted_instances); plugin->proj_transform[1][1] *= -1; glFrontFace(GL_CCW);