Files
nunuhara_xsystem4/src/3d/renderer.c
T
kichikuou 9eee82c13b 3d: Fix camera yaw direction and rotation order
The camera view direction was computed incorrectly in two ways:

1. glm_euler (XYZ order) applies pitch around world X. The original
   engine uses YXZ where pitch is around the camera's local X after yaw.

2. SetCameraAngle negated the input yaw.
2026-05-20 07:10:41 +09:00

1157 lines
40 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 <limits.h>
#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"
#define BONE_TRANSFORMS_BINDING 0
// TODO: Respect RE_plugin.shadow_map_resolution_level
#define SHADOW_WIDTH 512
#define SHADOW_HEIGHT 512
enum {
COLOR_TEXTURE_UNIT,
SPECULAR_TEXTURE_UNIT,
ALPHA_TEXTURE_UNIT,
LIGHT_TEXTURE_UNIT,
NORMAL_TEXTURE_UNIT,
SHADOW_TEXTURE_UNIT,
BLEND_TEXTURE_UNIT,
};
enum {
DIFFUSE_EMISSIVE = 0,
DIFFUSE_NORMAL = 1,
DIFFUSE_LIGHT_MAP = 2,
DIFFUSE_ENV_MAP = 3,
};
enum {
ALPHA_BLEND = 0,
ALPHA_TEST = 1,
ALPHA_MAP_BLEND = 2,
ALPHA_MAP_TEST = 3,
};
enum draw_phase {
DRAW_OPAQUE,
DRAW_TRANSPARENT,
};
static GLuint load_shader(const char *vertex_shader_path, const char *fragment_shader_path)
{
char defines[256];
snprintf(defines, sizeof(defines),
"#define REIGN_ENGINE %d\n"
"#define TAPIR_ENGINE %d\n"
"#define SEAL_ENGINE %d\n"
"#define ENGINE %d\n",
RE_REIGN_PLUGIN,
RE_TAPIR_PLUGIN,
RE_SEAL_PLUGIN,
re_plugin_version);
GLuint program = glCreateProgram();
GLuint vertex_shader = gfx_load_shader_file(vertex_shader_path, GL_VERTEX_SHADER, defines);
GLuint fragment_shader = gfx_load_shader_file(fragment_shader_path, GL_FRAGMENT_SHADER, defines);
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_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);
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->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");
glUniformBlockBinding(sr->program, bone_transforms, BONE_TRANSFORMS_BINDING);
glGenFramebuffers(1, &sr->fbo);
glGenTextures(1, &sr->texture);
glBindTexture(GL_TEXTURE_2D, sr->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 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_outline_renderer(struct outline_renderer *or)
{
if (re_plugin_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[] = {
// 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_COLOR);
glVertexAttrib4f(VATTR_COLOR, 1.0, 1.0, 1.0, 1.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(void)
{
struct RE_renderer *r = xcalloc(1, sizeof(struct RE_renderer));
r->program = load_shader("shaders/reign.v.glsl", "shaders/reign.f.glsl");
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");
GLuint bone_transforms = glGetUniformBlockIndex(r->program, "BoneTransforms");
glUniformBlockBinding(r->program, bone_transforms, BONE_TRANSFORMS_BINDING);
r->global_ambient = glGetUniformLocation(r->program, "global_ambient");
r->instance_ambient = glGetUniformLocation(r->program, "instance_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->shadow_darkness = glGetUniformLocation(r->program, "shadow_darkness");
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");
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);
init_shadow_renderer(&r->shadow);
init_outline_renderer(&r->outline);
init_billboard_mesh(r);
r->billboard_textures = ht_create(256);
r->last_frame_timestamp = SDL_GetTicks();
return r;
}
void RE_renderer_set_viewport_size(struct RE_renderer *r, int width, int height)
{
r->viewport_width = width;
r->viewport_height = height;
glBindRenderbuffer(GL_RENDERBUFFER, r->depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
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);
destroy_outline_renderer(&r->outline);
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 + camera->quake_pitch),
glm_rad(camera->yaw + camera->quake_yaw),
glm_rad(camera->roll)
};
mat4 rot;
glm_euler_yxz(euler, rot);
glm_mat4_mulv3(rot, front, 0.0, front);
glm_look(camera->pos, front, up, out);
}
static bool should_draw_shadow(struct mesh *mesh, struct material *material)
{
return !(mesh->flags & (MESH_ALPHA | MESH_BOTH | MESH_SPRITE))
&& !(material->flags & (MATERIAL_ALPHA | MATERIAL_SPRITE));
}
static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
struct model *model = inst->model;
if (!inst->model)
return;
bool all_transparent = inst->alpha < 1.0f;
bool all_opaque = !model->has_transparent_mesh && inst->alpha >= 1.0f;
if ((phase == DRAW_OPAQUE && all_transparent) || (phase == DRAW_TRANSPARENT && all_opaque))
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);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
bool draw_shadow = inst->draw_shadow && inst->plugin->shadow_mode;
if (draw_shadow) {
glActiveTexture(GL_TEXTURE0 + SHADOW_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, r->shadow.texture);
glUniform1i(r->shadow_texture, SHADOW_TEXTURE_UNIT);
}
for (int i = 0; i < model->nr_meshes; i++) {
struct mesh *mesh = &model->meshes[i];
if (mesh->hidden)
continue;
struct material *material = &model->materials[mesh->material];
bool is_transparent = mesh->is_transparent || inst->alpha < 1.0f;
if (phase != (is_transparent ? DRAW_TRANSPARENT : DRAW_OPAQUE))
continue;
if (mesh->flags & MESH_BLEND_ADDITIVE) {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
} else {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
}
glUniform1i(r->fog_type, (inst->plugin->fog_mode && !(mesh->flags & MESH_NOLIGHTING))
? inst->plugin->fog_type : 0);
GLboolean use_specular_map = GL_FALSE;
if (inst->plugin->specular_mode && !(mesh->flags & MESH_NOLIGHTING)) {
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);
int animation_index = inst->texture_animation_index < material->nr_color_maps
? inst->texture_animation_index : 0;
glBindTexture(GL_TEXTURE_2D, material->color_maps[animation_index]);
glUniform1i(r->texture, COLOR_TEXTURE_UNIT);
if (draw_shadow && should_draw_shadow(mesh, material))
glUniform1f(r->shadow_darkness, material->shadow_darkness);
else
glUniform1f(r->shadow_darkness, 0.0f);
if (mesh->flags & MESH_ENVMAP) {
glUniform1i(r->diffuse_type, DIFFUSE_ENV_MAP);
} else if (mesh->flags & MESH_NOLIGHTING) {
glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE);
} else if (material->light_map && mesh->flags & MESH_HAS_LIGHT_UV && 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);
}
if (material->alpha_map) {
glUniform1i(r->alpha_mode, mesh->is_transparent ? ALPHA_MAP_BLEND : ALPHA_MAP_TEST);
glActiveTexture(GL_TEXTURE0 + ALPHA_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->alpha_map);
glUniform1i(r->alpha_texture, ALPHA_TEXTURE_UNIT);
} else {
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);
glBindVertexArray(mesh->vao);
if (mesh->flags & MESH_BOTH)
glDisable(GL_CULL_FACE);
if (mesh->nr_indices)
glDrawElements(GL_TRIANGLES, mesh->nr_indices, GL_UNSIGNED_SHORT, NULL);
else
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
if (mesh->flags & MESH_BOTH)
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
static void render_static_model(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
if (!inst->draw)
return;
glUniform1i(r->has_bones, GL_FALSE);
render_model(inst, r, phase);
}
static void render_skinned_model(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
if (!inst->draw)
return;
struct model *model = inst->model;
if (!model)
return;
if (model->nr_bones > 0 && inst->motion) {
glUniform1i(r->has_bones, GL_TRUE);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, inst->bone_transforms_ubo);
} else {
glUniform1i(r->has_bones, GL_FALSE);
}
render_model(inst, r, phase);
if (inst->shadow_volume_instance && phase == DRAW_TRANSPARENT)
render_static_model(inst->shadow_volume_instance, r, phase);
}
static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat, enum draw_phase phase)
{
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;
bool is_transparent = bt->has_alpha || inst->alpha < 1.0f;
if (phase != (is_transparent ? DRAW_TRANSPARENT : DRAW_OPAQUE))
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);
glUniform3fv(r->instance_ambient, 1, inst->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);
glUniform1f(r->shadow_darkness, 0.0f);
glUniform1i(r->alpha_mode, ALPHA_BLEND);
glUniform1i(r->fog_type, inst->plugin->fog_mode ? inst->plugin->fog_type : 0);
switch (inst->draw_type) {
case RE_DRAW_TYPE_NORMAL:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
break;
case RE_DRAW_TYPE_ADDITIVE:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
break;
}
glDepthFunc(GL_LEQUAL);
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);
glDepthFunc(GL_LESS);
}
static void render_billboard_particles(struct RE_renderer *r, struct RE_instance *inst, struct particle_object *po, float frame)
{
struct pae_object *pae_obj = po->pae_obj;
if (pae_obj->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 < pae_obj->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) / pae_obj->texture_anime_frame);
struct billboard_texture *bt = ht_get(inst->effect->pae->textures, pae_obj->textures[step % pae_obj->nr_textures], NULL);
if (!bt)
continue;
glBindTexture(GL_TEXTURE_2D, bt->texture);
float alpha = pae_object_calc_alpha(pae_obj, pi, frame);
if (pae_obj->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);
}
static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *inst, struct particle_object *po, float frame)
{
struct pae_object *pae_obj = po->pae_obj;
struct model *model = pae_obj->model;
if (!model)
return;
glUniform1i(r->diffuse_type, DIFFUSE_NORMAL);
glUniform1i(r->fog_type, inst->plugin->fog_mode ? inst->plugin->fog_type : 0);
for (int index = 0; index < pae_obj->nr_particles; index++) {
struct particle_instance *pi = &po->instances[index];
if (frame < pi->begin_frame || pi->end_frame <= frame)
continue;
float alpha = pae_object_calc_alpha(pae_obj, 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_maps[0]);
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, enum draw_phase phase)
{
// NOTE: inst->draw flag has no effect for particle effects.
if (!inst->effect || phase == DRAW_OPAQUE)
return;
glUniform3fv(r->instance_ambient, 1, inst->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);
glUniform1f(r->shadow_darkness, 0.0f);
glUniform1i(r->alpha_mode, ALPHA_BLEND);
glDepthMask(GL_FALSE);
for (int i = 0; i < inst->effect->pae->nr_objects; i++) {
struct particle_object *po = &inst->effect->objects[i];
switch (po->pae_obj->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->pae_obj->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
case PARTICLE_TYPE_CAMERA_QUAKE: // handled in particle_effect_update()
break;
}
}
glDepthMask(GL_TRUE);
}
static void render_instance(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat, enum draw_phase phase)
{
switch (inst->type) {
case RE_ITYPE_STATIC:
render_static_model(inst, r, phase);
break;
case RE_ITYPE_SKINNED:
render_skinned_model(inst, r, phase);
break;
case RE_ITYPE_BILLBOARD:
render_billboard(inst, r, view_mat, phase);
break;
case RE_ITYPE_PARTICLE_EFFECT:
render_particle_effect(inst, r, phase);
break;
default:
break;
}
}
static void merge_spheres(vec4 s1, vec4 s2, vec4 dest)
{
// glm_sphere_merge() doesn't make the smallest possible sphere, so do it manually.
float distance = glm_vec3_distance(s1, s2);
float r1 = s1[3];
float r2 = s2[3];
if (distance + r2 <= r1) {
glm_vec4_copy(s1, dest);
} else if (distance + r1 <= r2) {
glm_vec4_copy(s2, dest);
} else {
float radius = (distance + r1 + r2) / 2.f;
vec3 v12;
glm_vec3_sub(s2, s1, v12);
glm_vec3_copy(s1, dest);
glm_vec3_muladds(v12, (radius - r1) / distance, dest);
dest[3] = radius;
}
}
static bool calc_shadow_light_transform(struct RE_plugin *plugin, mat4 dest)
{
// Compute a bounding sphere of shadow casters.
vec4 bounding_sphere = {};
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;
if (bounding_sphere[3] > 0.0f)
merge_spheres(inst->bounding_sphere, bounding_sphere, bounding_sphere);
else
glm_vec4_copy(inst->bounding_sphere, bounding_sphere);
}
float radius = bounding_sphere[3] * 1.2f; // Add some padding.
if (radius <= 0.0f)
return false;
// Create a orthographic frustum that contains the bounding sphere.
mat4 view_matrix;
glm_look_anyup(bounding_sphere, plugin->shadow_map_light_dir, view_matrix);
mat4 proj_matrix;
glm_ortho(-radius, radius, -radius, radius, -radius, radius, 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);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, inst->bone_transforms_ubo);
} else {
glUniform1i(r->shadow.has_bones, GL_FALSE);
}
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)
continue;
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_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)
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);
glUniform3fv(r->global_ambient, 1, plugin->global_ambient);
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);
}
}
static int cmp_instances_by_z(const void *lhs, const void *rhs)
{
struct RE_instance *l = *(struct RE_instance **)lhs;
struct RE_instance *r = *(struct RE_instance **)rhs;
float lz = l ? l->z_from_camera : FLT_MAX;
float rz = r ? r->z_from_camera : FLT_MAX;
return (lz > rz) - (lz < rz); // ascending order.
}
static struct RE_instance **sort_instances(struct RE_plugin *plugin, mat4 view_transform)
{
struct RE_instance **instances = xmalloc(plugin->nr_instances * sizeof(struct RE_instance *));
memcpy(instances, plugin->instances, plugin->nr_instances * sizeof(struct RE_instance *));
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = instances[i];
if (!inst)
continue;
vec3 center;
if (inst->model) {
glm_aabb_center(inst->model->aabb, center);
if (inst->local_transform_needs_update)
RE_instance_update_local_transform(inst);
glm_mat4_mulv3(inst->local_transform, center, 1.0, center);
} else {
glm_vec3_copy(inst->pos, center);
}
glm_mat4_mulv3(view_transform, center, 1.0f, center);
inst->z_from_camera = center[2];
}
qsort(instances, plugin->nr_instances, sizeof(struct RE_instance *), cmp_instances_by_z);
return instances;
}
void RE_render(struct sact_sprite *sp)
{
struct RE_plugin *plugin = (struct RE_plugin *)sp->plugin;
struct RE_renderer *r = plugin->renderer;
if (!r || plugin->suspended)
return;
if (re_plugin_version >= RE_TAPIR_PLUGIN) {
uint32_t timestamp = SDL_GetTicks();
RE_build_model(plugin, timestamp - r->last_frame_timestamp);
r->last_frame_timestamp = timestamp;
}
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");
glClearColor(0.f, 0.f, 0.f, 1.f);
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) {
switch (plugin->fog_type) {
case RE_FOG_NONE:
break;
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;
}
}
glEnable(GL_BLEND);
setup_lights(plugin);
// Sort instances by z-order.
struct RE_instance **sorted_instances = sort_instances(plugin, view_transform);
// Render opaque instances, from nearest to farthest.
for (int i = plugin->nr_instances - 1; i >= 0; i--) {
struct RE_instance *inst = sorted_instances[i];
if (!inst)
continue;
render_instance(inst, r, view_transform, DRAW_OPAQUE);
}
// Render transparent instances, from farthest to nearest.
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = sorted_instances[i];
if (!inst)
continue;
render_instance(inst, r, view_transform, DRAW_TRANSPARENT);
}
// Render outlines.
render_outlines(plugin, view_transform);
free(sorted_instances);
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);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
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.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);
// Bone transforms are not used, but we still need to bind a UBO.
GLuint bone_transforms_ubo;
glGenBuffers(1, &bone_transforms_ubo);
glBindBuffer(GL_UNIFORM_BUFFER, bone_transforms_ubo);
glBufferData(GL_UNIFORM_BUFFER, MAX_BONES * sizeof(mat3x4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, BONE_TRANSFORMS_BINDING, bone_transforms_ubo);
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]);
glDeleteBuffers(1, &bone_transforms_ubo);
glDeleteTextures(1, &color_texture);
return hd;
}
void RE_renderer_free_height_detector(struct height_detector *hd)
{
free(hd->buf);
free(hd);
}
bool RE_renderer_detect_height(struct height_detector *hd, float x, float z, float *y_out)
{
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 false;
int px = glm_percent(minx, maxx, x) * SHADOW_WIDTH;
int pz = glm_percent(maxz, minz, z) * SHADOW_HEIGHT;
GLushort val = hd->buf[pz * SHADOW_WIDTH + px];
if (!val)
return false;
*y_out = glm_lerp(maxy, miny, val / 65535.0f);
return true;
}