diff --git a/include/reign.h b/include/reign.h index 96360e8..d630f6c 100644 --- a/include/reign.h +++ b/include/reign.h @@ -63,10 +63,16 @@ enum RE_draw_type { }; enum RE_draw_options { - RE_DRAW_OPTION_UNKNOWN, + RE_DRAW_OPTION_EDGE = 0, RE_DRAW_OPTION_MAX }; +enum RE_draw_edge_mode { + RE_DRAW_EDGE_NONE = 0, + RE_DRAW_EDGE_CHARACTERS_ONLY = 1, + RE_DRAW_EDGE_ALL = 2, +}; + struct RE_options { int anti_aliasing; int wait_vsync; diff --git a/shaders/reign_outline.f.glsl b/shaders/reign_outline.f.glsl new file mode 100644 index 0000000..6175b92 --- /dev/null +++ b/shaders/reign_outline.f.glsl @@ -0,0 +1,22 @@ +/* Copyright (C) 2024 kichikuou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +uniform vec3 outline_color; +out vec4 frag_color; + +void main() { + frag_color = vec4(outline_color, 1.0); +} diff --git a/shaders/reign_outline.v.glsl b/shaders/reign_outline.v.glsl new file mode 100644 index 0000000..ee82afd --- /dev/null +++ b/shaders/reign_outline.v.glsl @@ -0,0 +1,53 @@ +/* Copyright (C) 2024 kichikuou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +uniform mat4 local_transform; +uniform mat4 view_transform; +uniform mat4 proj_transform; +uniform mat3 normal_transform; + +const int MAX_BONES = 308; // see 3d_internal.h +const int NR_WEIGHTS = 4; +uniform bool has_bones; +layout(std140) uniform BoneTransforms { + mat4 bone_matrices[MAX_BONES]; +}; +uniform float outline_thickness; + +in vec3 vertex_pos; +in vec3 vertex_normal; +in ivec4 vertex_bone_index; +in vec4 vertex_bone_weight; + +void main() { + mat4 local_bone_transform = local_transform; + mat3 normal_bone_transform = normal_transform; + if (has_bones) { + mat4 bone_transform = mat4(0.f); + for (int i = 0; i < NR_WEIGHTS; i++) { + if (vertex_bone_index[i] >= 0) { + bone_transform += bone_matrices[vertex_bone_index[i]] * vertex_bone_weight[i]; + } + } + local_bone_transform *= bone_transform; + normal_bone_transform *= mat3(bone_transform); + } + + vec4 pos = local_bone_transform * vec4(vertex_pos, 1.0); + vec3 normal = normalize(normal_bone_transform * vertex_normal); + + gl_Position = proj_transform * view_transform * (pos + vec4(normal, 0.0) * outline_thickness); +} diff --git a/shaders/reign_shadow.v.glsl b/shaders/reign_shadow.v.glsl index 6af53c5..9a2729d 100644 --- a/shaders/reign_shadow.v.glsl +++ b/shaders/reign_shadow.v.glsl @@ -14,7 +14,7 @@ * along with this program; if not, see . */ -uniform mat4 world_transform; +uniform mat4 local_transform; uniform mat4 view_transform; const int MAX_BONES = 308; // see 3d_internal.h @@ -29,7 +29,7 @@ in ivec4 vertex_bone_index; in vec4 vertex_bone_weight; void main() { - mat4 world_bone_transform = world_transform; + mat4 local_bone_transform = local_transform; if (has_bones) { mat4 bone_transform = mat4(0.f); for (int i = 0; i < NR_WEIGHTS; i++) { @@ -37,8 +37,8 @@ void main() { bone_transform += bone_matrices[vertex_bone_index[i]] * vertex_bone_weight[i]; } } - world_bone_transform *= bone_transform; + local_bone_transform *= bone_transform; } - gl_Position = view_transform * world_bone_transform * vec4(vertex_pos, 1.0); + gl_Position = view_transform * local_bone_transform * vec4(vertex_pos, 1.0); } diff --git a/src/3d/3d_internal.h b/src/3d/3d_internal.h index b0d2f05..55d4415 100644 --- a/src/3d/3d_internal.h +++ b/src/3d/3d_internal.h @@ -57,6 +57,9 @@ struct mesh { int nr_vertices; int nr_indices; int material; + vec3 outline_color; + float outline_thickness; + vec2 uv_scroll; }; struct material { @@ -108,18 +111,31 @@ struct shadow_renderer { GLuint texture; // Uniform variable locations - GLint world_transform; + GLint local_transform; GLint view_transform; GLint has_bones; }; +struct outline_renderer { + GLuint program; + + // Uniform variable locations + GLint local_transform; + GLint view_transform; + GLint proj_transform; + GLint normal_transform; + GLint has_bones; + GLint outline_color; + GLint outline_thickness; +}; + struct RE_renderer { GLuint program; GLuint depth_buffer; struct shadow_renderer shadow; + struct outline_renderer outline; // Uniform variable locations - GLint world_transform; GLint view_transform; GLint texture; GLint local_transform; @@ -190,7 +206,7 @@ struct archive_data *RE_get_aar_entry(struct archive *aar, const char *dir, cons // renderer.c -struct RE_renderer *RE_renderer_new(void); +struct RE_renderer *RE_renderer_new(enum RE_plugin_version version); void RE_renderer_free(struct RE_renderer *r); void RE_renderer_set_viewport_size(struct RE_renderer *r, int width, int height); bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no); @@ -407,11 +423,14 @@ struct pol_material_group { }; enum mesh_flags { - MESH_NOLIGHTING = 1 << 0, - MESH_NOMAKESHADOW = 1 << 1, - MESH_ENVMAP = 1 << 2, - MESH_BOTH = 1 << 3, - MESH_SPRITE = 1 << 4, + MESH_NOLIGHTING = 1 << 0, + MESH_NOMAKESHADOW = 1 << 1, + MESH_ENVMAP = 1 << 2, + MESH_BOTH = 1 << 3, + MESH_SPRITE = 1 << 4, + MESH_BLEND_ADDITIVE = 1 << 5, + MESH_NO_EDGE = 1 << 6, + MESH_NO_HEIGHT_DETECTION = 1 << 7, }; struct pol_mesh { @@ -428,6 +447,10 @@ struct pol_mesh { vec3 *colors; uint32_t nr_triangles; struct pol_triangle *triangles; + // Parameters in .opr file. + Color edge_color; + float edge_size; + vec2 uv_scroll; }; struct pol_vertex { @@ -512,6 +535,8 @@ struct amt *amt_parse(uint8_t *data, size_t size); void amt_free(struct amt *amt); struct amt_material *amt_find_material(struct amt *amt, const char *name); +void opr_load(uint8_t *data, size_t size, struct pol *pol); + // collision.c struct collider { diff --git a/src/3d/model.c b/src/3d/model.c index 04e1495..f698811 100644 --- a/src/3d/model.c +++ b/src/3d/model.c @@ -29,6 +29,7 @@ #define FP16_MIN 6.103516e-5f #define NR_WEIGHTS 4 +#define DEFAULT_OUTLINE_THICKNESS 0.003 struct vertex_common { GLfloat pos[3]; @@ -298,6 +299,11 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_ struct mesh *mesh = &model->meshes[model->nr_meshes++]; mesh->flags = m->flags; mesh->material = material; + mesh->outline_color[0] = m->edge_color.r / 255.f; + mesh->outline_color[1] = m->edge_color.g / 255.f; + mesh->outline_color[2] = m->edge_color.b / 255.f; + mesh->outline_thickness = m->edge_size ? m->edge_size : DEFAULT_OUTLINE_THICKNESS; + glm_vec2_copy(m->uv_scroll, mesh->uv_scroll); mesh->nr_vertices = nr_vertices; glGenVertexArrays(1, &mesh->vao); @@ -440,6 +446,13 @@ struct model *model_load(struct archive *aar, const char *path) struct model *model = xcalloc(1, sizeof(struct model)); model->path = strdup(path); + // Load .opr file, if any + struct archive_data *opr_file = RE_get_aar_entry(aar, path, basename, ".opr"); + if (opr_file) { + opr_load(opr_file->data, opr_file->size, pol); + archive_free_data(opr_file); + } + // Bones if (pol->nr_bones > 0) { if (pol->nr_bones > MAX_BONES) diff --git a/src/3d/parser.c b/src/3d/parser.c index c37271c..9bc32a6 100644 --- a/src/3d/parser.c +++ b/src/3d/parser.c @@ -473,3 +473,64 @@ struct amt_material *amt_find_material(struct amt *amt, const char *name) } return NULL; } + +void opr_load(uint8_t *data, size_t size, struct pol *pol) +{ + struct pol_mesh *current_mesh = NULL; + while (size > 0) { + char line[200]; + uint8_t *nl = memchr(data, '\n', size); + if (nl) + nl++; + else + nl = data + size; + int copylen = min(nl - data, sizeof(line) - 1); + memcpy(line, (char *)data, copylen); + line[copylen] = '\0'; + size -= nl - data; + data = nl; + + char s[200]; + if (sscanf(line, "Mesh = \"%[^\"]\"", s) == 1 || + sscanf(line, "MeshPart = \"%[^\"]\"", s) == 1) { + current_mesh = NULL; + for (int i = 0; i < pol->nr_meshes; i++) { + if (pol->meshes[i] && !strcmp(pol->meshes[i]->name, s)) { + current_mesh = pol->meshes[i]; + break; + } + } + continue; + } + if (!current_mesh) + continue; + + int i1, i2, i3; + float f1, f2; + if (sscanf(line, "BlendMode = %s", s) == 1) { + if (!strcmp(s, "Add")) + current_mesh->flags |= MESH_BLEND_ADDITIVE; + else + WARNING("unknown BlendMode: %s", s); + } else if (sscanf(line, "Edge = %d", &i1) == 1) { + if (i1 == 0) + current_mesh->flags |= MESH_NO_EDGE; + else + WARNING("invalid Edge value: %d", i1); + } else if (sscanf(line, "EdgeColor = ( %d , %d , %d )", &i1, &i2, &i3) == 3) { + current_mesh->edge_color = COLOR(i1, i2, i3, 255); + } else if (sscanf(line, "EdgeSize = %f", &f1) == 1) { + current_mesh->edge_size = f1; + } else if (sscanf(line, "HeightDetection = %s", s) == 1) { + if (!strcmp(s, "false")) + current_mesh->flags |= MESH_NO_HEIGHT_DETECTION; + else + WARNING("invalid HeightDetection: %s", s); + } else if (sscanf(line, "UVScroll = ( %f , %f )", &f1, &f2) == 2) { + current_mesh->uv_scroll[0] = f1; + current_mesh->uv_scroll[1] = f2; + } else if (strchr(line, '=')) { + WARNING("unknown field: %s", line); + } + } +} diff --git a/src/3d/reign.c b/src/3d/reign.c index 929e189..0bfd762 100644 --- a/src/3d/reign.c +++ b/src/3d/reign.c @@ -260,7 +260,7 @@ bool RE_plugin_bind(struct RE_plugin *plugin, int sprite) struct sact_sprite *sp = sact_try_get_sprite(sprite); if (!sp) return false; - plugin->renderer = RE_renderer_new(); + plugin->renderer = RE_renderer_new(plugin->version); plugin->sprite = sprite; sprite_bind_plugin(sp, &plugin->plugin); struct texture *texture = sprite_get_texture(sp); diff --git a/src/3d/renderer.c b/src/3d/renderer.c index e80d42d..2a59a64 100644 --- a/src/3d/renderer.c +++ b/src/3d/renderer.c @@ -99,7 +99,7 @@ static void init_shadow_renderer(struct shadow_renderer *sr) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &orig_fbo); sr->program = load_shader("shaders/reign_shadow.v.glsl", "shaders/reign_shadow.f.glsl"); - sr->world_transform = glGetUniformLocation(sr->program, "world_transform"); + sr->local_transform = glGetUniformLocation(sr->program, "local_transform"); sr->view_transform = glGetUniformLocation(sr->program, "view_transform"); sr->has_bones = glGetUniformLocation(sr->program, "has_bones"); GLuint bone_transforms = glGetUniformBlockIndex(sr->program, "BoneTransforms"); @@ -128,6 +128,30 @@ static void destroy_shadow_renderer(struct shadow_renderer *sr) glDeleteProgram(sr->program); } +static void init_outline_renderer(struct outline_renderer *or, enum RE_plugin_version version) +{ + if (version < RE_TAPIR_PLUGIN) { + or->program = 0; + return; + } + or->program = load_shader("shaders/reign_outline.v.glsl", "shaders/reign_outline.f.glsl"); + or->local_transform = glGetUniformLocation(or->program, "local_transform"); + or->view_transform = glGetUniformLocation(or->program, "view_transform"); + or->proj_transform = glGetUniformLocation(or->program, "proj_transform"); + or->normal_transform = glGetUniformLocation(or->program, "normal_transform"); + or->has_bones = glGetUniformLocation(or->program, "has_bones"); + GLuint bone_transforms = glGetUniformBlockIndex(or->program, "BoneTransforms"); + glUniformBlockBinding(or->program, bone_transforms, BONE_TRANSFORMS_BINDING); + or->outline_color = glGetUniformLocation(or->program, "outline_color"); + or->outline_thickness = glGetUniformLocation(or->program, "outline_thickness"); +} + +static void destroy_outline_renderer(struct outline_renderer *or) +{ + if (or->program) + glDeleteProgram(or->program); +} + static void init_billboard_mesh(struct RE_renderer *r) { static const GLfloat vertices[] = { @@ -171,12 +195,11 @@ static void destroy_billboard_mesh(struct RE_renderer *r) glDeleteBuffers(1, &r->billboard_attr_buffer); } -struct RE_renderer *RE_renderer_new(void) +struct RE_renderer *RE_renderer_new(enum RE_plugin_version version) { struct RE_renderer *r = xcalloc(1, sizeof(struct RE_renderer)); r->program = load_shader("shaders/reign.v.glsl", "shaders/reign.f.glsl"); - r->world_transform = glGetUniformLocation(r->program, "world_transform"); r->view_transform = glGetUniformLocation(r->program, "view_transform"); r->texture = glGetUniformLocation(r->program, "tex"); r->local_transform = glGetUniformLocation(r->program, "local_transform"); @@ -226,6 +249,7 @@ struct RE_renderer *RE_renderer_new(void) glGenRenderbuffers(1, &r->depth_buffer); init_shadow_renderer(&r->shadow); + init_outline_renderer(&r->outline, version); init_billboard_mesh(r); r->billboard_textures = ht_create(256); r->last_frame_timestamp = SDL_GetTicks(); @@ -280,6 +304,7 @@ void RE_renderer_free(struct RE_renderer *r) ht_free_int(r->billboard_textures); destroy_billboard_mesh(r); destroy_shadow_renderer(&r->shadow); + destroy_outline_renderer(&r->outline); free(r); } @@ -729,7 +754,7 @@ static void render_shadow_map(struct RE_plugin *plugin, mat4 light_space_transfo } else { glUniform1i(r->shadow.has_bones, GL_FALSE); } - glUniformMatrix4fv(r->shadow.world_transform, 1, GL_FALSE, inst->local_transform[0]); + glUniformMatrix4fv(r->shadow.local_transform, 1, GL_FALSE, inst->local_transform[0]); for (int j = 0; j < model->nr_meshes; j++) { struct mesh *mesh = &model->meshes[j]; if (mesh->flags & MESH_NOMAKESHADOW) @@ -745,6 +770,49 @@ static void render_shadow_map(struct RE_plugin *plugin, mat4 light_space_transfo glViewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]); } +static void render_outlines(struct RE_plugin *plugin, mat4 view_transform) +{ + enum RE_draw_edge_mode mode = plugin->draw_options[RE_DRAW_OPTION_EDGE]; + if (mode == RE_DRAW_EDGE_NONE) + return; + struct outline_renderer *or = &plugin->renderer->outline; + if (!or->program) + return; + glUseProgram(or->program); + glUniformMatrix4fv(or->view_transform, 1, GL_FALSE, view_transform[0]); + glUniformMatrix4fv(or->proj_transform, 1, GL_FALSE, plugin->proj_transform[0]); + + glCullFace(GL_FRONT); + for (int i = 0; i < plugin->nr_instances; i++) { + struct RE_instance *inst = plugin->instances[i]; + if (!inst || !inst->draw) + continue; + if (mode == RE_DRAW_EDGE_CHARACTERS_ONLY && inst->type != RE_ITYPE_SKINNED) + continue; + struct model *model = inst->model; + if (inst->model && model->nr_bones > 0) { + glUniform1i(or->has_bones, GL_TRUE); + glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, inst->bone_transforms_ubo); + } else { + glUniform1i(or->has_bones, GL_FALSE); + } + glUniformMatrix4fv(or->local_transform, 1, GL_FALSE, inst->local_transform[0]); + glUniformMatrix3fv(or->normal_transform, 1, GL_FALSE, inst->normal_transform[0]); + for (int j = 0; j < model->nr_meshes; j++) { + struct mesh *mesh = &model->meshes[j]; + if (mesh->flags & MESH_NO_EDGE) + continue; + glUniform3fv(or->outline_color, 1, mesh->outline_color); + glUniform1f(or->outline_thickness, mesh->outline_thickness); + glBindVertexArray(mesh->vao); + glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices); + } + glBindVertexArray(0); + } + glCullFace(GL_BACK); + glUseProgram(plugin->renderer->program); +} + static void render_back_cg(struct texture *dst, struct RE_back_cg *bcg, struct RE_renderer *r) { if (!bcg->show || !bcg->texture.handle) @@ -912,6 +980,10 @@ void RE_render(struct sact_sprite *sp) continue; render_instance(inst, r, view_transform, DRAW_OPAQUE); } + + // Render outlines. + render_outlines(plugin, view_transform); + // Render transparent instances, from farthest to nearest. for (int i = 0; i < plugin->nr_instances; i++) { struct RE_instance *inst = sorted_instances[i]; @@ -975,7 +1047,7 @@ struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); glClear(GL_DEPTH_BUFFER_BIT); glUseProgram(r->shadow.program); - glUniformMatrix4fv(r->shadow.world_transform, 1, GL_FALSE, view_matrix[0]); + glUniformMatrix4fv(r->shadow.local_transform, 1, GL_FALSE, view_matrix[0]); glUniformMatrix4fv(r->shadow.view_transform, 1, GL_FALSE, proj_matrix[0]); glUniform1i(r->shadow.has_bones, GL_FALSE);