From 4239345289615d3aad7ce673c69a2f9f0a6042fc Mon Sep 17 00:00:00 2001 From: kichikuou Date: Fri, 27 Mar 2026 15:29:09 +0900 Subject: [PATCH] SealEngine: Support POL v4 format POL v4 introduces material groups with texture blending. A material's sub-material can be a "blend group" containing exactly two leaf materials, whose textures are blended using per-vertex weights for smooth terrain transitions. --- shaders/reign.f.glsl | 7 +++ shaders/reign.v.glsl | 6 +++ src/3d/3d_internal.h | 13 +++++- src/3d/model.c | 45 ++++++++++++++++++- src/3d/parser.c | 103 ++++++++++++++++++++++++++++++++++--------- src/3d/renderer.c | 14 ++++++ 6 files changed, 165 insertions(+), 23 deletions(-) diff --git a/shaders/reign.f.glsl b/shaders/reign.f.glsl index 067ce09..2299ac5 100644 --- a/shaders/reign.f.glsl +++ b/shaders/reign.f.glsl @@ -76,6 +76,8 @@ vec3 dir_lights_diffuse(vec3 norm) #endif // ENGINE == REIGN_ENGINE uniform sampler2D tex; +uniform sampler2D blend_tex; +uniform bool use_blend_texture; uniform sampler2D alpha_texture; uniform sampler2D light_texture; uniform sampler2D shadow_texture; @@ -98,6 +100,8 @@ in vec4 shadow_frag_pos; in float dist; in vec3 eye; in vec3 normal; +in float blend_weight; +in vec2 blend_tex_coord; out vec4 frag_color; void main() { @@ -119,6 +123,9 @@ void main() { frag_rgb = texel.rgb; } else { texel = texture(tex, tex_coord); + if (use_blend_texture) { + texel = mix(texel, texture(blend_tex, blend_tex_coord), blend_weight); + } if (diffuse_type == DIFFUSE_EMISSIVE) { frag_rgb = texel.rgb; } else { diff --git a/shaders/reign.v.glsl b/shaders/reign.v.glsl index 08e4f16..12f0973 100644 --- a/shaders/reign.v.glsl +++ b/shaders/reign.v.glsl @@ -72,6 +72,8 @@ in vec4 vertex_color; in vec4 vertex_tangent; in ivec4 vertex_bone_index; in vec4 vertex_bone_weight; +in float vertex_blend_weight; +in vec2 vertex_blend_uv; out vec2 tex_coord; out vec2 light_tex_coord; @@ -81,6 +83,8 @@ out vec4 shadow_frag_pos; out float dist; out vec3 eye; out vec3 normal; +out float blend_weight; +out vec2 blend_tex_coord; void main() { mat4 local_bone_transform = local_transform; @@ -117,6 +121,8 @@ void main() { tex_coord = vertex_uv + uv_scroll; light_tex_coord = vertex_light_uv + uv_scroll; color_mod = vertex_color; + blend_weight = vertex_blend_weight; + blend_tex_coord = vertex_blend_uv + uv_scroll; dist = -view_pos.z; shadow_frag_pos = shadow_transform * world_pos; diff --git a/src/3d/3d_internal.h b/src/3d/3d_internal.h index 7494e2f..8fdb9ed 100644 --- a/src/3d/3d_internal.h +++ b/src/3d/3d_internal.h @@ -74,6 +74,7 @@ struct material { GLuint alpha_map; GLuint light_map; GLuint normal_map; + GLuint blend_texture; float specular_strength; float specular_shininess; float shadow_darkness; @@ -108,6 +109,8 @@ enum RE_attribute_location { VATTR_LIGHT_UV, VATTR_COLOR, VATTR_TANGENT, + VATTR_BLEND_WEIGHT, + VATTR_BLEND_UV, }; struct shadow_renderer { @@ -185,6 +188,8 @@ struct RE_renderer { GLint alpha_mode; GLint alpha_texture; GLint uv_scroll; + GLint blend_tex; + GLint use_blend_texture; GLuint billboard_vao; GLuint billboard_attr_buffer; @@ -431,7 +436,7 @@ struct pol_material { struct pol_material_group { struct pol_material m; uint32_t nr_children; - struct pol_material *children; + struct pol_material_group *children; }; enum mesh_flags { @@ -461,6 +466,10 @@ struct pol_mesh { vec3 *colors; uint32_t nr_alphas; float *alphas; + uint32_t nr_blend_uvs; + vec2 *blend_uvs; + uint32_t nr_blend_weights; + float *blend_weights; uint32_t nr_triangles; struct pol_triangle *triangles; // Parameters in .opr file. @@ -486,6 +495,8 @@ struct pol_triangle { uint32_t light_uv_index[3]; uint32_t color_index[3]; uint32_t alpha_index[3]; + uint32_t blend_uv_index[3]; + uint32_t blend_weight_index[3]; vec3 normals[3]; uint32_t material_group_index; }; diff --git a/src/3d/model.c b/src/3d/model.c index fbef578..5c9c6ea 100644 --- a/src/3d/model.c +++ b/src/3d/model.c @@ -54,6 +54,11 @@ struct vertex_bones { GLfloat bone_weight[NR_WEIGHTS]; }; +struct vertex_blend { + GLfloat blend_weight; + GLfloat blend_uv[2]; +}; + static bool is_transparent_mesh(const struct pol_mesh *mesh) { if (re_plugin_version == RE_REIGN_PLUGIN) @@ -202,6 +207,8 @@ static void destroy_material(struct material *material) glDeleteTextures(1, &material->light_map); if (material->normal_map) glDeleteTextures(1, &material->normal_map); + if (material->blend_texture) + glDeleteTextures(1, &material->blend_texture); } static int cmp_by_bone_weight(const void *lhs, const void *rhs) @@ -276,6 +283,7 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_ bool has_vertex_colors = m->nr_colors > 0 || m->nr_alphas > 0; bool has_normal_map = model->materials[material].normal_map != 0; bool has_bones = !!model->bone_map; + bool has_blend = m->blend_weights && model->materials[material].blend_texture; GLsizei stride = sizeof(struct vertex_common); if (has_light_map) @@ -286,6 +294,8 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_ stride += sizeof(struct vertex_tangent); if (has_bones) stride += sizeof(struct vertex_bones); + if (has_blend) + stride += sizeof(struct vertex_blend); void *buffer = xmalloc(m->nr_triangles * 3 * stride); uint8_t *ptr = buffer; @@ -336,6 +346,14 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_ } } } + if (has_blend) { + struct vertex_blend *v_blend = buf_alloc(&ptr, sizeof(struct vertex_blend)); + v_blend->blend_weight = m->blend_weights[t->blend_weight_index[j]]; + if (m->blend_uvs) + glm_vec2_copy(m->blend_uvs[t->blend_uv_index[j]], v_blend->blend_uv); + else + glm_vec2_copy(m->uvs[t->uv_index[j]], v_blend->blend_uv); + } nr_vertices++; } } @@ -412,6 +430,18 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_ glDisableVertexAttribArray(VATTR_BONE_WEIGHT); glVertexAttrib4f(VATTR_BONE_WEIGHT, 0.0, 0.0, 0.0, 0.0); } + if (has_blend) { + glEnableVertexAttribArray(VATTR_BLEND_WEIGHT); + glVertexAttribPointer(VATTR_BLEND_WEIGHT, 1, GL_FLOAT, GL_FALSE, stride, base + offsetof(struct vertex_blend, blend_weight)); + glEnableVertexAttribArray(VATTR_BLEND_UV); + glVertexAttribPointer(VATTR_BLEND_UV, 2, GL_FLOAT, GL_FALSE, stride, base + offsetof(struct vertex_blend, blend_uv)); + base += sizeof(struct vertex_blend); + } else { + glDisableVertexAttribArray(VATTR_BLEND_WEIGHT); + glVertexAttrib1f(VATTR_BLEND_WEIGHT, 0.0); + glDisableVertexAttribArray(VATTR_BLEND_UV); + glVertexAttrib2f(VATTR_BLEND_UV, 0.0, 0.0); + } assert((intptr_t)base == stride); glBufferData(GL_ARRAY_BUFFER, mesh->nr_vertices * stride, buffer, GL_STATIC_DRAW); @@ -543,8 +573,19 @@ struct model *model_load(struct archive *aar, const char *path) continue; } for (uint32_t j = 0; j < pol->materials[i].nr_children; j++) { - init_material(&model->materials[material_offsets[i] + j], - &pol->materials[i].children[j], amt, aar, path); + struct pol_material_group *child = &pol->materials[i].children[j]; + if (child->nr_children >= 2) { + // Group node: texture blending (base + blend) + init_material(&model->materials[material_offsets[i] + j], + &child->children[0].m, amt, aar, path); + if (child->children[1].m.textures[COLOR_MAP]) { + model->materials[material_offsets[i] + j].blend_texture = + load_texture(aar, path, child->children[1].m.textures[COLOR_MAP], NULL); + } + } else { + init_material(&model->materials[material_offsets[i] + j], + &child->m, amt, aar, path); + } } } diff --git a/src/3d/parser.c b/src/3d/parser.c index b03b7f2..a9c057e 100644 --- a/src/3d/parser.c +++ b/src/3d/parser.c @@ -70,15 +70,14 @@ static uint32_t parse_material_attributes(const char *name) return flags; } -static void parse_material(struct buffer *r, struct pol_material *m) +static void parse_textures(struct buffer *r, int pol_version, 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++) { char *filename = read_cstring(r); int type = buffer_read_int32(r); + if (pol_version >= 3) + buffer_skip(r, 8); // 2 unknown float params if ((unsigned)type < MAX_TEXTURE_TYPE) { m->textures[type] = filename; } else { @@ -95,29 +94,60 @@ static void destroy_material(struct pol_material *m) free(m->textures[i]); } -static void parse_material_group(struct buffer *r, struct pol_material_group *mg) -{ - parse_material(r, &mg->m); - - mg->nr_children = buffer_read_int32(r); - if (mg->nr_children > 0) { - mg->children = xcalloc(mg->nr_children, sizeof(struct pol_material)); - for (uint32_t i = 0; i < mg->nr_children; i++) { - parse_material(r, &mg->children[i]); - } - } -} - static void destroy_material_group(struct pol_material_group *mg) { destroy_material(&mg->m); if (mg->children) { for (uint32_t i = 0; i < mg->nr_children; i++) - destroy_material(&mg->children[i]); + destroy_material_group(&mg->children[i]); free(mg->children); } } +// Material tree layout: +// level 0: top-level material per pol->materials[]. Either a textured leaf +// or a group containing sub-materials, never both. +// level 1: sub-material. Either a textured leaf or, in v4 only, a "blend +// group" (is_group=1) with exactly 2 leaf children. +// level 2: leaf children of a v4 blend group; cannot themselves be a group. +static void parse_material_group(struct buffer *r, int pol_version, + struct pol_material_group *mg, int level) +{ + mg->m.name = read_cstring(r); + mg->m.flags = parse_material_attributes(mg->m.name); + + bool is_group = false; + if (pol_version >= 4) + is_group = buffer_read_int32(r); + + if (is_group) { + if (level >= 2) + ERROR("material group nesting too deep"); + uint32_t uk = buffer_read_int32(r); + if (uk != 0) + ERROR("unexpected nonzero value in material group: %u", uk); + + mg->nr_children = buffer_read_int32(r); + if (level == 1 && mg->nr_children != 2) + ERROR("blend group must have 2 children, got %u", mg->nr_children); + } else { + parse_textures(r, pol_version, &mg->m); + + if (pol_version >= 4) { + uint32_t uk = buffer_read_int32(r); + if (uk != 0) + ERROR("unexpected nonzero value in material leaf: %u", uk); + } else if (level == 0) { + mg->nr_children = buffer_read_int32(r); + } + } + if (mg->nr_children > 0) { + mg->children = xcalloc(mg->nr_children, sizeof(struct pol_material_group)); + for (uint32_t i = 0; i < mg->nr_children; i++) + parse_material_group(r, pol_version, &mg->children[i], level + 1); + } +} + static void parse_vertex(struct buffer *r, int pol_version, struct pol_vertex *v) { read_position(r, v->pos); @@ -151,6 +181,11 @@ static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle t->light_uv_index[1] = buffer_read_int32(r) - mesh->nr_uvs; t->light_uv_index[2] = buffer_read_int32(r) - mesh->nr_uvs; } + if (mesh->blend_uvs) { + t->blend_uv_index[0] = buffer_read_int32(r) - mesh->nr_uvs - mesh->nr_light_uvs; + t->blend_uv_index[1] = buffer_read_int32(r) - mesh->nr_uvs - mesh->nr_light_uvs; + t->blend_uv_index[2] = buffer_read_int32(r) - mesh->nr_uvs - mesh->nr_light_uvs; + } t->color_index[0] = buffer_read_int32(r); t->color_index[1] = buffer_read_int32(r); @@ -160,6 +195,11 @@ static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle t->alpha_index[1] = buffer_read_int32(r); t->alpha_index[2] = buffer_read_int32(r); } + if (mesh->blend_weights) { + t->blend_weight_index[0] = buffer_read_int32(r); + t->blend_weight_index[1] = buffer_read_int32(r); + t->blend_weight_index[2] = buffer_read_int32(r); + } read_direction(r, t->normals[0]); read_direction(r, t->normals[1]); @@ -227,6 +267,17 @@ static struct pol_mesh *parse_mesh(struct buffer *r, const struct pol *pol) } } + if (pol->version >= 4) { + mesh->nr_blend_uvs = buffer_read_int32(r); + if (mesh->nr_blend_uvs > 0) { + mesh->blend_uvs = xcalloc(mesh->nr_blend_uvs, sizeof(vec2)); + for (uint32_t i = 0; i < mesh->nr_blend_uvs; i++) { + mesh->blend_uvs[i][0] = buffer_read_float(r); + mesh->blend_uvs[i][1] = buffer_read_float(r); + } + } + } + mesh->nr_colors = buffer_read_int32(r); if (mesh->nr_colors > 0) { mesh->colors = xcalloc(mesh->nr_colors, sizeof(vec3)); @@ -254,6 +305,16 @@ static struct pol_mesh *parse_mesh(struct buffer *r, const struct pol *pol) } } + if (pol->version >= 4) { + mesh->nr_blend_weights = buffer_read_int32(r); + if (mesh->nr_blend_weights > 0) { + mesh->blend_weights = xcalloc(mesh->nr_blend_weights, sizeof(float)); + for (uint32_t i = 0; i < mesh->nr_blend_weights; i++) { + mesh->blend_weights[i] = buffer_read_u8(r) / 255.f; + } + } + } + mesh->nr_triangles = buffer_read_int32(r); mesh->triangles = xcalloc(mesh->nr_triangles, sizeof(struct pol_triangle)); for (uint32_t i = 0; i < mesh->nr_triangles; i++) { @@ -280,8 +341,10 @@ static void free_mesh(struct pol_mesh *mesh) free(mesh->vertices); free(mesh->uvs); free(mesh->light_uvs); + free(mesh->blend_uvs); free(mesh->colors); free(mesh->alphas); + free(mesh->blend_weights); free(mesh->triangles); free(mesh); } @@ -310,7 +373,7 @@ struct pol *pol_parse(uint8_t *data, size_t size) struct pol *pol = xcalloc(1, sizeof(struct pol)); pol->version = buffer_read_int32(&r); - if (pol->version != 1 && pol->version != 2) { + if (pol->version != 1 && pol->version != 2 && pol->version != 4) { WARNING("unknown POL version: %d", pol->version); free(pol); return NULL; @@ -318,7 +381,7 @@ struct pol *pol_parse(uint8_t *data, size_t size) pol->nr_materials = buffer_read_int32(&r); pol->materials = xcalloc(pol->nr_materials, sizeof(struct pol_material_group)); for (uint32_t i = 0; i < pol->nr_materials; i++) { - parse_material_group(&r, &pol->materials[i]); + parse_material_group(&r, pol->version, &pol->materials[i], 0); } pol->nr_meshes = buffer_read_int32(&r); diff --git a/src/3d/renderer.c b/src/3d/renderer.c index b92c45e..0b913c3 100644 --- a/src/3d/renderer.c +++ b/src/3d/renderer.c @@ -39,6 +39,7 @@ enum { LIGHT_TEXTURE_UNIT, NORMAL_TEXTURE_UNIT, SHADOW_TEXTURE_UNIT, + BLEND_TEXTURE_UNIT, }; enum { @@ -89,6 +90,8 @@ static GLuint load_shader(const char *vertex_shader_path, const char *fragment_s glBindAttribLocation(program, VATTR_LIGHT_UV, "vertex_light_uv"); glBindAttribLocation(program, VATTR_COLOR, "vertex_color"); glBindAttribLocation(program, VATTR_TANGENT, "vertex_tangent"); + glBindAttribLocation(program, VATTR_BLEND_WEIGHT, "vertex_blend_weight"); + glBindAttribLocation(program, VATTR_BLEND_UV, "vertex_blend_uv"); glLinkProgram(program); @@ -258,6 +261,8 @@ struct RE_renderer *RE_renderer_new(void) r->alpha_mode = glGetUniformLocation(r->program, "alpha_mode"); r->alpha_texture = glGetUniformLocation(r->program, "alpha_texture"); r->uv_scroll = glGetUniformLocation(r->program, "uv_scroll"); + r->blend_tex = glGetUniformLocation(r->program, "blend_tex"); + r->use_blend_texture = glGetUniformLocation(r->program, "use_blend_texture"); glGenRenderbuffers(1, &r->depth_buffer); @@ -447,6 +452,15 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d glUniform1i(r->alpha_mode, mesh->is_transparent ? ALPHA_BLEND : ALPHA_TEST); } + if (material->blend_texture) { + glUniform1i(r->use_blend_texture, GL_TRUE); + glActiveTexture(GL_TEXTURE0 + BLEND_TEXTURE_UNIT); + glBindTexture(GL_TEXTURE_2D, material->blend_texture); + glUniform1i(r->blend_tex, BLEND_TEXTURE_UNIT); + } else { + glUniform1i(r->use_blend_texture, GL_FALSE); + } + vec2 uv_scroll; glm_vec2_scale(mesh->uv_scroll, r->last_frame_timestamp / 1000.f, uv_scroll); glUniform2fv(r->uv_scroll, 1, uv_scroll);