Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/render/renderer.c raw
#include "render/renderer.h"
#include "render/shader.h"
#include <string.h>
#include <SDL3/SDL.h>
#define BOIL_FPS 12.0f /* "animated on twos" */
#define SHADOW_MAP_SIZE 4096
/* Texture units: 0 is left free for uploads (binding a texture there to
update it must never unbind what a shader samples). */
#define UNIT_WEAR 1
#define UNIT_SHADOW 2
#define UNIT_WATER 3
#define UNIT_SURFACE 4
#define SHADOW_HALF_EXTENT 40.0f /* shadowed area around the focus point */
#define SHADOW_DEPTH_RANGE 80.0f /* depth range in front of/behind the focus */
#define SHADOW_LIGHT_DIST 100.0f
/* ---------- Setup ---------- */
static bool loadShaders(Renderer *r) {
SceneShader *s = &r->scene;
s->program = Shader_load("shaders/scene.vert", "shaders/scene.frag");
if (!s->program) return false;
s->model = glGetUniformLocation(s->program, "model");
s->view = glGetUniformLocation(s->program, "view");
s->projection = glGetUniformLocation(s->program, "projection");
s->light_space = glGetUniformLocation(s->program, "lightSpaceMatrix");
s->boil_time = glGetUniformLocation(s->program, "boilTime");
s->view_pos = glGetUniformLocation(s->program, "viewPos");
s->light_dir = glGetUniformLocation(s->program, "lightDirection");
s->light_color = glGetUniformLocation(s->program, "lightColor");
s->shadow_color = glGetUniformLocation(s->program, "shadowColor");
s->sky_top = glGetUniformLocation(s->program, "skyTop");
s->sky_horizon = glGetUniformLocation(s->program, "skyHorizon");
s->shadow_strength = glGetUniformLocation(s->program, "shadowStrength");
s->fire_light = glGetUniformLocation(s->program, "fireLight");
s->tint = glGetUniformLocation(s->program, "tint");
s->puff = glGetUniformLocation(s->program, "puff");
s->grass = glGetUniformLocation(s->program, "grass");
s->rain_wet = glGetUniformLocation(s->program, "rainWet");
s->shadow_map = glGetUniformLocation(s->program, "shadowMap");
s->terrain_size = glGetUniformLocation(s->program, "terrainSize");
s->grass_depth = glGetUniformLocation(s->program, "grassDepth");
s->decal_size = glGetUniformLocation(s->program, "decalSize");
s->wear_map = glGetUniformLocation(s->program, "wearMap");
s->water_map = glGetUniformLocation(s->program, "waterMap");
s->surface_map = glGetUniformLocation(s->program, "surfaceMap");
s->holes = glGetUniformLocation(s->program, "holes");
s->hole_size = glGetUniformLocation(s->program, "holeSize");
s->hole_count = glGetUniformLocation(s->program, "holeCount");
ShadowShader *d = &r->shadow;
d->program = Shader_load("shaders/shadow.vert", "shaders/shadow.frag");
if (!d->program) return false;
d->model = glGetUniformLocation(d->program, "model");
d->light_space = glGetUniformLocation(d->program, "lightSpaceMatrix");
d->boil_time = glGetUniformLocation(d->program, "boilTime");
InkShader *k = &r->ink;
k->program = Shader_load("shaders/ink.vert", "shaders/ink.frag");
if (!k->program) return false;
k->color_tex = glGetUniformLocation(k->program, "colorTex");
k->normal_depth_tex = glGetUniformLocation(k->program, "normalDepthTex");
k->texel = glGetUniformLocation(k->program, "texel");
k->boil_time = glGetUniformLocation(k->program, "boilTime");
k->time = glGetUniformLocation(k->program, "time");
k->drunk = glGetUniformLocation(k->program, "drunk");
k->dark = glGetUniformLocation(k->program, "dark");
FigureShader *f = &r->figure;
f->program = Shader_load("shaders/figure.vert", "shaders/figure.frag");
if (!f->program) return false;
f->view_proj = glGetUniformLocation(f->program, "viewProj");
f->view = glGetUniformLocation(f->program, "view");
f->light_space = glGetUniformLocation(f->program, "lightSpaceMatrix");
f->shadow_pass = glGetUniformLocation(f->program, "shadowPass");
f->boil_time = glGetUniformLocation(f->program, "boilTime");
f->figure_pos = glGetUniformLocation(f->program, "figurePos");
f->card_center = glGetUniformLocation(f->program, "cardCenter");
f->card_u = glGetUniformLocation(f->program, "cardU");
f->card_v = glGetUniformLocation(f->program, "cardV");
f->facing = glGetUniformLocation(f->program, "facing");
f->card_min = glGetUniformLocation(f->program, "cardMin");
f->card_max = glGetUniformLocation(f->program, "cardMax");
f->joints = glGetUniformLocation(f->program, "joints");
f->skin = glGetUniformLocation(f->program, "skinColor");
f->hair = glGetUniformLocation(f->program, "hairColor");
f->shirt = glGetUniformLocation(f->program, "shirtColor");
f->jacket = glGetUniformLocation(f->program, "jacketColor");
f->pants = glGetUniformLocation(f->program, "pantsColor");
f->shoes = glGetUniformLocation(f->program, "shoeColor");
f->hair_style = glGetUniformLocation(f->program, "hairStyle");
f->long_sleeves = glGetUniformLocation(f->program, "longSleeves");
f->cast = glGetUniformLocation(f->program, "castLimb");
f->head_forward = glGetUniformLocation(f->program, "headForward");
f->head_up = glGetUniformLocation(f->program, "headUp");
f->chest_forward = glGetUniformLocation(f->program, "chestForward");
f->waist_bend = glGetUniformLocation(f->program, "waistBend");
f->face = glGetUniformLocation(f->program, "face");
f->expression = glGetUniformLocation(f->program, "expression");
f->stream = glGetUniformLocation(f->program, "stream");
f->stream_kind = glGetUniformLocation(f->program, "streamKind");
f->stream_width = glGetUniformLocation(f->program, "streamWidth");
f->garment = glGetUniformLocation(f->program, "garment");
f->undress = glGetUniformLocation(f->program, "undress");
f->top = glGetUniformLocation(f->program, "top");
f->jacket_tied = glGetUniformLocation(f->program, "jacketTied");
f->belly = glGetUniformLocation(f->program, "belly");
f->wet = glGetUniformLocation(f->program, "soaked");
f->hood = glGetUniformLocation(f->program, "hood");
f->hat = glGetUniformLocation(f->program, "hat");
f->hat_color = glGetUniformLocation(f->program, "hatColor");
f->cig_a = glGetUniformLocation(f->program, "cigA");
f->uri_a = glGetUniformLocation(f->program, "uriA");
f->uri_b = glGetUniformLocation(f->program, "uriB");
f->uri = glGetUniformLocation(f->program, "uri");
f->cig_tip = glGetUniformLocation(f->program, "cigTip");
f->cig_glow = glGetUniformLocation(f->program, "cigGlow");
f->species = glGetUniformLocation(f->program, "species");
f->tail_a = glGetUniformLocation(f->program, "tailA");
f->tail_b = glGetUniformLocation(f->program, "tailB");
f->view_pos = glGetUniformLocation(f->program, "viewPos");
f->light_dir = glGetUniformLocation(f->program, "lightDirection");
f->light_color = glGetUniformLocation(f->program, "lightColor");
f->shadow_color = glGetUniformLocation(f->program, "shadowColor");
f->sky_top = glGetUniformLocation(f->program, "skyTop");
f->sky_horizon = glGetUniformLocation(f->program, "skyHorizon");
f->shadow_strength = glGetUniformLocation(f->program, "shadowStrength");
f->fire_light = glGetUniformLocation(f->program, "fireLight");
f->shadow_map = glGetUniformLocation(f->program, "shadowMap");
return true;
}
static GLuint createTexture(GLint internal, int w, int h, GLenum format, GLenum type, GLint filter) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, format, type, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
return tex;
}
static bool createShadowMap(Renderer *r) {
r->shadow_size = SHADOW_MAP_SIZE;
r->shadow_map = createTexture(GL_DEPTH_COMPONENT24, r->shadow_size, r->shadow_size,
GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, GL_NEAREST);
glGenFramebuffers(1, &r->shadow_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, r->shadow_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, r->shadow_map, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
bool complete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (!complete) SDL_Log("Shadow framebuffer incomplete");
return complete;
}
static void destroyTarget(SceneTarget *t) {
if (t->fbo) glDeleteFramebuffers(1, &t->fbo);
if (t->color) glDeleteTextures(1, &t->color);
if (t->normal_depth) glDeleteTextures(1, &t->normal_depth);
if (t->depth) glDeleteTextures(1, &t->depth);
memset(t, 0, sizeof(*t));
}
static bool createTarget(SceneTarget *t, int w, int h) {
destroyTarget(t);
t->width = w;
t->height = h;
t->color = createTexture(GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR);
t->normal_depth = createTexture(GL_RGBA16F, w, h, GL_RGBA, GL_FLOAT, GL_NEAREST);
t->depth = createTexture(GL_DEPTH_COMPONENT24, w, h, GL_DEPTH_COMPONENT,
GL_UNSIGNED_INT, GL_NEAREST);
glGenFramebuffers(1, &t->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, t->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, t->color, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, t->normal_depth, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, t->depth, 0);
const GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, buffers);
bool complete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (!complete) SDL_Log("Scene framebuffer incomplete (%dx%d)", w, h);
return complete;
}
bool Renderer_init(Renderer *r) {
memset(r, 0, sizeof(*r));
if (!loadShaders(r) || !createShadowMap(r)) {
Renderer_shutdown(r);
return false;
}
glGenVertexArrays(1, &r->fullscreen_vao); /* core profile needs a VAO */
glEnable(GL_DEPTH_TEST);
return true;
}
void Renderer_shutdown(Renderer *r) {
Shader_destroy(&r->scene.program);
Shader_destroy(&r->shadow.program);
Shader_destroy(&r->ink.program);
Shader_destroy(&r->figure.program);
destroyTarget(&r->target);
if (r->fullscreen_vao) glDeleteVertexArrays(1, &r->fullscreen_vao);
if (r->shadow_fbo) glDeleteFramebuffers(1, &r->shadow_fbo);
if (r->shadow_map) glDeleteTextures(1, &r->shadow_map);
memset(r, 0, sizeof(*r));
}
/* ---------- Frame ---------- */
void Renderer_beginFrame(Renderer *r, int width, int height, float time) {
r->width = width;
r->height = height;
r->time = time;
r->boil_time = floorf(time * BOIL_FPS) / BOIL_FPS;
r->stats.drawn = r->stats.culled = 0;
if (r->target.width != width || r->target.height != height)
createTarget(&r->target, width, height);
/* Nothing that we render into may stay bound for sampling. */
for (int unit = UNIT_SHADOW; unit >= 0; unit--) {
glActiveTexture(GL_TEXTURE0 + (GLenum)unit);
glBindTexture(GL_TEXTURE_2D, 0);
}
glBindFramebuffer(GL_FRAMEBUFFER, r->target.fbo);
glViewport(0, 0, width, height);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
/* ---------- Shadow pass ---------- */
void Renderer_beginShadowPass(Renderer *r, const Lighting *light, Vec3 focus) {
r->shadow_focus = focus;
Vec3 forward = vec3_normalize(light->light_direction);
Vec3 helper = fabsf(forward.y) > 0.99f ? vec3(1, 0, 0) : vec3(0, 1, 0);
Vec3 up = vec3_cross(forward, vec3_normalize(vec3_cross(helper, forward)));
Mat4 view = mat4_look_at(vec3_scale(forward, SHADOW_LIGHT_DIST), vec3_zero(), up);
/* Center the shadow area on the focus, snapped to whole shadow-map texels
so shadow edges don't crawl while the camera moves. */
const float texel = 2.0f * SHADOW_HALF_EXTENT / (float)SHADOW_MAP_SIZE;
Vec3 f = mat4_mul_point(view, focus);
float cx = floorf(f.x / texel) * texel;
float cy = floorf(f.y / texel) * texel;
Mat4 proj = mat4_orthographic(cx - SHADOW_HALF_EXTENT, cx + SHADOW_HALF_EXTENT,
cy - SHADOW_HALF_EXTENT, cy + SHADOW_HALF_EXTENT,
-f.z - SHADOW_DEPTH_RANGE, -f.z + SHADOW_DEPTH_RANGE);
r->light_space = mat4_mul(proj, view);
r->frustum = frustum_from_matrix(r->light_space); /* (only what falls into the shadow map) */
glBindFramebuffer(GL_FRAMEBUFFER, r->shadow_fbo);
glViewport(0, 0, r->shadow_size, r->shadow_size);
glClear(GL_DEPTH_BUFFER_BIT);
/* Faces towards the light cast the shadow, so it starts right at the
base of whatever stands on the ground (back faces left a lit rim
there). Receivers avoid acne with their normal offset instead. */
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0f, 1.0f);
/* Figures cut their shadow silhouette from a card facing the light. */
const FigureShader *fs = &r->figure;
const Mat4 identity = mat4_identity();
glUseProgram(fs->program);
glUniformMatrix4fv(fs->view_proj, 1, GL_FALSE, r->light_space.m);
glUniformMatrix4fv(fs->view, 1, GL_FALSE, identity.m);
glUniformMatrix4fv(fs->light_space, 1, GL_FALSE, identity.m);
glUniform1i(fs->shadow_pass, 1);
glUniform1f(fs->boil_time, r->boil_time);
glUniform3f(fs->light_dir, forward.x, forward.y, forward.z);
r->light_dir = forward;
r->shadow_pass = true;
glUseProgram(r->shadow.program);
glUniformMatrix4fv(r->shadow.light_space, 1, GL_FALSE, r->light_space.m);
glUniform1f(r->shadow.boil_time, r->boil_time);
r->pass_program = r->shadow.program;
}
/* ---------- Culling: only what's in view is drawn ---------- */
#define CULL_MARGIN 0.5f /* wobble, wind in the grass, the ground sinking under trampled things */
bool Renderer_inView(Renderer *r, Vec3 center, float radius) {
bool in = frustum_sphere(&r->frustum, center, radius + CULL_MARGIN);
if (in) r->stats.drawn++;
else r->stats.culled++;
return in;
}
static bool meshInView(Renderer *r, const Mesh *mesh, Mat4 model) {
if (!mesh->vao) return false;
float scale = 0.0f;
for (int c = 0; c < 3; c++)
scale = fmaxf(scale, sqrtf(model.m[c * 4] * model.m[c * 4] + model.m[c * 4 + 1] * model.m[c * 4 + 1] +
model.m[c * 4 + 2] * model.m[c * 4 + 2]));
return Renderer_inView(r, mat4_mul_point(model, mesh->center), mesh->radius * scale);
}
void Renderer_drawShadowCaster(Renderer *r, const Mesh *mesh, Mat4 model) {
if (!meshInView(r, mesh, model)) return;
glUniformMatrix4fv(r->shadow.model, 1, GL_FALSE, model.m);
Mesh_draw(mesh);
}
void Renderer_endShadowPass(Renderer *r) {
glDisable(GL_POLYGON_OFFSET_FILL);
glCullFace(GL_BACK);
glBindFramebuffer(GL_FRAMEBUFFER, r->target.fbo);
glViewport(0, 0, r->width, r->height);
}
/* ---------- Scene pass ---------- */
static void setVec3(GLint loc, Vec3 v) { glUniform3f(loc, v.x, v.y, v.z); }
void Renderer_beginScenePass(Renderer *r, const Camera *camera, const Lighting *light) {
const SceneShader *s = &r->scene;
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glUseProgram(s->program);
glUniformMatrix4fv(s->view, 1, GL_FALSE, camera->view.m);
glUniformMatrix4fv(s->projection, 1, GL_FALSE, camera->projection.m);
glUniformMatrix4fv(s->light_space, 1, GL_FALSE, r->light_space.m);
glUniform1f(s->boil_time, r->boil_time);
setVec3(s->view_pos, camera->position);
setVec3(s->light_dir, light->light_direction);
setVec3(s->light_color, light->light_color);
setVec3(s->shadow_color, light->shadow_color);
setVec3(s->sky_top, light->sky_top);
setVec3(s->sky_horizon, light->sky_horizon);
glUniform1f(s->shadow_strength, light->shadow_strength);
glUniform1f(s->rain_wet, light->wet);
glUniform4f(s->fire_light, light->fire_position.x, light->fire_position.y, light->fire_position.z, light->fire_strength);
glUniform1f(s->terrain_size, 0.0f); /* 0 = regular object */
glUniform1f(s->decal_size, 0.0f);
glUniform4f(s->tint, 1.0f, 1.0f, 1.0f, 0.0f);
glUniform1f(s->grass, 0.0f);
glUniform1f(s->puff, 0.0f);
glUniform1i(s->wear_map, UNIT_WEAR);
glUniform1i(s->water_map, UNIT_WATER);
glUniform1i(s->surface_map, UNIT_SURFACE);
glActiveTexture(GL_TEXTURE0 + UNIT_SHADOW);
glBindTexture(GL_TEXTURE_2D, r->shadow_map);
glActiveTexture(GL_TEXTURE0);
glUniform1i(s->shadow_map, UNIT_SHADOW);
/* Figures share camera and lighting */
const FigureShader *f = &r->figure;
const Mat4 view_proj = mat4_mul(camera->projection, camera->view);
r->frustum = frustum_from_matrix(view_proj);
glUseProgram(f->program);
glUniformMatrix4fv(f->view_proj, 1, GL_FALSE, view_proj.m);
glUniformMatrix4fv(f->view, 1, GL_FALSE, camera->view.m);
glUniformMatrix4fv(f->light_space, 1, GL_FALSE, r->light_space.m);
glUniform1i(f->shadow_pass, 0);
glUniform1f(f->boil_time, r->boil_time);
setVec3(f->view_pos, camera->position);
setVec3(f->light_dir, light->light_direction);
setVec3(f->light_color, light->light_color);
setVec3(f->shadow_color, light->shadow_color);
setVec3(f->sky_top, light->sky_top);
setVec3(f->sky_horizon, light->sky_horizon);
glUniform1f(f->shadow_strength, light->shadow_strength);
glUniform4f(f->fire_light, light->fire_position.x, light->fire_position.y, light->fire_position.z, light->fire_strength);
glUniform1i(f->shadow_map, UNIT_SHADOW);
r->viewer_pos = camera->position;
r->shadow_pass = false;
glUseProgram(s->program);
r->pass_program = s->program;
}
void Renderer_beginAirPass(Renderer *r) {
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glDepthFunc(GL_LESS);
glUseProgram(r->scene.program);
}
void Renderer_drawMeshTinted(Renderer *r, const Mesh *mesh, Mat4 model, Vec3 rgb, float glow) {
if (!meshInView(r, mesh, model)) return;
glUniform4f(r->scene.tint, rgb.x, rgb.y, rgb.z, glow);
glUniformMatrix4fv(r->scene.model, 1, GL_FALSE, model.m);
Mesh_draw(mesh);
glUniform4f(r->scene.tint, 1.0f, 1.0f, 1.0f, 0.0f);
}
void Renderer_drawSmoke(Renderer *r, const Mesh *mesh, Mat4 model, Vec3 rgb) {
if (!meshInView(r, mesh, model)) return;
glColorMaski(1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); /* (no normal / depth for the ink: no outline) */
glDepthMask(GL_FALSE);
Renderer_drawMeshTinted(r, mesh, model, rgb, 0.0f);
glDepthMask(GL_TRUE);
glColorMaski(1, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
void Renderer_drawPuff(Renderer *r, const Mesh *mesh, Mat4 model, Vec3 rgb, float alpha) {
if (!meshInView(r, mesh, model) || alpha <= 0.01f) return;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColorMaski(1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); /* (no outline) */
glDepthMask(GL_FALSE);
glUniform1f(r->scene.puff, alpha);
Renderer_drawMeshTinted(r, mesh, model, rgb, 0.0f);
glUniform1f(r->scene.puff, 0.0f);
glDepthMask(GL_TRUE);
glColorMaski(1, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_BLEND);
}
void Renderer_drawGrass(Renderer *r, const Mesh *mesh, float terrain_size, float grass_depth) {
if (!meshInView(r, mesh, mat4_identity())) return;
glUniform1f(r->scene.grass, terrain_size);
glUniform1f(r->scene.grass_depth, grass_depth);
glUniformMatrix4fv(r->scene.model, 1, GL_FALSE, mat4_identity().m);
glDisable(GL_CULL_FACE); /* blades are seen from both sides */
Mesh_draw(mesh);
glEnable(GL_CULL_FACE);
glUniform1f(r->scene.grass, 0.0f);
}
void Renderer_drawMesh(Renderer *r, const Mesh *mesh, Mat4 model) {
if (!meshInView(r, mesh, model)) return;
glUniformMatrix4fv(r->scene.model, 1, GL_FALSE, model.m);
Mesh_draw(mesh);
}
void Renderer_drawDecal(Renderer *r, const Mesh *mesh, Mat4 model, float terrain_size) {
if (!meshInView(r, mesh, model)) return;
glUniform1f(r->scene.decal_size, terrain_size);
glUniformMatrix4fv(r->scene.model, 1, GL_FALSE, model.m);
Mesh_draw(mesh);
glUniform1f(r->scene.decal_size, 0.0f);
}
void Renderer_setHoles(Renderer *r, const float (*rects)[6], int count) {
float at[RENDER_HOLES * 4] = { 0 }, size[RENDER_HOLES * 2] = { 0 };
if (count > RENDER_HOLES) count = RENDER_HOLES;
for (int i = 0; i < count; i++) {
for (int k = 0; k < 4; k++) at[i * 4 + k] = rects[i][k];
size[i * 2] = rects[i][4];
size[i * 2 + 1] = rects[i][5];
}
glUniform4fv(r->scene.holes, RENDER_HOLES, at);
glUniform2fv(r->scene.hole_size, RENDER_HOLES, size);
glUniform1i(r->scene.hole_count, count);
}
void Renderer_drawTerrain(Renderer *r, const Mesh *mesh, GLuint wear_map, GLuint water_map, GLuint surface_map,
float size, float grass_depth) {
const SceneShader *s = &r->scene;
glActiveTexture(GL_TEXTURE0 + UNIT_WEAR); /* (bound even out of view: grass and decals read them too) */
glBindTexture(GL_TEXTURE_2D, wear_map);
glActiveTexture(GL_TEXTURE0 + UNIT_WATER);
glBindTexture(GL_TEXTURE_2D, water_map);
glActiveTexture(GL_TEXTURE0 + UNIT_SURFACE);
glBindTexture(GL_TEXTURE_2D, surface_map);
glActiveTexture(GL_TEXTURE0);
if (!meshInView(r, mesh, mat4_identity())) return;
glUniform1f(s->terrain_size, size);
glUniform1f(s->grass_depth, grass_depth);
glUniformMatrix4fv(s->model, 1, GL_FALSE, mat4_identity().m);
Mesh_draw(mesh);
glUniform1f(s->terrain_size, 0.0f);
}
/* ---------- Figures ---------- */
#define FIGURE_BODY_RADIUS 0.25f /* how far flesh and hair reach beyond the joints */
void Renderer_drawFigure(Renderer *r, const FigurePose *pose) {
const FigureShader *f = &r->figure;
const Skeleton *sk = &pose->skeleton;
/* Card through the middle of the body, facing the viewer:
the camera, or the light in the shadow pass. */
Vec3 center = vec3_zero();
Vec3 world[JOINT_COUNT];
for (int i = 0; i < JOINT_COUNT; i++) {
world[i] = Figure_toWorld(pose->position, pose->facing, sk->joints[i]);
center = vec3_add(center, world[i]);
}
center = vec3_scale(center, 1.0f / (float)JOINT_COUNT);
{
float reach = 0.0f;
for (int i = 0; i < JOINT_COUNT; i++) reach = fmaxf(reach, vec3_length(vec3_sub(world[i], center)));
if (sk->stream_kind != STREAM_NONE)
for (int k = 0; k < 4; k++)
reach = fmaxf(reach, vec3_length(vec3_sub(Figure_toWorld(pose->position, pose->facing, sk->stream[k]), center)));
if (!Renderer_inView(r, center, reach + FIGURE_BODY_RADIUS)) return;
}
Vec3 to_viewer = r->shadow_pass ? r->light_dir : vec3_normalize(vec3_sub(r->viewer_pos, center));
Vec3 u = vec3_cross(vec3(0, 1, 0), to_viewer);
u = vec3_length(u) > 1e-3f ? vec3_normalize(u) : vec3_normalize(vec3_cross(pose->facing, vec3(0, 1, 0)));
Vec3 v = vec3_normalize(vec3_cross(to_viewer, u));
/* Bounds of the joints as the viewer sees them on the card: straight
along the light, or in perspective from the camera eye. */
float min_x = 1e9f, max_x = -1e9f, min_y = 1e9f, max_y = -1e9f;
float card_dist = vec3_dot(vec3_sub(center, r->viewer_pos), vec3_scale(to_viewer, -1.0f));
/* The card also covers a stream to the ground */
Vec3 points[JOINT_COUNT + 4];
int n = JOINT_COUNT;
for (int i = 0; i < JOINT_COUNT; i++) points[i] = world[i];
if (sk->stream_kind != STREAM_NONE)
for (int k = 0; k < 4; k++) points[n++] = Figure_toWorld(pose->position, pose->facing, sk->stream[k]);
for (int i = 0; i < n; i++) {
Vec3 hit = points[i];
float grow = 1.0f;
if (!r->shadow_pass) {
Vec3 from_eye = vec3_sub(points[i], r->viewer_pos);
float depth = fmaxf(vec3_dot(from_eye, vec3_scale(to_viewer, -1.0f)), 0.05f);
grow = card_dist / depth;
hit = vec3_add(r->viewer_pos, vec3_scale(from_eye, grow));
}
Vec3 rel = vec3_sub(hit, center);
float x = vec3_dot(rel, u), y = vec3_dot(rel, v);
float reach = FIGURE_BODY_RADIUS * fmaxf(grow, 1.0f);
min_x = fminf(min_x, x - reach); max_x = fmaxf(max_x, x + reach);
min_y = fminf(min_y, y - reach); max_y = fmaxf(max_y, y + reach);
}
const float margin = 0.02f;
glUseProgram(f->program);
glDisable(GL_CULL_FACE);
setVec3(f->figure_pos, pose->position);
setVec3(f->facing, pose->facing);
setVec3(f->card_center, center);
setVec3(f->card_u, u);
setVec3(f->card_v, v);
glUniform2f(f->card_min, min_x - margin, min_y - margin);
glUniform2f(f->card_max, max_x + margin, max_y + margin);
glUniform3fv(f->joints, JOINT_COUNT, &sk->joints[0].x);
setVec3(f->skin, pose->look.skin);
setVec3(f->hair, pose->look.hair);
setVec3(f->shirt, pose->look.shirt);
setVec3(f->jacket, pose->look.jacket);
setVec3(f->pants, pose->look.pants);
setVec3(f->shoes, pose->look.shoes);
glUniform1i(f->hair_style, (GLint)pose->look.hair_style);
glUniform1i(f->long_sleeves, pose->look.long_sleeves ? 1 : 0);
glUniform1i(f->cast, pose->look.cast);
setVec3(f->head_forward, sk->head_forward);
setVec3(f->head_up, sk->head_up);
setVec3(f->chest_forward, sk->chest_forward);
setVec3(f->waist_bend, sk->waist_bend);
setVec3(f->face, vec3(sk->face.mouth, sk->face.eyelids, sk->face.brow));
glUniform2f(f->expression, sk->face.smile, sk->face.gaze);
glUniform1i(f->stream_kind, sk->stream_kind);
glUniform3fv(f->stream, 4, &sk->stream[0].x);
glUniform1f(f->stream_width, sk->stream_width);
glUniform1i(f->garment, pose->look.garment);
glUniform1f(f->undress, sk->undress);
glUniform1i(f->top, pose->look.top);
glUniform1i(f->jacket_tied, pose->look.jacket_tied && pose->look.top != TOP_JACKET ? 1 : 0);
glUniform1f(f->belly, pose->look.belly);
glUniform1f(f->wet, pose->look.wet);
glUniform1i(f->hood, pose->look.hoodie && pose->look.hood_up && pose->look.top == TOP_JACKET ? 1 : 0);
glUniform1i(f->hat, pose->look.hat);
glUniform3f(f->hat_color, pose->look.hat_color.x, pose->look.hat_color.y, pose->look.hat_color.z);
setVec3(f->cig_a, sk->cig_a);
setVec3(f->uri_a, sk->uri_a);
setVec3(f->uri_b, sk->uri_b);
glUniform1i(f->uri, sk->uri ? 1 : 0);
setVec3(f->cig_tip, sk->cig_tip);
glUniform1f(f->cig_glow, sk->cig_glow);
glUniform1i(f->species, sk->species);
setVec3(f->tail_a, sk->tail_a);
setVec3(f->tail_b, sk->tail_b);
glBindVertexArray(r->fullscreen_vao); /* no vertex data: the card is built from gl_VertexID */
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glEnable(GL_CULL_FACE);
glUseProgram(r->pass_program);
}
/* ---------- Ink pass ---------- */
void Renderer_endFrame(Renderer *r) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, r->width, r->height);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
const InkShader *k = &r->ink;
glUseProgram(k->program);
glUniform2f(k->texel, 1.0f / (float)r->width, 1.0f / (float)r->height);
glUniform1f(k->boil_time, r->boil_time);
glUniform1f(k->time, r->time);
glUniform1f(k->drunk, r->drunk);
glUniform1f(k->dark, r->dark);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, r->target.color);
glUniform1i(k->color_tex, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, r->target.normal_depth);
glUniform1i(k->normal_depth_tex, 1);
glBindVertexArray(r->fullscreen_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glActiveTexture(GL_TEXTURE0);
glEnable(GL_DEPTH_TEST);
}