Files
nunuhara_xsystem4/src/3d/renderer.c
T

888 lines
30 KiB
C

/* Copyright (C) 2022 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/>.
*/
#include <cglm/cglm.h>
#include "system4.h"
#include "system4/cg.h"
#include "system4/hashtable.h"
#include "3d_internal.h"
#include "asset_manager.h"
#include "reign.h"
#include "sact.h"
// TODO: Respect RE_plugin.shadow_map_resolution_level
#define SHADOW_WIDTH 1024
#define SHADOW_HEIGHT 1024
enum {
COLOR_TEXTURE_UNIT,
SPECULAR_TEXTURE_UNIT,
LIGHT_TEXTURE_UNIT,
NORMAL_TEXTURE_UNIT,
SHADOW_TEXTURE_UNIT,
};
enum {
DIFFUSE_EMISSIVE = 0,
DIFFUSE_NORMAL = 1,
DIFFUSE_LIGHT_MAP = 2,
};
static GLuint load_shader(const char *vertex_shader_path, const char *fragment_shader_path)
{
GLuint program = glCreateProgram();
GLuint vertex_shader = gfx_load_shader_file(vertex_shader_path, GL_VERTEX_SHADER);
GLuint fragment_shader = gfx_load_shader_file(fragment_shader_path, GL_FRAGMENT_SHADER);
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
// Bind vertex attributes to fixed locations, so that common VAO can be used
// by multiple programs.
glBindAttribLocation(program, VATTR_POS, "vertex_pos");
glBindAttribLocation(program, VATTR_NORMAL, "vertex_normal");
glBindAttribLocation(program, VATTR_UV, "vertex_uv");
glBindAttribLocation(program, VATTR_BONE_INDEX, "vertex_bone_index");
glBindAttribLocation(program, VATTR_BONE_WEIGHT, "vertex_bone_weight");
glBindAttribLocation(program, VATTR_LIGHT_UV, "vertex_light_uv");
glBindAttribLocation(program, VATTR_TANGENT, "vertex_tangent");
glLinkProgram(program);
GLint link_success;
glGetProgramiv(program, GL_LINK_STATUS, &link_success);
if (!link_success) {
GLint len;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
char *infolog = xmalloc(len + 1);
glGetProgramInfoLog(program, len, NULL, infolog);
ERROR("Failed to link shader %s, %s: %s", vertex_shader_path, fragment_shader_path, infolog);
}
return program;
}
static void init_shadow_renderer(struct shadow_renderer *sr)
{
GLint orig_fbo;
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->view_transform = glGetUniformLocation(sr->program, "view_transform");
sr->has_bones = glGetUniformLocation(sr->program, "has_bones");
sr->bone_matrices = glGetUniformLocation(sr->program, "bone_matrices");
glGenFramebuffers(1, &sr->fbo);
glGenTextures(1, &sr->texture);
glBindTexture(GL_TEXTURE_2D, sr->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindFramebuffer(GL_FRAMEBUFFER, sr->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, sr->texture, 0);
glDrawBuffers(0, NULL);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, orig_fbo);
}
static void destroy_shadow_renderer(struct shadow_renderer *sr)
{
glDeleteTextures(1, &sr->texture);
glDeleteFramebuffers(1, &sr->fbo);
glDeleteProgram(sr->program);
}
static void init_billboard_mesh(struct RE_renderer *r)
{
static const GLfloat vertices[] = {
// x, y, z, u, v
-1.0, 1.0, 0.0, 0.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0,
};
glGenVertexArrays(1, &r->billboard_vao);
glBindVertexArray(r->billboard_vao);
glGenBuffers(1, &r->billboard_attr_buffer);
glBindBuffer(GL_ARRAY_BUFFER, r->billboard_attr_buffer);
glEnableVertexAttribArray(VATTR_POS);
glVertexAttribPointer(VATTR_POS, 3, GL_FLOAT, GL_FALSE, 20, (const void *)0);
glEnableVertexAttribArray(VATTR_UV);
glVertexAttribPointer(VATTR_UV, 2, GL_FLOAT, GL_FALSE, 20, (const void *)12);
glDisableVertexAttribArray(VATTR_LIGHT_UV);
glVertexAttrib2f(VATTR_LIGHT_UV, 0.0, 0.0);
glDisableVertexAttribArray(VATTR_NORMAL);
glVertexAttrib3f(VATTR_NORMAL, 0.0, 0.0, 1.0);
glDisableVertexAttribArray(VATTR_TANGENT);
glVertexAttrib3f(VATTR_TANGENT, 1.0, 0.0, 0.0);
glDisableVertexAttribArray(VATTR_BONE_INDEX);
glVertexAttribI4i(VATTR_BONE_INDEX, 0, 0, 0, 0);
glDisableVertexAttribArray(VATTR_BONE_WEIGHT);
glVertexAttrib4f(VATTR_BONE_WEIGHT, 0.0, 0.0, 0.0, 0.0);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
static void destroy_billboard_mesh(struct RE_renderer *r)
{
glDeleteVertexArrays(1, &r->billboard_vao);
glDeleteBuffers(1, &r->billboard_attr_buffer);
}
struct RE_renderer *RE_renderer_new(struct texture *texture)
{
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");
r->proj_transform = glGetUniformLocation(r->program, "proj_transform");
r->normal_transform = glGetUniformLocation(r->program, "normal_transform");
r->alpha_mod = glGetUniformLocation(r->program, "alpha_mod");
r->has_bones = glGetUniformLocation(r->program, "has_bones");
r->bone_matrices = glGetUniformLocation(r->program, "bone_matrices");
r->ambient = glGetUniformLocation(r->program, "ambient");
for (int i = 0; i < NR_DIR_LIGHTS; i++) {
char buf[64];
sprintf(buf, "dir_lights[%d].dir", i);
r->dir_lights[i].dir = glGetUniformLocation(r->program, buf);
sprintf(buf, "dir_lights[%d].diffuse", i);
r->dir_lights[i].diffuse = glGetUniformLocation(r->program, buf);
sprintf(buf, "dir_lights[%d].globe_diffuse", i);
r->dir_lights[i].globe_diffuse = glGetUniformLocation(r->program, buf);
}
r->specular_light_dir = glGetUniformLocation(r->program, "specular_light_dir");
r->specular_strength = glGetUniformLocation(r->program, "specular_strength");
r->specular_shininess = glGetUniformLocation(r->program, "specular_shininess");
r->use_specular_map = glGetUniformLocation(r->program, "use_specular_map");
r->specular_texture = glGetUniformLocation(r->program, "specular_texture");
r->rim_exponent = glGetUniformLocation(r->program, "rim_exponent");
r->rim_color = glGetUniformLocation(r->program, "rim_color");
r->camera_pos = glGetUniformLocation(r->program, "camera_pos");
r->diffuse_type = glGetUniformLocation(r->program, "diffuse_type");
r->light_texture = glGetUniformLocation(r->program, "light_texture");
r->use_normal_map = glGetUniformLocation(r->program, "use_normal_map");
r->normal_texture = glGetUniformLocation(r->program, "normal_texture");
r->use_shadow_map = glGetUniformLocation(r->program, "use_shadow_map");
r->shadow_transform = glGetUniformLocation(r->program, "shadow_transform");
r->shadow_texture = glGetUniformLocation(r->program, "shadow_texture");
r->shadow_bias = glGetUniformLocation(r->program, "shadow_bias");
r->fog_type = glGetUniformLocation(r->program, "fog_type");
r->fog_near = glGetUniformLocation(r->program, "fog_near");
r->fog_far = glGetUniformLocation(r->program, "fog_far");
r->fog_color = glGetUniformLocation(r->program, "fog_color");
r->ls_params = glGetUniformLocation(r->program, "ls_params");
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");
glGenRenderbuffers(1, &r->depth_buffer);
glBindRenderbuffer(GL_RENDERBUFFER, r->depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, texture->w, texture->h);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
init_shadow_renderer(&r->shadow);
init_billboard_mesh(r);
r->billboard_textures = ht_create(256);
return r;
}
bool 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 cg *cg = asset_cg_load(cg_no);
if (!cg)
return false;
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
bt->has_alpha = cg->metrics.has_alpha;
cg_free(cg);
ht_put_int(r->billboard_textures, cg_no, bt);
return true;
}
static void free_billboard_texture(void *value)
{
struct billboard_texture *bt = value;
glDeleteTextures(1, &bt->texture);
free(bt);
}
void RE_renderer_free(struct RE_renderer *r)
{
glDeleteProgram(r->program);
glDeleteRenderbuffers(1, &r->depth_buffer);
ht_foreach_value(r->billboard_textures, free_billboard_texture);
ht_free_int(r->billboard_textures);
destroy_billboard_mesh(r);
destroy_shadow_renderer(&r->shadow);
free(r);
}
void RE_calc_view_matrix(struct RE_camera *camera, vec3 up, mat4 out)
{
vec3 front = { 0.0, 0.0, -1.0 };
vec3 euler = {
glm_rad(camera->pitch),
glm_rad(camera->yaw),
glm_rad(camera->roll)
};
mat4 rot;
glm_euler(euler, rot);
glm_mat4_mulv3(rot, front, 0.0, front);
glm_look(camera->pos, front, up, out);
}
static void render_model(struct RE_instance *inst, struct RE_renderer *r)
{
struct model *model = inst->model;
if (!inst->model)
return;
if (inst->local_transform_needs_update)
RE_instance_update_local_transform(inst);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, inst->local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, inst->normal_transform[0]);
glUniform1f(r->alpha_mod, inst->alpha);
vec3 ambient;
glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient);
glUniform3fv(r->ambient, 1, ambient);
if (inst->draw_shadow && inst->plugin->shadow_mode) {
glUniform1i(r->use_shadow_map, GL_TRUE);
glActiveTexture(GL_TEXTURE0 + SHADOW_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, r->shadow.texture);
glUniform1i(r->shadow_texture, SHADOW_TEXTURE_UNIT);
} else {
glUniform1i(r->use_shadow_map, GL_FALSE);
}
for (int i = 0; i < model->nr_meshes; i++) {
struct mesh *mesh = &model->meshes[i];
struct material *material = &model->materials[mesh->material];
GLboolean use_specular_map = GL_FALSE;
if (inst->plugin->specular_mode) {
glUniform1f(r->specular_strength, material->specular_strength);
glUniform1f(r->specular_shininess, material->specular_shininess);
if (material->specular_map) {
glActiveTexture(GL_TEXTURE0 + SPECULAR_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->specular_map);
glUniform1i(r->specular_texture, SPECULAR_TEXTURE_UNIT);
use_specular_map = GL_TRUE;
}
} else {
glUniform1f(r->specular_strength, 0.0);
glUniform1f(r->specular_shininess, 0.0);
}
glUniform1i(r->use_specular_map, use_specular_map);
glUniform1f(r->rim_exponent, material->rim_exponent);
glUniform3fv(r->rim_color, 1, material->rim_color);
glActiveTexture(GL_TEXTURE0 + COLOR_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->color_map);
glUniform1i(r->texture, COLOR_TEXTURE_UNIT);
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);
glUniform1i(r->light_texture, LIGHT_TEXTURE_UNIT);
} else {
glUniform1i(r->diffuse_type, DIFFUSE_NORMAL);
}
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);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
static void render_static_model(struct RE_instance *inst, struct RE_renderer *r)
{
if (!inst->draw)
return;
glUniform1i(r->has_bones, GL_FALSE);
render_model(inst, r);
}
static void render_skinned_model(struct RE_instance *inst, struct RE_renderer *r)
{
if (!inst->draw)
return;
struct model *model = inst->model;
if (!model)
return;
if (model->nr_bones > 0) {
glUniform1i(r->has_bones, GL_TRUE);
glUniformMatrix4fv(r->bone_matrices, model->nr_bones, GL_FALSE, inst->bone_transforms[0][0]);
} else {
glUniform1i(r->has_bones, GL_FALSE);
}
render_model(inst, r);
}
static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat)
{
if (!inst->draw)
return;
int cg_no = inst->motion->current_frame;
struct billboard_texture *bt = ht_get_int(r->billboard_textures, cg_no, NULL);
if (!bt)
return;
mat4 local_transform;
glm_translate_make(local_transform, inst->pos);
mat3 rot;
glm_mat4_pick3t(view_mat, rot);
glm_mat4_ins3(rot, local_transform);
glm_scale(local_transform, inst->scale);
// Billboard instances are bottomed at y=0.
glm_translate_y(local_transform, 1.0);
mat3 normal_transform;
// This should be safe because billboards do not have non-uniform scaling.
glm_mat4_pick3(local_transform, normal_transform);
vec3 ambient;
glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient);
glUniform3fv(r->ambient, 1, ambient);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, normal_transform[0]);
glUniform1i(r->has_bones, GL_FALSE);
glUniform1f(r->alpha_mod, inst->alpha);
glUniform1f(r->specular_strength, 0.0);
glUniform1f(r->specular_shininess, 0.0);
glUniform1i(r->use_specular_map, GL_FALSE);
glUniform1f(r->rim_exponent, 0.0);
glUniform1i(r->diffuse_type, DIFFUSE_NORMAL);
glUniform1i(r->use_normal_map, GL_FALSE);
glUniform1i(r->use_shadow_map, GL_FALSE);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, bt->texture);
glUniform1i(r->texture, 0);
glBindVertexArray(r->billboard_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void render_billboard_particles(struct RE_renderer *r, struct RE_instance *inst, struct particle_object *po, float frame)
{
if (po->nr_textures == 0)
return;
glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE);
glUniform1i(r->fog_type, 0);
glActiveTexture(GL_TEXTURE0);
glUniform1i(r->texture, 0);
glBindVertexArray(r->billboard_vao);
for (int index = 0; index < po->nr_particles; index++) {
struct particle_instance *pi = &po->instances[index];
if (frame < pi->begin_frame || pi->end_frame < frame)
continue;
int step = (int)((frame - pi->begin_frame) / po->texture_anime_frame);
struct billboard_texture *bt = ht_get(inst->effect->textures, po->textures[step % po->nr_textures], NULL);
if (!bt)
continue;
glBindTexture(GL_TEXTURE_2D, bt->texture);
float alpha = particle_object_calc_alpha(po, pi, frame);
if (po->blend_type == PARTICLE_BLEND_ADDITIVE && !bt->has_alpha)
alpha *= alpha; // why...
glUniform1f(r->alpha_mod, alpha);
mat4 local_transform;
particle_object_calc_local_transform(inst, po, pi, frame, local_transform);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, local_transform[0]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
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)
{
struct model *model = po->model;
if (!model)
return;
glUniform1i(r->diffuse_type, DIFFUSE_NORMAL);
for (int index = 0; index < po->nr_particles; index++) {
struct particle_instance *pi = &po->instances[index];
if (frame < pi->begin_frame || pi->end_frame < frame)
continue;
float alpha = particle_object_calc_alpha(po, pi, frame);
glUniform1f(r->alpha_mod, alpha);
mat4 local_transform;
particle_object_calc_local_transform(inst, po, pi, frame, local_transform);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, local_transform[0]);
for (int i = 0; i < model->nr_meshes; i++) {
struct mesh *mesh = &model->meshes[i];
struct material *material = &model->materials[mesh->material];
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, material->color_map);
glUniform1i(r->texture, 0);
glBindVertexArray(mesh->vao);
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
}
static void render_particle_effect(struct RE_instance *inst, struct RE_renderer *r)
{
// NOTE: inst->draw flag has no effect for particle effects.
if (!inst->effect)
return;
vec3 ambient;
glm_vec3_add(inst->plugin->global_ambient, inst->ambient, ambient);
glUniform3fv(r->ambient, 1, ambient);
glUniform1i(r->has_bones, GL_FALSE);
glUniform1f(r->specular_strength, 0.0);
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);
glUniform1i(r->use_shadow_map, GL_FALSE);
glDepthMask(GL_FALSE);
for (int i = 0; i < inst->effect->nr_objects; i++) {
struct particle_object *po = &inst->effect->objects[i];
switch (po->blend_type) {
case PARTICLE_BLEND_NORMAL:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
break;
case PARTICLE_BLEND_ADDITIVE:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
break;
}
switch (po->type) {
case PARTICLE_TYPE_BILLBOARD:
render_billboard_particles(r, inst, po, inst->motion->current_frame);
break;
case PARTICLE_TYPE_POLYGON_OBJECT:
render_polygon_particles(r, inst, po, inst->motion->current_frame);
break;
case PARTICLE_TYPE_SWORD_BLUR: // unused
break;
}
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_TRUE);
}
static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest)
{
// Compute AABB of shadow casters.
vec3 aabb[2];
glm_aabb_invalidate(aabb);
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (!inst || !inst->draw || !inst->make_shadow || !inst->model)
continue;
// Start with the model's AABB, inflated with shadow_volume_bone_radius.
vec3 inst_aabb[2];
glm_vec3_subs(inst->model->aabb[0], inst->shadow_volume_bone_radius, inst_aabb[0]);
glm_vec3_adds(inst->model->aabb[1], inst->shadow_volume_bone_radius, inst_aabb[1]);
// To save CPU cycles, consider only the translation components of bone transforms.
vec3 bone_translation_aabb[2];
glm_aabb_invalidate(bone_translation_aabb);
for (int j = 0; j < inst->model->nr_bones; j++) {
glm_vec3_minv(inst->bone_transforms[j][3], bone_translation_aabb[0], bone_translation_aabb[0]);
glm_vec3_maxv(inst->bone_transforms[j][3], bone_translation_aabb[1], bone_translation_aabb[1]);
}
if (glm_aabb_isvalid(bone_translation_aabb)) {
glm_vec3_add(inst_aabb[0], bone_translation_aabb[0], inst_aabb[0]);
glm_vec3_add(inst_aabb[1], bone_translation_aabb[1], inst_aabb[1]);
}
// Apply local transform.
if (inst->local_transform_needs_update)
RE_instance_update_local_transform(inst);
glm_aabb_transform(inst_aabb, inst->local_transform, inst_aabb);
glm_aabb_merge(inst_aabb, aabb, aabb);
}
if (!glm_aabb_isvalid(aabb))
return false;
// Create a orthographic frustum that contains the AABB.
vec3 center;
glm_aabb_center(aabb, center);
vec3 light_pos;
glm_vec3_scale(plugin->shadow_map_light_dir, -glm_aabb_radius(aabb), light_pos);
glm_vec3_add(light_pos, center, light_pos);
mat4 view_matrix;
vec3 up = {0.0, 1.0, 0.0};
glm_lookat(light_pos, center, up, view_matrix);
mat4 proj_matrix;
glm_aabb_transform(aabb, view_matrix, aabb);
glm_ortho_aabb(aabb, proj_matrix);
glm_mat4_mul(proj_matrix, view_matrix, dest);
return true;
}
static void render_shadow_map(struct RE_plugin *plugin, mat4 light_space_transform)
{
if (!calc_shadow_light_transform(plugin, light_space_transform)) {
// No shadow casters, but proceed anyway to clear the shadow texture.
glm_mat4_identity(light_space_transform);
}
struct RE_renderer *r = plugin->renderer;
GLint orig_fbo, orig_viewport[4];
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &orig_fbo);
glGetIntegerv(GL_VIEWPORT, orig_viewport);
glBindFramebuffer(GL_FRAMEBUFFER, r->shadow.fbo);
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glClear(GL_DEPTH_BUFFER_BIT);
glUseProgram(r->shadow.program);
glUniformMatrix4fv(r->shadow.view_transform, 1, GL_FALSE, light_space_transform[0]);
glEnable(GL_DEPTH_TEST);
// Render the shadow casters.
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (!inst || !inst->draw || !inst->make_shadow || !inst->model)
continue;
struct model *model = inst->model;
if (model->nr_bones > 0) {
glUniform1i(r->shadow.has_bones, GL_TRUE);
glUniformMatrix4fv(r->shadow.bone_matrices, model->nr_bones, GL_FALSE, inst->bone_transforms[0][0]);
} else {
glUniform1i(r->shadow.has_bones, GL_FALSE);
}
glUniformMatrix4fv(r->shadow.world_transform, 1, GL_FALSE, inst->local_transform[0]);
for (int j = 0; j < model->nr_meshes; j++) {
struct mesh *mesh = &model->meshes[j];
glBindVertexArray(mesh->vao);
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
}
glBindVertexArray(0);
}
glDisable(GL_DEPTH_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, orig_fbo);
glViewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]);
}
static void render_back_cg(struct texture *dst, struct RE_back_cg *bcg, struct RE_renderer *r)
{
if (!bcg->show || !bcg->texture.handle)
return;
int sw = bcg->texture.w;
int sh = bcg->texture.h;
gfx_copy_stretch_blend_amap_alpha(dst, bcg->x, bcg->y, sw * bcg->mag, sh * bcg->mag, &bcg->texture, 0, 0, sw, sh, bcg->blend_rate * 255);
}
static void setup_lights(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
glUniform3fv(r->camera_pos, 1, plugin->camera.pos);
int light_index = 0;
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (!inst)
continue;
switch (inst->type) {
case RE_ITYPE_DIRECTIONAL_LIGHT:
if (light_index >= NR_DIR_LIGHTS) {
WARNING("too many directional lights");
break;
}
glUniform3fv(r->dir_lights[light_index].dir, 1, inst->vec);
glUniform3fv(r->dir_lights[light_index].diffuse, 1, inst->diffuse);
glUniform3fv(r->dir_lights[light_index].globe_diffuse, 1, inst->globe_diffuse);
light_index++;
break;
case RE_ITYPE_SPECULAR_LIGHT:
glUniform3fv(r->specular_light_dir, 1, inst->vec);
break;
default:
break;
}
}
for (; light_index < NR_DIR_LIGHTS; light_index++) {
const vec3 zero = {0.0, 0.0, 0.0};
glUniform3fv(r->dir_lights[light_index].dir, 1, zero);
glUniform3fv(r->dir_lights[light_index].diffuse, 1, zero);
glUniform3fv(r->dir_lights[light_index].globe_diffuse, 1, zero);
}
}
void RE_render(struct sact_sprite *sp)
{
struct RE_plugin *plugin = (struct RE_plugin *)sp->plugin;
struct RE_renderer *r = plugin->renderer;
if (!r)
return;
sprite_dirty(sp);
struct texture *texture = sprite_get_texture(sp);
mat4 shadow_transform;
if (plugin->shadow_mode)
render_shadow_map(plugin, shadow_transform);
else
glm_mat4_identity(shadow_transform);
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, texture, 0, 0, texture->w, texture->h);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, r->depth_buffer);
if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
ERROR("Incomplete framebuffer");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw background CGs.
glDisable(GL_DEPTH_TEST);
for (int i = 0; i < RE_NR_BACK_CGS; i++)
render_back_cg(texture, &plugin->back_cg[i], r);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
mat4 view_transform;
RE_calc_view_matrix(&plugin->camera, GLM_YUP, view_transform);
// Tweak the projection transform so that the rendering result is vertically
// flipped. If we render the scene normally, the resulting image will be
// bottom-up (the first pixel is at the bottom-left), but we want a top-down
// image (the first pixel is at the top-left). This changes the coordinate
// system from right-handed to left-handed, we also need to reverse the
// winding order.
plugin->proj_transform[1][1] *= -1;
glFrontFace(GL_CW);
glUseProgram(r->program);
glUniformMatrix4fv(r->view_transform, 1, GL_FALSE, view_transform[0]);
glUniformMatrix4fv(r->proj_transform, 1, GL_FALSE, plugin->proj_transform[0]);
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_LINEAR:
glUniform1f(r->fog_near, plugin->fog_near);
glUniform1f(r->fog_far, plugin->fog_far);
glUniform3fv(r->fog_color, 1, plugin->fog_color);
break;
case RE_FOG_LIGHT_SCATTERING:
glUniform4f(r->ls_params, plugin->ls_beta_r, plugin->ls_beta_m, plugin->ls_g, plugin->ls_distance);
glUniform3fv(r->ls_light_dir, 1, plugin->ls_light_dir);
glUniform3fv(r->ls_light_color, 1, plugin->ls_light_color);
glUniform3fv(r->ls_sun_color, 1, plugin->ls_sun_color);
break;
}
} else {
glUniform1i(r->fog_type, 0);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
setup_lights(plugin);
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (!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;
}
}
plugin->proj_transform[1][1] *= -1;
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(0);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
}
struct height_detector {
GLushort *buf;
vec3 aabb[2];
};
struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r, struct model *model)
{
struct height_detector *hd = xcalloc(1, sizeof(struct height_detector));
vec3 aabb[2];
memcpy(aabb, model->aabb, sizeof(aabb));
// Add some Y padding.
aabb[0][1] -= 1.0;
aabb[1][1] += 1.0;
memcpy(hd->aabb, aabb, sizeof(aabb));
// Set up an orthographic projection matrix looking down the instance from
// right above.
mat4 view_matrix, proj_matrix;
glm_mat4_identity(view_matrix);
glm_rotate_x(view_matrix, glm_rad(90.0), view_matrix);
glm_aabb_transform(aabb, view_matrix, aabb);
glm_ortho_aabb(aabb, proj_matrix);
GLint orig_fbo, orig_viewport[4];
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &orig_fbo);
glGetIntegerv(GL_VIEWPORT, orig_viewport);
// Since OpenGL ES does not allow reading from depth textures, create a
// color texture and render depth values into it.
GLuint color_texture;
glGenTextures(1, &color_texture);
glBindTexture(GL_TEXTURE_2D, color_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16UI, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, NULL);
glBindFramebuffer(GL_FRAMEBUFFER, r->shadow.fbo);
GLenum draw_buffers[1] = { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, draw_buffers);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_texture, 0);
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.view_transform, 1, GL_FALSE, proj_matrix[0]);
glUniform1i(r->shadow.has_bones, GL_FALSE);
glEnable(GL_DEPTH_TEST);
// Render the model.
for (int i = 0; i < model->nr_meshes; i++) {
struct mesh *mesh = &model->meshes[i];
glBindVertexArray(mesh->vao);
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
}
glBindVertexArray(0);
glFinish();
hd->buf = xmalloc(SHADOW_WIDTH * SHADOW_HEIGHT * sizeof(GLushort));
glReadPixels(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT, GL_RED_INTEGER, GL_UNSIGNED_SHORT, hd->buf);
glDisable(GL_DEPTH_TEST);
glDrawBuffers(0, NULL);
glReadBuffer(GL_NONE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glBindFramebuffer(GL_FRAMEBUFFER, orig_fbo);
glViewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]);
glDeleteTextures(1, &color_texture);
return hd;
}
void RE_renderer_free_height_detector(struct height_detector *hd)
{
free(hd->buf);
free(hd);
}
float RE_renderer_detect_height(struct height_detector *hd, float x, float z)
{
float minx = hd->aabb[0][0];
float maxx = hd->aabb[1][0];
float miny = hd->aabb[0][1];
float maxy = hd->aabb[1][1];
float minz = hd->aabb[0][2];
float maxz = hd->aabb[1][2];
if (x < minx || x > maxx || z < minz || z > maxz)
return 0.0;
int px = glm_percent(minx, maxx, x) * SHADOW_WIDTH;
int pz = glm_percent(maxz, minz, z) * SHADOW_HEIGHT;
float py = hd->buf[pz * SHADOW_WIDTH + px] / 65535.0;
return glm_lerp(maxy, miny, py);
}