Initial
This commit is contained in:
Executable
+354
@@ -0,0 +1,354 @@
|
||||
|
||||
let
|
||||
options = {antialias: false},
|
||||
gl = (c.getContext('webgl', options) || c.getContext('experimental-webgl', options)),
|
||||
|
||||
R_MAX_VERTS = 1024 * 64, // allow 512k verts max
|
||||
R_MAX_LIGHT_V3 = 64,
|
||||
|
||||
// Vertex shader source. This translates the model position & rotation and also
|
||||
// mixes positions of two buffers for animations.
|
||||
R_SOURCE_VS =
|
||||
'precision highp float;' +
|
||||
|
||||
// Vertex positions, normals and uv coords for the fragment shader
|
||||
'varying vec3 vp,vn;' +
|
||||
'varying vec2 vt;' +
|
||||
|
||||
// Input vertex positions & normals and blend vertex positions & normals
|
||||
'attribute vec3 p,n,p2,n2;' +
|
||||
|
||||
// Input UV coords
|
||||
'attribute vec2 t;' +
|
||||
|
||||
// Camera position (x, y, z) and aspect ratio (w)
|
||||
'uniform vec4 c;' +
|
||||
|
||||
// Model position (x, y, z)
|
||||
'uniform vec3 mp;' +
|
||||
|
||||
// Model rotation (yaw, pitch)
|
||||
'uniform vec2 mr;' +
|
||||
|
||||
// Mouse rotation yaw (x), pitch (y)
|
||||
'uniform vec2 m;' +
|
||||
|
||||
// Blend factor between the two vertex positions
|
||||
'uniform float f;' +
|
||||
|
||||
// Generate a rotation Matrix around the x,y,z axis;
|
||||
// Used for model rotation and camera yaw
|
||||
'mat4 rx(float r){' +
|
||||
'return mat4(' +
|
||||
'1,0,0,0,' +
|
||||
'0,cos(r),sin(r),0,' +
|
||||
'0,-sin(r),cos(r),0,' +
|
||||
'0,0,0,1' +
|
||||
');' +
|
||||
'}' +
|
||||
|
||||
'mat4 ry(float r){' +
|
||||
'return mat4(' +
|
||||
'cos(r),0,-sin(r),0,' +
|
||||
'0,1,0,0,' +
|
||||
'sin(r),0,cos(r),0,' +
|
||||
'0,0,0,1' +
|
||||
');' +
|
||||
'}' +
|
||||
|
||||
'mat4 rz(float r){' +
|
||||
'return mat4(' +
|
||||
'cos(r),sin(r),0,0,' +
|
||||
'-sin(r),cos(r),0,0,' +
|
||||
'0,0,1,0,' +
|
||||
'0,0,0,1' +
|
||||
');' +
|
||||
'}' +
|
||||
|
||||
'void main(void){' +
|
||||
// Rotation Matrixes for model rotation
|
||||
'mat4 '+
|
||||
'mry=ry(mr.x),' +
|
||||
'mrz=rz(mr.y);' +
|
||||
|
||||
// Mix vertex positions, rotate and add the model position
|
||||
'vp=(mry*mrz*vec4(mix(p,p2,f),1.)).xyz+mp;' +
|
||||
|
||||
// Mix normals
|
||||
'vn=(mry*mrz*vec4(mix(n,n2,f),1.)).xyz;' +
|
||||
|
||||
// UV coords are handed over to the fragment shader as is
|
||||
'vt=t;' +
|
||||
|
||||
// Final vertex position is transformed by the projection matrix,
|
||||
// rotated around mouse yaw/pitch and offset by the camera position
|
||||
// We use a FOV of 90, so the matrix[0] and [5] are conveniently 1.
|
||||
// (1 / Math.tan((90/180) * Math.PI / 2) === 1)
|
||||
'gl_Position=' +
|
||||
'mat4(' +
|
||||
'1,0,0,0,' +
|
||||
'0,c.w,0,0,' +
|
||||
'0,0,1,1,' +
|
||||
'0,0,-2,0' +
|
||||
')*' + // projection
|
||||
'rx(-m.y)*ry(-m.x)*' +
|
||||
'vec4(vp-c.xyz,1.);' +
|
||||
'}',
|
||||
|
||||
// Fragment shader source. Calculates the lighting, does some cheesy gamma
|
||||
// correction and reduces the colors of the final output.
|
||||
R_SOURCE_FS =
|
||||
'precision highp float;' +
|
||||
|
||||
// Vertex positions, normals and uv coords
|
||||
'varying vec3 vp,vn;' +
|
||||
'varying vec2 vt;' +
|
||||
|
||||
'uniform sampler2D s;' +
|
||||
|
||||
// Lights [(x,y,z), [r,g,b], ...]
|
||||
'uniform vec3 l['+R_MAX_LIGHT_V3+'];' +
|
||||
|
||||
'void main(void){' +
|
||||
'gl_FragColor=texture2D(s,vt);' +
|
||||
|
||||
// Debug: no textures
|
||||
// 'gl_FragColor=vec4(1.0,1.0,1.0,1.0);' +
|
||||
|
||||
// Calculate all lights
|
||||
'vec3 vl;' +
|
||||
'for(int i=0;i<'+R_MAX_LIGHT_V3+';i+=2) {' +
|
||||
'vl+=' +
|
||||
// Angle to normal
|
||||
'max('+
|
||||
'dot('+
|
||||
'vn, normalize(l[i]-vp)' +
|
||||
')' +
|
||||
',0.)*' +
|
||||
'(1./pow(length(l[i]-vp),2.))' + // Inverse distance squared
|
||||
'*l[i+1];' + // Light color/intensity
|
||||
'}' +
|
||||
|
||||
// Debug: full bright lights
|
||||
// 'vl = vec3(2,2,2);' +
|
||||
|
||||
'gl_FragColor.rgb=floor('+
|
||||
'gl_FragColor.rgb*pow(vl,vec3(0.75))'+ // Light, Gamma
|
||||
'*16.0+0.5'+
|
||||
')/16.0;' + // Reduce final output color for some extra dirty looks
|
||||
'}',
|
||||
|
||||
// 8 properties per vert [x,y,z, u,v, nx,ny,nz]
|
||||
r_buffer = new Float32Array(R_MAX_VERTS*8),
|
||||
r_num_verts = 0,
|
||||
|
||||
// 2 vec3 per light [(x,y,z), [r,g,b], ...]
|
||||
r_light_buffer = new Float32Array(R_MAX_LIGHT_V3*3),
|
||||
r_num_lights = 0,
|
||||
|
||||
// Uniform locations
|
||||
r_u_camera,
|
||||
r_u_lights,
|
||||
r_u_mouse,
|
||||
r_u_pos,
|
||||
r_u_rotation,
|
||||
r_u_frame_mix,
|
||||
|
||||
// Vertex attribute location for mixing
|
||||
r_va_p2, r_va_n2,
|
||||
|
||||
// Texture handles
|
||||
r_textures = [],
|
||||
|
||||
// Camera position
|
||||
r_camera = vec3(0, 0,-50),
|
||||
r_camera_pitch = 0.2,
|
||||
r_camera_yaw = 0,
|
||||
|
||||
// We collect all draw calls in an array and draw them all at once at the end
|
||||
// the frame. This way the lights buffer will be completely filled and we
|
||||
// only need to set it once for all geometry
|
||||
r_draw_calls = [],
|
||||
|
||||
r_init = () => {
|
||||
// Create shorthand WebGL function names
|
||||
// let webglShortFunctionNames = {};
|
||||
for (let name in gl) {
|
||||
if (gl[name].length != undefined) {
|
||||
gl[name.match(/(^..|[A-Z]|\d.|v$)/g).join('')] = gl[name];
|
||||
// webglShortFunctionNames[name] = 'gl.' +name.match(/(^..|[A-Z]|\d.|v$)/g).join('');
|
||||
}
|
||||
}
|
||||
// console.log(JSON.stringify(webglShortFunctionNames, null, '\t'));
|
||||
|
||||
let shader_program = gl.createProgram();
|
||||
gl.attachShader(shader_program, r_compile_shader(gl.VERTEX_SHADER, R_SOURCE_VS));
|
||||
gl.attachShader(shader_program, r_compile_shader(gl.FRAGMENT_SHADER, R_SOURCE_FS));
|
||||
gl.linkProgram(shader_program);
|
||||
gl.useProgram(shader_program);
|
||||
|
||||
r_u_camera = gl.getUniformLocation(shader_program, 'c');
|
||||
r_u_lights = gl.getUniformLocation(shader_program, 'l');
|
||||
r_u_mouse = gl.getUniformLocation(shader_program, 'm');
|
||||
r_u_pos = gl.getUniformLocation(shader_program, 'mp');
|
||||
r_u_rotation = gl.getUniformLocation(shader_program, 'mr');
|
||||
r_u_frame_mix = gl.getUniformLocation(shader_program, 'f');
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
|
||||
|
||||
r_vertex_attrib(shader_program, 'p', 3, 8, 0); // position
|
||||
r_vertex_attrib(shader_program, 't', 2, 8, 3); // texture coord
|
||||
r_vertex_attrib(shader_program, 'n', 3, 8, 5); // normals
|
||||
|
||||
r_va_p2 = r_vertex_attrib(shader_program, 'p2', 3, 8, 0); // mix position
|
||||
r_va_n2 = r_vertex_attrib(shader_program, 'n2', 3, 8, 5); // mix normals
|
||||
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
gl.viewport(0,0,c.width,c.height);
|
||||
},
|
||||
|
||||
r_compile_shader = (shader_type, shader_source) => {
|
||||
let shader = gl.createShader(shader_type);
|
||||
gl.shaderSource(shader, shader_source);
|
||||
gl.compileShader(shader);
|
||||
// console.log(gl.getShaderInfoLog(shader));
|
||||
return shader;
|
||||
},
|
||||
|
||||
r_vertex_attrib = (shader_program, attrib_name, count, vertex_size, offset) => {
|
||||
let location = gl.getAttribLocation(shader_program, attrib_name);
|
||||
gl.enableVertexAttribArray(location);
|
||||
gl.vertexAttribPointer(location, count, gl.FLOAT, false, vertex_size * 4, offset * 4);
|
||||
return location;
|
||||
},
|
||||
|
||||
r_create_texture = (c) => {
|
||||
let t = {t:gl.createTexture(), c};
|
||||
gl.bindTexture(gl.TEXTURE_2D, t.t);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
r_textures.push(t);
|
||||
},
|
||||
|
||||
r_prepare_frame = (r,g,b) => {
|
||||
gl.clearColor(r,g,b,1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
r_num_lights = 0;
|
||||
r_light_buffer.fill(0);
|
||||
},
|
||||
|
||||
r_end_frame = () => {
|
||||
gl.uniform4f(r_u_camera, r_camera.x, r_camera.y, r_camera.z, 16/9);
|
||||
gl.uniform2f(r_u_mouse, r_camera_yaw, r_camera_pitch);
|
||||
gl.uniform3fv(r_u_lights, r_light_buffer);
|
||||
|
||||
let vo = 0,
|
||||
last_texture = -1;
|
||||
for (let c of r_draw_calls) {
|
||||
|
||||
// c = [x, y, z, yaw, pitch, texture, offset1, offset2, mix, length]
|
||||
|
||||
// Bind new texture only if it changed from the previous one. The map
|
||||
// is sorted by texture indices, so this helps.
|
||||
if (last_texture != c[5]) {
|
||||
last_texture = c[5];
|
||||
gl.bindTexture(gl.TEXTURE_2D, r_textures[last_texture].t);
|
||||
}
|
||||
|
||||
gl.uniform3f(r_u_pos, c[0], c[1], c[2]);
|
||||
gl.uniform2f(r_u_rotation, c[3], c[4]);
|
||||
gl.uniform1f(r_u_frame_mix, c[8]);
|
||||
|
||||
// If we have two different frames, calculate the offset from the
|
||||
// drawArrays call to the mix frame.
|
||||
// Setting the vertexAttribPointer is quite expensive, so we only
|
||||
// do this if we have to; i.e. for animated models.
|
||||
if (vo != (c[7]-c[6])) {
|
||||
vo = (c[7]-c[6]);
|
||||
gl.vertexAttribPointer(r_va_p2, 3, gl.FLOAT, false, 8 * 4, vo*8*4);
|
||||
gl.vertexAttribPointer(r_va_n2, 3, gl.FLOAT, false, 8 * 4, (vo*8+5)*4);
|
||||
}
|
||||
gl.drawArrays(gl.TRIANGLES, c[6], c[9]);
|
||||
}
|
||||
|
||||
// Reset draw calls
|
||||
r_draw_calls = [];
|
||||
},
|
||||
|
||||
r_draw = (pos, yaw, pitch, texture, f1, f2, mix, num_verts) => {
|
||||
r_draw_calls.push([
|
||||
pos.x, pos.y, pos.z, yaw, pitch,
|
||||
texture, f1, f2, mix, num_verts
|
||||
]);
|
||||
},
|
||||
|
||||
r_submit_buffer = () => {
|
||||
gl.bufferData(gl.ARRAY_BUFFER, r_buffer.subarray(0, r_num_verts*8), gl.STATIC_DRAW);
|
||||
},
|
||||
|
||||
r_push_vert = (pos, normal, u, v) => {
|
||||
r_buffer.set([pos.x, pos.y, pos.z, u, v, normal.x, normal.y, normal.z], r_num_verts * 8);
|
||||
r_num_verts++;
|
||||
},
|
||||
|
||||
r_push_quad = (v0, v1, v2, v3, u, v) => {
|
||||
let n = vec3_face_normal(v0, v1, v2);
|
||||
r_push_vert(v0, n, u, 0);
|
||||
r_push_vert(v1, n, 0, 0);
|
||||
r_push_vert(v2, n, u, v);
|
||||
r_push_vert(v3, n, 0, v);
|
||||
r_push_vert(v2, n, u, v);
|
||||
r_push_vert(v1, n, 0, 0);
|
||||
},
|
||||
|
||||
r_push_block = (x, y, z, sx, sy, sz, texture) => {
|
||||
let canvas = r_textures[texture].c,
|
||||
index = r_num_verts,
|
||||
tx = sx/canvas.width,
|
||||
ty = sy/canvas.height,
|
||||
tz = sz/canvas.width,
|
||||
|
||||
// top
|
||||
v0 = vec3(x, y + sy, z),
|
||||
v1 = vec3(x + sx, y + sy, z),
|
||||
v2 = vec3(x, y + sy, z + sz),
|
||||
v3 = vec3(x + sx, y + sy, z + sz),
|
||||
|
||||
// bottom
|
||||
v4 = vec3(x, y, z + sz),
|
||||
v5 = vec3(x + sx, y, z + sz),
|
||||
v6 = vec3(x, y, z),
|
||||
v7 = vec3(x + sx, y, z);
|
||||
|
||||
r_push_quad(v0, v1, v2, v3, tx, tz); // top
|
||||
r_push_quad(v4, v5, v6, v7, tx, tz); // bottom
|
||||
r_push_quad(v2, v3, v4, v5, tx, ty); // front
|
||||
r_push_quad(v1, v0, v7, v6, tx, ty); // back
|
||||
r_push_quad(v3, v1, v5, v7, tz, ty); // right
|
||||
r_push_quad(v0, v2, v6, v4, tz, ty); // left
|
||||
return index;
|
||||
},
|
||||
|
||||
r_push_light = (pos, intensity, r, g, b) => {
|
||||
// Calculate the distance to the light, fade it out between 768--1024
|
||||
let fade = clamp(
|
||||
scale(
|
||||
vec3_dist(pos, r_camera),
|
||||
768, 1024, 1, 0
|
||||
),
|
||||
0, 1
|
||||
) * intensity * 10;
|
||||
|
||||
if (fade && r_num_lights < R_MAX_LIGHT_V3/2) {
|
||||
r_light_buffer.set([pos.x, pos.y, pos.z, r*fade, g*fade, b*fade], r_num_lights*6);
|
||||
r_num_lights++;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user