1 module game.renderer; 2 3 import spasm.webgl; 4 import spasm.types; 5 import spasm.rt.memory; 6 import game.entity; 7 import game.math; 8 import std.range : only; 9 10 nothrow: 11 @safe: 12 13 immutable GLuint max_verts = 1024 * 64; 14 immutable GLint max_lights = 16; 15 __gshared float camera_shake = 0f; 16 17 extern(C) void glSetContext(Handle); 18 19 auto compile_shader(int shader_type, string shader_source) { 20 auto shader = glCreateShader(shader_type); 21 glShaderSource(shader, shader_source); 22 glCompileShader(shader); 23 return shader; 24 }; 25 26 auto enable_vertex_attrib(GLint shader_program, string attrib_name, int count, int vertex_size, int offset) { 27 auto location = glGetAttribLocation(shader_program, attrib_name); 28 glEnableVertexAttribArray(location); 29 glVertexAttribPointer(location, count, GL_FLOAT, false, vertex_size * 4, offset * 4); 30 }; 31 32 struct Renderer { 33 nothrow: 34 GLuint vertex_buffer; 35 GLuint shader_program; 36 37 immutable GLuint texture_size = 1024; 38 immutable GLuint tile_size = 16; 39 immutable GLfloat tile_fraction = cast(float)tile_size / texture_size; 40 immutable GLfloat px_nudge = 0.5 / texture_size; 41 GLuint num_verts = 0; 42 GLuint level_num_verts; 43 float[] buffer_data; 44 45 GLint light_uniform; 46 GLint num_lights = 0; 47 float[] light_data; 48 49 GLfloat camera_x = 0, camera_y = 0, camera_z = 0; 50 GLint camera_uniform; 51 52 immutable string shader_attribute_vec = "attribute vec"; 53 immutable string shader_varying = 54 "precision highp float;" ~ 55 "varying vec3 vl;" ~ 56 "varying vec2 vuv;"; 57 immutable string shader_uniform = "uniform "; 58 immutable string shader_const_mat4 = "const mat4 "; 59 60 immutable string vertex_shader = 61 shader_varying ~ 62 shader_attribute_vec ~ "3 p;" ~ 63 shader_attribute_vec ~ "2 uv;" ~ 64 shader_attribute_vec ~ "3 n;" ~ 65 shader_uniform ~ "vec3 cam;" ~ 66 shader_uniform ~ "float l[7*16];" ~ 67 shader_const_mat4 ~ "v=mat4(1,0,0,0,0,.707,.707,0,0,-.707,.707,0,0,-22.627,-22.627,1);" ~ // view 68 shader_const_mat4 ~ "r=mat4(.977,0,0,0,0,1.303,0,0,0,0,-1,-1,0,0,-2,0);"~ // projection 69 "void main(void){" ~ 70 "vl=vec3(0.3,0.3,0.6);" ~ // ambient color 71 "for(int i=0; i<16; i++) {"~ 72 "vec3 lp=vec3(l[i*7],l[i*7+1],l[i*7+2]);" ~ // light position 73 "vl+=vec3(l[i*7+3],l[i*7+4],l[i*7+5])" ~ // light color * 74 "*max(dot(n,normalize(lp-p)),0.)" ~ // diffuse * 75 "*(1./(l[i*7+6]*(" ~ // attentuation * 76 "length(lp-p)" ~ // distance 77 ")));" ~ 78 "}" ~ 79 "vuv=uv;" ~ 80 "gl_Position=r*v*(vec4(p+cam,1.));" ~ 81 "}"; 82 83 immutable string fragment_shader = 84 shader_varying ~ 85 shader_uniform ~ "sampler2D s;" ~ 86 "void main(void){" ~ 87 "vec4 t=texture2D(s,vuv);" ~ 88 "if(t.a<.8)" ~ // 1) discard alpha 89 "discard;" ~ 90 "if(t.r>0.95&&t.g>0.25&&t.b==0.0)" ~ // 2) red glowing spider eyes 91 "gl_FragColor=t;" ~ 92 "else{" ~ // 3) calculate color with lights and fog 93 "gl_FragColor=t*vec4(vl,1.);" ~ 94 "gl_FragColor.rgb*=smoothstep(" ~ 95 "112.,16.," ~ // fog far, near 96 "gl_FragCoord.z/gl_FragCoord.w" ~ // fog depth 97 ");" ~ 98 "}" ~ 99 "gl_FragColor.rgb=floor(gl_FragColor.rgb*6.35)/6.35;" ~ // reduce colors to ~256 100 "}"; 101 102 auto begin_build_level() { 103 num_verts = 0; 104 } 105 106 auto end_build_level(float x, float y, float z) { 107 camera_x = x; 108 camera_y = y; 109 camera_z = z; 110 level_num_verts = num_verts; 111 } 112 113 auto update_camera(ref Player player) @trusted { 114 // center camera on player, apply damping 115 camera_x = camera_x * 0.92 - player.entity.x * 0.08; 116 camera_y = camera_y * 0.92 - player.entity.y * 0.08; 117 camera_z = camera_z * 0.92 - player.entity.z * 0.08; 118 119 // add camera shake 120 camera_shake *= 0.9; 121 camera_x += camera_shake * (random()-0.5); 122 camera_z += camera_shake * (random()-0.5); 123 } 124 auto renderer_init(int width, int height) @trusted { 125 126 buffer_data = allocator.make!(float[max_verts*8]); 127 light_data = allocator.make!(float[max_lights*7]); 128 vertex_buffer = glCreateBuffer(); 129 glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 130 glBufferData(GL_ARRAY_BUFFER, buffer_data, GL_DYNAMIC_DRAW); 131 132 shader_program = glCreateProgram(); 133 glAttachShader(shader_program, compile_shader(GL_VERTEX_SHADER, vertex_shader)); 134 glAttachShader(shader_program, compile_shader(GL_FRAGMENT_SHADER, fragment_shader)); 135 glLinkProgram(shader_program); 136 glUseProgram(shader_program); 137 138 camera_uniform = glGetUniformLocation(shader_program, "cam"); 139 light_uniform = glGetUniformLocation(shader_program, "l"); 140 141 glEnable(GL_DEPTH_TEST); 142 glEnable(GL_BLEND); 143 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 144 glViewport(0,0,width,height); 145 146 shader_program.enable_vertex_attrib("p", 3, 8, 0); 147 shader_program.enable_vertex_attrib("uv", 2, 8, 3); 148 shader_program.enable_vertex_attrib("n", 3, 8, 5); 149 } 150 151 void renderer_bind_image(Handle image) { 152 auto texture_2d = GL_TEXTURE_2D; 153 glBindTexture(texture_2d, glCreateTexture()); 154 glTexImage2D(texture_2d, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, image); 155 glTexParameteri(texture_2d, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 156 glTexParameteri(texture_2d, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 157 glTexParameteri(texture_2d, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 158 glTexParameteri(texture_2d, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 159 } 160 auto renderer_prepare_frame() { 161 num_verts = level_num_verts; 162 num_lights = 0; 163 164 // reset all lights 165 light_data[] = 1f; 166 } 167 168 auto renderer_end_frame() { 169 glUniform3f(camera_uniform, camera_x, camera_y - 10, camera_z-30); 170 glUniform1fv(light_uniform, light_data); 171 172 glClearColor(0,0,0,1); 173 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 174 175 glBufferData(GL_ARRAY_BUFFER, buffer_data, GL_DYNAMIC_DRAW); 176 glDrawArrays(GL_TRIANGLES, 0, num_verts); 177 } 178 179 auto push_quad(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4, float nx, float ny, float nz, int tile) { 180 auto u = tile * tile_fraction + px_nudge; 181 import std.range : only; 182 import std.algorithm : copy; 183 only( 184 x1, y1, z1, u, 0, nx, ny, nz, 185 x2, y2, z2, u + tile_fraction - px_nudge, 0, nx, ny, nz, 186 x3, y3, z3, u, 1, nx, ny, nz, 187 x2, y2, z2, u + tile_fraction - px_nudge, 0, nx, ny, nz, 188 x3, y3, z3, u, 1, nx, ny, nz, 189 x4, y4, z4, u + tile_fraction - px_nudge, 1, nx, ny, nz 190 ).copy(buffer_data[num_verts*8..$]); 191 num_verts += 6; 192 } 193 auto push_sprite(float x, float y, float z, int tile) { 194 // Only push sprites near to the camera 195 if ( 196 abs(-x - camera_x) < 128 && 197 abs(-z - camera_z) < 128 198 ) { 199 auto tilt = 3+(camera_z + z)/12; // tilt sprite when closer to camera 200 push_quad(x, y + 6, z, x + 6, y + 6, z, x, y, z + tilt, x + 6, y, z + tilt, 0, 0, 1, tile); 201 } 202 } 203 auto push_floor(float x, float z, int tile) { 204 push_quad(x, 0, z, x + 8, 0, z, x, 0, z + 8, x + 8, 0, z + 8, 0,1,0, tile); 205 }; 206 auto push_block(float x, float z, int tile_top, int tile_sites) { 207 // tall blocks for certain tiles 208 bool tall = tile_sites == 8 || tile_sites == 9 || tile_sites == 17; 209 float y = tall ? 16 : 8; 210 211 push_quad(x, y, z, x + 8, y, z, x, y, z + 8, x + 8, y, z + 8, 0, 1, 0, tile_top); // top 212 push_quad(x + 8, y, z, x + 8, y, z + 8, x + 8, 0, z, x + 8, 0, z + 8, 1, 0, 0, tile_sites); // right 213 push_quad(x, y, z + 8, x + 8, y, z + 8, x, 0, z + 8, x + 8, 0, z + 8, 0, 0, 1, tile_sites); // front 214 push_quad(x, y, z, x, y, z + 8, x, 0, z, x, 0, z + 8, -1, 0, 0, tile_sites); // left 215 }; 216 auto push_light(float x, float y, float z, float r, float g, float b, float falloff) { 217 import std.algorithm : copy; 218 // Only push lights near to the camera 219 float max_light_distance = (128f + 1f/falloff); // cheap ass approximation 220 if ( 221 num_lights < max_lights && 222 abs(-x - camera_x) < max_light_distance && 223 abs(-z - camera_z) < max_light_distance 224 ) { 225 only(x, y, z, r, g, b, falloff).copy(light_data[num_lights*7..$]); 226 num_lights++; 227 } 228 } 229 } 230