Merge pull request #201 from kichikuou/outline

TapirEngine: Outline rendering
This commit is contained in:
Nunuhara Cabbage
2024-10-13 11:28:53 -07:00
committed by GitHub
9 changed files with 271 additions and 19 deletions
+7 -1
View File
@@ -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;
+22
View File
@@ -0,0 +1,22 @@
/* Copyright (C) 2024 kichikuou <KichikuouChrome@gmail.com>
*
* 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 <http://gnu.org/licenses/>.
*/
uniform vec3 outline_color;
out vec4 frag_color;
void main() {
frag_color = vec4(outline_color, 1.0);
}
+53
View File
@@ -0,0 +1,53 @@
/* Copyright (C) 2024 kichikuou <KichikuouChrome@gmail.com>
*
* 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 <http://gnu.org/licenses/>.
*/
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);
}
+4 -4
View File
@@ -14,7 +14,7 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
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);
}
+33 -8
View File
@@ -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 {
+13
View File
@@ -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)
+61
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -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);
+77 -5
View File
@@ -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);