diff --git a/include/reign.h b/include/reign.h index 5ece7b9..dea70c4 100644 --- a/include/reign.h +++ b/include/reign.h @@ -109,6 +109,7 @@ struct RE_instance { vec3 scale; float alpha; bool draw; + bool draw_bump; float fps; bool motion_blend; float motion_blend_rate; diff --git a/shaders/reign.f.glsl b/shaders/reign.f.glsl index 791f79b..bb67130 100644 --- a/shaders/reign.f.glsl +++ b/shaders/reign.f.glsl @@ -26,30 +26,41 @@ uniform sampler2D tex; uniform sampler2D specular_texture; uniform float alpha_mod; +uniform bool use_normal_map; +uniform sampler2D normal_texture; + uniform vec3 ambient; uniform dir_light dir_lights[NR_DIR_LIGHTS]; -uniform vec3 specular_light_dir; uniform float specular_strength; uniform float specular_shininess; uniform bool use_specular_map; uniform float rim_exponent; uniform vec3 rim_color; -uniform vec3 view_pos; in vec2 tex_coord; -in vec3 normal; in vec3 frag_pos; +in vec3 eye; +in vec3 normal; +in vec3 light_dir[NR_DIR_LIGHTS]; +in vec3 specular_dir; out vec4 frag_color; void main() { - vec3 norm = normalize(normal); - vec3 view_dir = normalize(view_pos - frag_pos); + vec3 norm; + if (use_normal_map) { + // NOTE: TT3's shader doesn't normalize this vector. + norm = texture(normal_texture, tex_coord).rgb * 2.0 - 1.0; + } else { + norm = normalize(normal); + } + + vec3 view_dir = normalize(eye - frag_pos); vec3 frag_rgb = ambient; // Diffuse lighting vec3 diffuse = vec3(0.0); for (int i = 0; i < NR_DIR_LIGHTS; i++) { - float half_lambert = dot(norm, -dir_lights[i].dir) * 0.5 + 0.5; + 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); } vec4 texel = texture(tex, tex_coord); @@ -57,7 +68,7 @@ void main() { // Specular lighting if (specular_strength > 0.0) { - vec3 reflect_dir = reflect(specular_light_dir, norm); + vec3 reflect_dir = reflect(specular_dir, norm); float specular = pow(max(dot(view_dir, reflect_dir), 0.0), specular_shininess) * specular_strength; if (use_specular_map) { specular *= texture(specular_texture, tex_coord).r; @@ -66,8 +77,9 @@ void main() { } // Rim lighting - if (rim_exponent > 0) { - frag_rgb += pow(1.0 - max(dot(norm, view_dir), 0.0), rim_exponent) * rim_color; + 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; } frag_color = vec4(frag_rgb, texel.a * alpha_mod); diff --git a/shaders/reign.v.glsl b/shaders/reign.v.glsl index 832b229..c402c59 100644 --- a/shaders/reign.v.glsl +++ b/shaders/reign.v.glsl @@ -14,6 +14,14 @@ * along with this program; if not, see . */ +#define NR_DIR_LIGHTS 3 + +struct dir_light { + vec3 dir; + vec3 diffuse; + vec3 globe_diffuse; +}; + uniform mat4 local_transform; uniform mat4 view_transform; uniform mat4 proj_transform; @@ -24,31 +32,63 @@ const int NR_WEIGHTS = 4; uniform bool has_bones; uniform mat4 bone_matrices[MAX_BONES]; +uniform bool use_normal_map; + +uniform vec3 view_pos; +uniform dir_light dir_lights[NR_DIR_LIGHTS]; +uniform vec3 specular_light_dir; + in vec3 vertex_pos; in vec3 vertex_normal; in vec2 vertex_uv; +in vec4 vertex_tangent; in ivec4 vertex_bone_index; in vec4 vertex_bone_weight; out vec2 tex_coord; -out vec3 normal; out vec3 frag_pos; +out vec3 eye; +out vec3 normal; +out vec3 light_dir[NR_DIR_LIGHTS]; +out vec3 specular_dir; void main() { - mat4 bone_transform = mat4(1.0f); + mat4 local_bone_transform = local_transform; + mat3 normal_bone_transform = normal_transform; if (has_bones) { - bone_transform = mat4(0.f); + 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 world_pos = local_transform * bone_transform * vec4(vertex_pos, 1.0); + // World-space normal vector. + normal = normal_bone_transform * vertex_normal; + + mat3 TBN = mat3(1.0f); + if (use_normal_map) { + 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); gl_Position = proj_transform * view_transform * world_pos; - normal = normal_transform * mat3(bone_transform) * vertex_normal; tex_coord = vertex_uv; - frag_pos = vec3(world_pos); + + // These are in tangent-space if use_normal_map is true, in world-space + // otherwise. + frag_pos = TBN * vec3(world_pos); + eye = TBN * view_pos; + light_dir[0] = TBN * dir_lights[0].dir; + light_dir[1] = TBN * dir_lights[1].dir; + light_dir[2] = TBN * dir_lights[2].dir; + specular_dir = TBN * specular_light_dir; } diff --git a/src/3d/3d_internal.h b/src/3d/3d_internal.h index 9877710..16cc869 100644 --- a/src/3d/3d_internal.h +++ b/src/3d/3d_internal.h @@ -48,6 +48,7 @@ struct mesh { struct material { GLuint color_map; GLuint specular_map; + GLuint normal_map; float specular_strength; float specular_shininess; float rim_exponent; @@ -97,10 +98,13 @@ struct RE_renderer { GLint rim_exponent; GLint rim_color; GLint view_pos; + GLint use_normal_map; + GLint normal_texture; // Attribute variable locations GLint vertex_normal; GLint vertex_uv; + GLint vertex_tangent; GLint vertex_bone_index; GLint vertex_bone_weight; @@ -144,6 +148,7 @@ struct pol { enum pol_texture_type { COLOR_MAP = 1, SPECULAR_MAP = 4, + NORMAL_MAP = 8, MAX_TEXTURE_TYPE }; diff --git a/src/3d/model.c b/src/3d/model.c index 8e9099f..fdec354 100644 --- a/src/3d/model.c +++ b/src/3d/model.c @@ -32,6 +32,7 @@ struct vertex { GLfloat pos[3]; GLfloat normal[3]; GLfloat uv[2]; + GLfloat tangent[4]; GLint bone_id[NR_WEIGHTS]; GLfloat bone_weight[NR_WEIGHTS]; }; @@ -87,6 +88,9 @@ static bool init_material(struct material *material, const struct pol_material * if (m->textures[SPECULAR_MAP]) { material->specular_map = load_texture(aar, path, m->textures[SPECULAR_MAP]); } + if (m->textures[NORMAL_MAP]) { + material->normal_map = load_texture(aar, path, m->textures[NORMAL_MAP]); + } struct amt_material *amt_m = amt ? amt_find_material(amt, m->name) : NULL; if (amt_m) { @@ -109,6 +113,8 @@ static void destroy_material(struct material *material) glDeleteTextures(1, &material->color_map); if (material->specular_map) glDeleteTextures(1, &material->specular_map); + if (material->normal_map) + glDeleteTextures(1, &material->normal_map); } static int cmp_by_bone_weight(const void *lhs, const void *rhs) @@ -130,19 +136,65 @@ static void sort_and_normalize_bone_weights(struct pol_vertex *v) } } +static void calc_tangent(struct pol_mesh *m, struct pol_triangle *t, vec4 tangent[3]) +{ + vec3 v1, v2; + glm_vec3_sub(m->vertices[t->vert_index[1]].pos, m->vertices[t->vert_index[0]].pos, v1); + glm_vec3_sub(m->vertices[t->vert_index[2]].pos, m->vertices[t->vert_index[0]].pos, v2); + + vec2 w1, w2; + glm_vec2_sub(m->uvs[t->uv_index[1]], m->uvs[t->uv_index[0]], w1); + glm_vec2_sub(m->uvs[t->uv_index[2]], m->uvs[t->uv_index[0]], w2); + + float r = 1.0 / (w1[0] * w2[1] - w2[0] * w1[1]); + if (!isfinite(r)) // degenerate uv triangle + r = 1.0; // ?? + + vec3 sdir, tdir; + glm_vec3_scale(v1, w2[1], sdir); + glm_vec3_muladds(v2, -w1[1], sdir); + glm_vec3_scale(sdir, r, sdir); + + glm_vec3_scale(v2, w1[0], tdir); + glm_vec3_muladds(v1, -w2[0], tdir); + glm_vec3_scale(tdir, r, tdir); + + for (int i = 0; i < 3; i++) { + vec3 s; + glm_vec3_copy(sdir, s); + + // Gram-Schmidt orthogonalize. + glm_vec3_muladds(t->normals[i], -glm_vec3_dot(t->normals[i], s), s); + glm_vec3_normalize(s); + + // Calculate handedness. + vec3 c; + glm_vec3_cross(t->normals[i], sdir, c); + float w = (glm_vec3_dot(c, tdir) < 0.0) ? -1.0 : 1.0; + + glm_vec4(s, w, tangent[i]); + } +} + static void add_mesh(struct model *model, struct pol_mesh *m, int material_index, int material, struct RE_renderer *r) { + bool has_normal_map = model->materials[material].normal_map != 0; + struct vertex *buf = xmalloc(m->nr_triangles * 3 * sizeof(struct vertex)); int v = 0; for (uint32_t i = 0; i < m->nr_triangles; i++) { struct pol_triangle *t = &m->triangles[i]; if (material_index >= 0 && t->material != (uint32_t)material_index) continue; + vec4 tangent[3]; + if (has_normal_map) + calc_tangent(m, t, tangent); for (int j = 0; j < 3; j++) { struct pol_vertex *vert = &m->vertices[t->vert_index[j]]; glm_vec3_copy(vert->pos, buf[v].pos); glm_vec3_copy(t->normals[j], buf[v].normal); glm_vec2_copy(m->uvs[t->uv_index[j]], buf[v].uv); + glm_vec4_copy(tangent[j], buf[v].tangent); sort_and_normalize_bone_weights(vert); for (uint32_t k = 0; k < NR_WEIGHTS; k++) { if (model->bone_map && k < vert->nr_weights) { @@ -179,6 +231,8 @@ static void add_mesh(struct model *model, struct pol_mesh *m, int material_index glVertexAttribPointer(r->vertex_normal, 3, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)offsetof(struct vertex, normal)); glEnableVertexAttribArray(r->vertex_uv); glVertexAttribPointer(r->vertex_uv, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)offsetof(struct vertex, uv)); + glEnableVertexAttribArray(r->vertex_tangent); + glVertexAttribPointer(r->vertex_tangent, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)offsetof(struct vertex, tangent)); glEnableVertexAttribArray(r->vertex_bone_index); glVertexAttribIPointer(r->vertex_bone_index, NR_WEIGHTS, GL_INT, sizeof(struct vertex), (const void *)offsetof(struct vertex, bone_id)); glEnableVertexAttribArray(r->vertex_bone_weight); diff --git a/src/3d/reign.c b/src/3d/reign.c index bf265c7..32b8000 100644 --- a/src/3d/reign.c +++ b/src/3d/reign.c @@ -40,6 +40,7 @@ static struct RE_instance *create_instance(struct RE_plugin *plugin) instance->scale[1] = 1.0; instance->scale[2] = 1.0; instance->alpha = 1.0; + instance->draw_bump = true; instance->fps = 30.0; glm_mat4_identity(instance->local_transform); glm_mat3_identity(instance->normal_transform); diff --git a/src/3d/renderer.c b/src/3d/renderer.c index 653a45b..eafd855 100644 --- a/src/3d/renderer.c +++ b/src/3d/renderer.c @@ -30,6 +30,7 @@ enum { COLOR_TEXTURE_UNIT, SPECULAR_TEXTURE_UNIT, + NORMAL_TEXTURE_UNIT, }; static void init_billboard_mesh(struct RE_renderer *r) @@ -52,6 +53,8 @@ static void init_billboard_mesh(struct RE_renderer *r) glVertexAttribPointer(r->vertex_uv, 2, GL_FLOAT, GL_FALSE, 20, (const void *)12); glDisableVertexAttribArray(r->vertex_normal); glVertexAttrib3f(r->vertex_normal, 0.0, 0.0, 1.0); + glDisableVertexAttribArray(r->vertex_tangent); + glVertexAttrib3f(r->vertex_tangent, 1.0, 0.0, 0.0); glDisableVertexAttribArray(r->vertex_bone_index); glVertexAttribI4i(r->vertex_bone_index, 0, 0, 0, 0); glDisableVertexAttribArray(r->vertex_bone_weight); @@ -92,8 +95,11 @@ struct RE_renderer *RE_renderer_new(struct texture *texture) r->rim_exponent = glGetUniformLocation(r->shader.program, "rim_exponent"); r->rim_color = glGetUniformLocation(r->shader.program, "rim_color"); r->view_pos = glGetUniformLocation(r->shader.program, "view_pos"); + r->use_normal_map = glGetUniformLocation(r->shader.program, "use_normal_map"); + r->normal_texture = glGetUniformLocation(r->shader.program, "normal_texture"); r->vertex_normal = glGetAttribLocation(r->shader.program, "vertex_normal"); r->vertex_uv = glGetAttribLocation(r->shader.program, "vertex_uv"); + r->vertex_tangent = glGetAttribLocation(r->shader.program, "vertex_tangent"); r->vertex_bone_index = glGetAttribLocation(r->shader.program, "vertex_bone_index"); r->vertex_bone_weight = glGetAttribLocation(r->shader.program, "vertex_bone_weight"); @@ -298,6 +304,15 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r) glBindTexture(GL_TEXTURE_2D, material->color_map); glUniform1i(r->shader.texture, COLOR_TEXTURE_UNIT); + if (material->normal_map && inst->draw_bump && inst->plugin->bump_mode) { + glUniform1i(r->use_normal_map, GL_TRUE); + glActiveTexture(GL_TEXTURE0 + NORMAL_TEXTURE_UNIT); + glBindTexture(GL_TEXTURE_2D, material->normal_map); + glUniform1i(r->normal_texture, NORMAL_TEXTURE_UNIT); + } else { + glUniform1i(r->use_normal_map, GL_FALSE); + } + glBindVertexArray(mesh->vao); glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices); glBindVertexArray(0); @@ -358,6 +373,7 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma glUniform1f(r->specular_shininess, 0.0); glUniform1i(r->use_specular_map, GL_FALSE); glUniform1f(r->rim_exponent, 0.0); + glUniform1i(r->use_normal_map, GL_FALSE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, bt->texture); diff --git a/src/hll/ReignEngine.c b/src/hll/ReignEngine.c index b98325f..41ae03d 100644 --- a/src/hll/ReignEngine.c +++ b/src/hll/ReignEngine.c @@ -285,7 +285,15 @@ static bool ReignEngine_SetInstanceDraw(int plugin, int instance, bool flag) HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceDrawShadow, int plugin, int instance, bool flag); HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceDrawBackShadow, int plugin, int instance, bool flag); HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceDrawMakeShadow, int plugin, int instance, bool flag); -HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceDrawBump, int plugin, int instance, bool flag); + +static bool ReignEngine_SetInstanceDrawBump(int plugin, int instance, bool flag) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + ri->draw_bump = flag; + return true; +} static bool ReignEngine_GetInstanceDraw(int plugin, int instance) { @@ -298,7 +306,15 @@ static bool ReignEngine_GetInstanceDraw(int plugin, int instance) //bool ReignEngine_GetInstanceDrawShadow(int plugin, int instance); //bool ReignEngine_GetInstanceDrawBackShadow(int plugin, int instance); //bool ReignEngine_GetInstanceDrawMakeShadow(int plugin, int instance); -//bool ReignEngine_GetInstanceDrawBump(int plugin, int instance); + +static bool ReignEngine_GetInstanceDrawBump(int plugin, int instance) +{ + struct RE_instance *ri = get_instance(plugin, instance); + if (!ri) + return false; + return ri->draw_bump; +} + //int ReignEngine_GetInstanceDrawType(int plugin, int instance); HLL_WARN_UNIMPLEMENTED(false, bool, ReignEngine, SetInstanceDrawType, int plugin, int instance, int draw_type); @@ -1164,7 +1180,7 @@ HLL_LIBRARY(ReignEngine, HLL_TODO_EXPORT(GetInstanceDrawShadow, ReignEngine_GetInstanceDrawShadow), HLL_TODO_EXPORT(GetInstanceDrawBackShadow, ReignEngine_GetInstanceDrawBackShadow), HLL_TODO_EXPORT(GetInstanceDrawMakeShadow, ReignEngine_GetInstanceDrawMakeShadow), - HLL_TODO_EXPORT(GetInstanceDrawBump, ReignEngine_GetInstanceDrawBump), + HLL_EXPORT(GetInstanceDrawBump, ReignEngine_GetInstanceDrawBump), HLL_TODO_EXPORT(GetInstanceDrawType, ReignEngine_GetInstanceDrawType), HLL_EXPORT(SetInstanceDrawType, ReignEngine_SetInstanceDrawType), HLL_EXPORT(LoadInstanceMotion, ReignEngine_LoadInstanceMotion),