<?phpnamespace App\Engine;/** The GLSL ES 1.00 shaders of the HD renderer. */final class HdShaders{ /** Shared by the world and the sprites: sector heights and lights live in a 256 x 2 texture. */ private const HEIGHT = <<<'GLSL' uniform sampler2D u_sectors; float sectorHeight(float s, float which) { vec4 t = texture2D(u_sectors, vec2((s + 0.5) / 256.0, 0.25)); vec2 hl = which < 0.5 ? t.rg : t.ba; return (hl.x * 65280.0 + hl.y * 255.0) / 8.0 - 2048.0; } GLSL; public const WORLD_VS = "attribute vec4 a_pos;\nattribute vec4 a_tex;\nattribute vec4 a_info;\nattribute vec4 a_clamp;\n" . "uniform mat4 u_vp;\nuniform vec2 u_texSize;\n" . self::HEIGHT . <<<'GLSL' varying vec2 v_uv; varying vec3 v_world; varying vec3 v_normal; varying vec3 v_tangent; varying float v_light; varying vec2 v_ao; void main() { float z = sectorHeight(a_pos.z, a_pos.w); if (a_clamp.z > 0.5) { float c = sectorHeight(a_clamp.x, a_clamp.y); z = a_clamp.z < 1.5 ? min(z, c) : max(z, c); } vec3 w = vec3(a_pos.x, a_pos.y, z); if (a_info.y < 0.5) { float vref = sectorHeight(a_tex.y, a_tex.z) + a_tex.w; v_uv = vec2(a_tex.x, vref - z) / u_texSize; v_normal = vec3(a_info.z, a_info.w, 0.0); v_tangent = vec3(-a_info.w, a_info.z, 0.0); } else { v_uv = vec2(w.x, -w.y) / 64.0; v_normal = vec3(0.0, 0.0, a_info.y < 1.5 ? 1.0 : -1.0); v_tangent = vec3(1.0, 0.0, 0.0); } v_light = texture2D(u_sectors, vec2((a_info.x + 0.5) / 256.0, 0.75)).r; // height above the floor / below the ceiling of the sector seen: contact shadows of the walls v_ao = a_info.y < 0.5 ? vec2(z - sectorHeight(a_info.x, 0.0), sectorHeight(a_info.x, 1.0) - z) : vec2(100.0); v_world = w; gl_Position = u_vp * vec4(w.x, w.z, -w.y, 1.0); } GLSL; /** Lighting shared by walls / floors and sprites. */ private const LIGHT = <<<'GLSL' uniform vec3 u_eye; uniform vec4 u_lpos[8]; uniform vec4 u_lcol[8]; uniform vec3 u_fog; uniform float u_gamma; vec3 shade(vec3 base, vec3 world, vec3 n, float sector, float gloss) { float dist = length(world - u_eye); // the original's diminishing light, smooth float amb = sector * sector * 1.2 * clamp(1.3 - dist / 1500.0, 0.3, 1.0) + 0.035; amb *= 0.82 + 0.18 * dot(n, normalize(vec3(0.45, 0.25, 0.85))); vec3 lit = vec3(amb); vec3 v = normalize(u_eye - world); for (int i = 0; i < 8; i++) { float r = u_lpos[i].w; if (r > 0.0) { vec3 d = u_lpos[i].xyz - world; float dl = max(length(d), 1.0); float att = clamp(1.0 - dl / r, 0.0, 1.0); att *= att; vec3 l = d / dl; lit += u_lcol[i].rgb * att * (0.3 + 0.7 * max(dot(n, l), 0.0)); vec3 h = normalize(l + v); lit += u_lcol[i].rgb * att * pow(max(dot(n, h), 0.0), 28.0) * gloss; } } vec3 col = base * lit * u_gamma; float f = clamp((dist - 250.0) / 2400.0, 0.0, 0.8); return mix(col, u_fog * (0.4 + sector), f); } GLSL; private const PRECISION = "#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n"; public const WORLD_FS = self::PRECISION . "uniform sampler2D u_tex;\nuniform vec2 u_texSize;\n" . self::LIGHT . <<<'GLSL' varying vec2 v_uv; varying vec3 v_world; varying vec3 v_normal; varying vec3 v_tangent; varying float v_light; varying vec2 v_ao; float luma(vec3 c) { return dot(c, vec3(0.3, 0.59, 0.11)); } void main() { vec4 c = texture2D(u_tex, v_uv); if (c.a < 0.5) discard; // relief from the picture: brighter texels stand out vec2 px = 1.0 / (u_texSize * 2.0); float l0 = luma(c.rgb); float lx = luma(texture2D(u_tex, v_uv + vec2(px.x, 0.0)).rgb); float ly = luma(texture2D(u_tex, v_uv + vec2(0.0, px.y)).rgb); vec3 b = cross(v_normal, v_tangent); vec3 n = normalize(v_normal - (lx - l0) * 3.0 * v_tangent + (ly - l0) * 3.0 * b); // texture detail: a fine grain so that close walls are not flat colour blocks float grain = fract(sin(dot(floor(v_uv * u_texSize * 4.0), vec2(12.9898, 78.233))) * 43758.5453); vec3 base = c.rgb * (0.94 + 0.12 * grain); base *= (0.5 + 0.5 * smoothstep(0.0, 36.0, v_ao.x)) * (0.65 + 0.35 * smoothstep(0.0, 28.0, v_ao.y)); gl_FragColor = vec4(shade(base, v_world, n, v_light, 0.25 + l0 * 0.8), 1.0); } GLSL; public const SPRITE_VS = <<<'GLSL' attribute vec2 a_corner; uniform mat4 u_vp; uniform vec3 u_center; uniform vec4 u_rect; uniform vec3 u_right; varying vec2 v_uv; varying vec3 v_world; void main() { float x = -u_rect.x + a_corner.x * u_rect.z; float z = u_rect.y - a_corner.y * u_rect.w; vec3 w = u_center + u_right * x + vec3(0.0, 0.0, z); v_uv = a_corner; v_world = w; gl_Position = u_vp * vec4(w.x, w.z, -w.y, 1.0); } GLSL; public const SPRITE_FS = self::PRECISION . "uniform sampler2D u_tex;\nuniform float u_sector;\nuniform float u_bright;\n" . self::LIGHT . <<<'GLSL' varying vec2 v_uv; varying vec3 v_world; void main() { vec4 c = texture2D(u_tex, v_uv); if (c.a < 0.5) discard; vec3 n = normalize(vec3(u_eye.xy - v_world.xy, 0.0)); vec3 col = u_bright > 0.5 ? c.rgb * 1.25 : shade(c.rgb, v_world, n, u_sector, 0.2); gl_FragColor = vec4(col, 1.0); } GLSL; /** Screen-space picture (the weapon in the hands): a_corner 0..1, u_rect in NDC. */ public const SCREEN_VS = <<<'GLSL' attribute vec2 a_corner; uniform vec4 u_rect; varying vec2 v_uv; void main() { v_uv = a_corner; gl_Position = vec4(u_rect.x + a_corner.x * u_rect.z, u_rect.y - a_corner.y * u_rect.w, 0.0, 1.0); } GLSL; public const SCREEN_FS = <<<'GLSL' precision mediump float; uniform sampler2D u_tex; uniform vec3 u_light; varying vec2 v_uv; void main() { vec4 c = texture2D(u_tex, v_uv); if (c.a < 0.5) discard; // a soft key light from above, like the room lights falling on the gun float top = 1.0 - v_uv.y * 0.35; gl_FragColor = vec4(c.rgb * u_light * top, 1.0); } GLSL; /** The sky behind everything: a panorama turning with the view. */ public const SKY_VS = <<<'GLSL' attribute vec2 a_corner; varying vec2 v_uv; void main() { v_uv = a_corner; gl_Position = vec4(a_corner * 2.0 - 1.0, 0.999, 1.0); } GLSL; public const SKY_FS = <<<'GLSL' precision mediump float; uniform sampler2D u_tex; uniform float u_angle; uniform float u_tanHalf; uniform float u_aspect; varying vec2 v_uv; void main() { float yaw = u_angle + atan((0.5 - v_uv.x) * 2.0 * u_tanHalf); float u = -yaw / 6.2831853 * 4.0; float row = 100.0 - (v_uv.y - 0.5) * 200.0 * 1.0; vec3 c = texture2D(u_tex, vec2(u, clamp(row / 128.0, 0.0, 1.0))).rgb; gl_FragColor = vec4(c * 1.05, 1.0); } GLSL; /** Post-processing of the whole picture: bloom, damage / pickup tint, vignette, filmic curve. */ public const POST = <<<'GLSL' uniform vec4 u_tint; void main() { vec2 px = 1.0 / u_resolution; vec3 c = texture2D(u_frame, v_uv).rgb; vec3 bloom = vec3(0.0); for (int i = 0; i < 8; i++) { float a = float(i) * 0.785398; vec2 d = vec2(cos(a), sin(a)); bloom += max(texture2D(u_frame, v_uv + d * px * 3.0).rgb - 0.55, 0.0); bloom += max(texture2D(u_frame, v_uv + d * px * 8.0).rgb - 0.55, 0.0) * 0.7; } c += bloom * 0.22; c = mix(c, u_tint.rgb, u_tint.a); vec2 v = v_uv - 0.5; c *= 1.0 - dot(v, v) * 0.85; c = c * (1.0 + c * 0.15) / (1.0 + c * 0.35); gl_FragColor = vec4(c * 1.08, 1.0); } GLSL;}