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