mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-30 10:58:15 +03:00
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.
1157 lines
40 KiB
C
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;
|
|
}
|