#include "render/renderer.h" #include "render/shader.h" #include #include #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); }