From 6dedc11c88542413ba5b9d2fd3a7986da87d37ce Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Thu, 22 Jul 2021 17:52:31 +0100 Subject: [PATCH] Added Minecraft-esque ambient occlusion & lighting --- shaders/ao_fragment.fs | 22 ++++++ shaders/ao_vertex.vs | 23 +++++++ src/client.js | 11 ++- src/geometry.js | 4 +- src/renderer.js | 153 ++++++++++++++++++++++++++++++++++------- src/shaders.js | 11 ++- src/voxel_manager.js | 6 ++ 7 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 shaders/ao_fragment.fs create mode 100644 shaders/ao_vertex.vs diff --git a/shaders/ao_fragment.fs b/shaders/ao_fragment.fs new file mode 100644 index 0000000..1f38f65 --- /dev/null +++ b/shaders/ao_fragment.fs @@ -0,0 +1,22 @@ +precision mediump float; + +varying vec4 v_colour; +varying vec4 v_occlusion; +varying vec2 v_texcoord; + + +void main() { + float u = v_texcoord.x; + float v = v_texcoord.y; + + float a = v_occlusion.x; + float b = v_occlusion.y; + float c = v_occlusion.z; + float d = v_occlusion.w; + + float g = v*(u*b + (1.0-u)*d) + (1.0-v)*(u*a + (1.0-u)*c); + + gl_FragColor = vec4(v_colour.xyz * g, 1.0); + //gl_FragColor = vec4(v_colour.xyz, 1.0); + //gl_FragColor = vec4(vec3(g), 1.0); +} diff --git a/shaders/ao_vertex.vs b/shaders/ao_vertex.vs new file mode 100644 index 0000000..3cff1e0 --- /dev/null +++ b/shaders/ao_vertex.vs @@ -0,0 +1,23 @@ +uniform mat4 u_worldViewProjection; + +attribute vec4 position; +attribute vec3 normal; +attribute vec4 occlusion; +attribute vec2 texcoord; + +varying vec4 v_colour; +varying vec4 v_occlusion; +varying vec2 v_texcoord; + +vec3 light = vec3(0.78, 0.98, 0.59); + + +void main() { + v_texcoord = texcoord; + v_occlusion = occlusion; + //float lighting = dot(light, abs(normal)) * (1.0 - occlusion * 0.2); + //float lighting = 0.2 * occlusion; + //v_colour = vec4(abs(normal), 1.0); + v_colour = vec4(vec3(dot(light, abs(normal))), 1.0); + gl_Position = u_worldViewProjection * vec4(position.xyz, 1.0); +} diff --git a/src/client.js b/src/client.js index bd623b3..7ebe3d0 100644 --- a/src/client.js +++ b/src/client.js @@ -7,13 +7,16 @@ const { Schematic } = require('./src/schematic.js'); const dialog = require('electron').remote.dialog; const voxelSize = document.querySelector("#voxelInput").value; -let renderer = new Renderer(30, new Vector3(0.1, 0.1, 0.1)); +let renderer = new Renderer(30, new Vector3(0.1, 0.1, 0.1)); const voxelManager = new VoxelManager(voxelSize); const canvas = document.querySelector("#c"); -let loadedMesh = null; +//const mesh = new Mesh('./resources/suzanne.obj'); +//renderer.registerMesh(mesh); +//renderer.compile(); +let loadedMesh = null; function showToastWithText(text, style) { @@ -48,8 +51,10 @@ $("#loadBtn").on("click", () => { } renderer.clear(); + //renderer.setDebug(true); renderer.registerMesh(loadedMesh); renderer.compile(); + console.log(renderer); $('#voxelInput').prop('disabled', false); $('#voxelBtn').prop('disabled', false); @@ -115,6 +120,7 @@ $("#voxelBtn").on("click", () => { }); +// SPLIT BUTTON $("#splitBtn").on("click", () => { const voxelSize = $("#voxelInput").prop('value'); $("#voxelInput").prop('value', voxelSize / 2); @@ -127,7 +133,6 @@ $("#splitBtn").on("click", () => { }); - // EXPORT SCHEMATIC $("#exportBtn").on("click", async () => { diff --git a/src/geometry.js b/src/geometry.js index 3c1d676..64b6cc0 100644 --- a/src/geometry.js +++ b/src/geometry.js @@ -70,12 +70,14 @@ class GeometryTemplates { let cube = { position: new Float32Array(72), normal: new Float32Array(72), - indices: new Float32Array(72) + indices: new Float32Array(72), + texcoord: new Float32Array(48) }; cube.position.set(default_cube.position); cube.normal.set(default_cube.normal); cube.indices.set(default_cube.indices); + cube.texcoord.set(default_cube.texcoord); for (let i = 0; i < 72; i += 3) { cube.position[i + 0] = (cube.position[i + 0] * size.x) + centre.x; diff --git a/src/renderer.js b/src/renderer.js index 33740c7..ee13fa5 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -16,8 +16,8 @@ class Renderer { this._gl = document.querySelector("#c").getContext("webgl"); this._camera = new ArcballCamera(fov, this._gl.canvas.clientWidth / this._gl.canvas.clientHeight, 0.5, 100.0); - this._registerEvents(); - this._getNewBuffers(); + this._registerEvents(); // Register mouse events for interacting with canvas + this._getNewBuffers(); // Setup WebGL Buffers this._debug = false; this._compiled = false; @@ -39,6 +39,39 @@ class Renderer { this._registerData(data); } + _registerVoxel(centre, voxelSize, voxelManager) { + const sizeVector = new Vector3(voxelSize, voxelSize, voxelSize); + + let occlusions = new Array(6); + // For each face + for (let f = 0; f < 6; ++f) { + // For each vertex + occlusions[f] = [0, 0, 0, 0]; + for (let v = 0; v < 4; ++v) { + // For each occlusion vertex + for (let o = 0; o < 3; ++o) { + occlusions[f][v] += voxelManager.isVoxelAt(Vector3.add(centre, this.occlusions[f][v][o])); + } + } + } + + // Each vertex of a face needs the occlusion data for the other 3 vertices + // in it's face, not just itself, pack this into a vec4. + for (let i = 0; i < 6; ++i) { + occlusions[i] = [].concat(...new Array(4).fill(occlusions[i])); + } + + let data = GeometryTemplates.getBoxBufferData(centre, sizeVector, false); + data.occlusion = [].concat(...occlusions); + + // Convert from occlusion denoting the occlusion factor to the + // attenuation in light value: 0 -> 1.0, 1 -> 0.8, 2 -> 0.6, 3 -> 0.4 + data.occlusion = data.occlusion.map(x => 1.0 - 0.2 * x); + + //this._registerData(data); + this._registerVoxels.add(data); + } + registerTriangle(triangle) { const data = GeometryTemplates.getTriangleBufferData(triangle, this._debug); this._registerData(data); @@ -51,10 +84,26 @@ class Renderer { } registerVoxelMesh(voxelManager) { + const voxelSize = voxelManager._voxelSize; + const sizeVector = new Vector3(voxelSize/2, voxelSize/2, voxelSize/2); + if (this.debug) { + voxelManager.voxels.forEach((voxel) => { + this.registerBox(voxel, sizeVector); + }); + } else { + this._setupOcclusions(voxelSize); // Setup arrays for calculating voxel ambient occlusion + + voxelManager.voxels.forEach((voxel) => { + this._registerVoxel(voxel, voxelSize, voxelManager); + }); + } + + /* const mesh = voxelManager.buildMesh(); for (const box of mesh) { this.registerBox(box.centre, box.size, false); } + */ } clear() { @@ -63,7 +112,8 @@ class Renderer { compile() { this._registerDebug.compile(this._gl); - this._register.compile(this._gl); + this._registerVoxels.compile(this._gl); + this._registerDefault.compile(this._gl); this._compiled = true; } @@ -75,32 +125,70 @@ class Renderer { this._setupScene(); - this._drawDebugRegisters(); - this._drawRegisters(); - } - - - - - _drawDebugRegisters() { - const debugUniforms = { + // Draw debug register + this._drawRegister(this._registerDebug, this._gl.LINES, shaderManager.debugProgram, { u_worldViewProjection: this._camera.getWorldViewProjection(), - }; - - for (const buffer of this._registerDebug.WebGLBuffers) { - this._drawBuffer(this._gl.LINES, buffer, shaderManager.debugProgram, debugUniforms); - } - } + }); - _drawRegisters() { - const uniforms = { + // Draw voxel register + this._drawRegister(this._registerVoxels, this._gl.TRIANGLES, shaderManager.aoProgram, { + u_worldViewProjection: this._camera.getWorldViewProjection() + }); + + // Draw default register + this._drawRegister(this._registerDefault, this._gl.TRIANGLES, shaderManager.shadedProgram, { u_lightWorldPos: this._camera.getCameraPosition(0.0, 0.0), u_worldViewProjection: this._camera.getWorldViewProjection(), u_worldInverseTranspose: this._camera.getWorldInverseTranspose() - }; + }); + } - for (const buffer of this._register.WebGLBuffers) { - this._drawBuffer(this._gl.TRIANGLES, buffer, shaderManager.shadedProgram, uniforms); + _drawRegister(register, drawMode, shaderProgram, uniforms) { + for (const buffer of register.WebGLBuffers) { + this._drawBuffer(drawMode, buffer, shaderProgram, uniforms); + } + } + + _setupOcclusions(voxelSize) { + this.occlusions = new Array(6).fill(null).map(function() { return new Array(4).fill(0); }); + + this.occlusions[0][0] = [new Vector3( 1, 1, 0), new Vector3( 1, 1, -1), new Vector3( 1, 0, -1)]; + this.occlusions[0][1] = [new Vector3( 1, -1, 0), new Vector3( 1, -1, -1), new Vector3( 1, 0, -1)]; + this.occlusions[0][2] = [new Vector3( 1, 1, 0), new Vector3( 1, 1, 1), new Vector3( 1, 0, 1)]; + this.occlusions[0][3] = [new Vector3( 1, -1, 0), new Vector3( 1, -1, 1), new Vector3( 1, 0, 1)]; + + this.occlusions[1][0] = [new Vector3(-1, 1, 0), new Vector3(-1, 1, 1), new Vector3(-1, 0, 1)]; + this.occlusions[1][1] = [new Vector3(-1, -1, 0), new Vector3(-1, -1, 1), new Vector3(-1, 0, 1)]; + this.occlusions[1][2] = [new Vector3(-1, 1, 0), new Vector3(-1, 1, -1), new Vector3(-1, 0, -1)]; + this.occlusions[1][3] = [new Vector3(-1, -1, 0), new Vector3(-1, -1, -1), new Vector3(-1, 0, -1)]; + + this.occlusions[2][0] = [new Vector3(-1, 1, 0), new Vector3(-1, 1, 1), new Vector3( 0, 1, 1)]; + this.occlusions[2][1] = [new Vector3(-1, 1, 0), new Vector3(-1, 1, -1), new Vector3( 0, 1, -1)]; + this.occlusions[2][2] = [new Vector3( 1, 1, 0), new Vector3( 1, 1, 1), new Vector3( 0, 1, 1)]; + this.occlusions[2][3] = [new Vector3( 1, 1, 0), new Vector3( 1, 1, -1), new Vector3( 0, 1, -1)]; + + this.occlusions[3][0] = [new Vector3(-1, -1, 0), new Vector3(-1, -1, -1), new Vector3( 0, -1, -1)]; + this.occlusions[3][1] = [new Vector3(-1, -1, 0), new Vector3(-1, -1, 1), new Vector3( 0, -1, 1)]; + this.occlusions[3][2] = [new Vector3( 1, -1, 0), new Vector3( 1, -1, -1), new Vector3( 0, -1, -1)]; + this.occlusions[3][3] = [new Vector3( 1, -1, 0), new Vector3( 1, -1, 1), new Vector3( 0, -1, 1)]; + + this.occlusions[4][0] = [new Vector3( 0, 1, 1), new Vector3( 1, 1, 1), new Vector3( 1, 0, 1)]; + this.occlusions[4][1] = [new Vector3( 0, -1, 1), new Vector3( 1, -1, 1), new Vector3( 1, 0, 1)]; + this.occlusions[4][2] = [new Vector3( 0, 1, 1), new Vector3(-1, 1, 1), new Vector3(-1, 0, 1)]; + this.occlusions[4][3] = [new Vector3( 0, -1, 1), new Vector3(-1, -1, 1), new Vector3(-1, 0, 1)]; + + this.occlusions[5][0] = [new Vector3( 0, 1, -1), new Vector3(-1, 1, -1), new Vector3(-1, 0, -1)]; + this.occlusions[5][1] = [new Vector3( 0, -1, -1), new Vector3(-1, -1, -1), new Vector3(-1, 0, -1)]; + this.occlusions[5][2] = [new Vector3( 0, 1, -1), new Vector3( 1, 1, -1), new Vector3( 1, 0, -1)]; + this.occlusions[5][3] = [new Vector3( 0, -1, -1), new Vector3( 1, -1, -1), new Vector3( 1, 0, -1)]; + + // Scale each Vector3 by voxelSize + for (let i = 0; i < 6; ++i) { + for (let j = 0; j < 4; ++j) { + for (let k = 0; k < 3; ++k) { + this.occlusions[i][j][k] = Vector3.mulScalar(this.occlusions[i][j][k], voxelSize); + } + } } } @@ -110,7 +198,7 @@ class Renderer { data.colour = [].concat(...new Array(numVertices).fill(this._strokeColour.toArray())); this._registerDebug.add(data); } else { - this._register.add(data); + this._registerDefault.add(data); } } @@ -149,6 +237,7 @@ class Renderer { } _drawBuffer(drawMode, buffer, shader, uniforms) { + //console.log(shader); this._gl.useProgram(shader.program); twgl.setBuffersAndAttributes(this._gl, shader, buffer.buffer); twgl.setUniforms(shader, uniforms); @@ -156,8 +245,20 @@ class Renderer { } _getNewBuffers() { - this._registerDebug = new SegmentedBuffer(16384, [{name: 'position', numComponents: 3}, {name: 'colour', numComponents: 3}]); - this._register = new SegmentedBuffer(16384, [{name: 'position', numComponents: 3}, {name: 'normal', numComponents: 3}]); + this._registerDebug = new SegmentedBuffer(16384, [ + {name: 'position', numComponents: 3}, + {name: 'colour', numComponents: 3} + ]); + this._registerVoxels = new SegmentedBuffer(16384, [ + {name: 'position', numComponents: 3}, + {name: 'normal', numComponents: 3}, + {name: 'occlusion', numComponents: 4}, + {name: 'texcoord', numComponents: 2} + ]); + this._registerDefault = new SegmentedBuffer(16384, [ + {name: 'position', numComponents: 3}, + {name: 'normal', numComponents: 3} + ]); } } diff --git a/src/shaders.js b/src/shaders.js index 735099c..36fa2e8 100644 --- a/src/shaders.js +++ b/src/shaders.js @@ -14,16 +14,13 @@ const shadedFragmentShader = getShader('shaded_fragment.fs'); const debugVertexShader = getShader('debug_vertex.vs'); const debugFragmentShader = getShader('debug_fragment.fs'); -/* -const shaded_vertex_shader = fs.readFileSync('./shaders/shaded_vertex.vs', 'utf8'); -const shaded_fragment_shader = fs.readFileSync('./shaders/shaded_fragment.fs', 'utf8'); - -const debug_vertex_shader = fs.readFileSync('./shaders/debug_vertex.vs', 'utf8'); -const debug_fragment_shader = fs.readFileSync('./shaders/debug_fragment.fs', 'utf8'); -*/ +const aoVertexShader = getShader('ao_vertex.vs'); +const aoFragmentShader = getShader('ao_fragment.fs'); const shadedProgram = twgl.createProgramInfo(gl, [shadedVertexShader, shadedFragmentShader]); const debugProgram = twgl.createProgramInfo(gl, [debugVertexShader, debugFragmentShader]); +const aoProgram = twgl.createProgramInfo(gl, [aoVertexShader, aoFragmentShader]); module.exports.shadedProgram = shadedProgram; module.exports.debugProgram = debugProgram; +module.exports.aoProgram = aoProgram; \ No newline at end of file diff --git a/src/voxel_manager.js b/src/voxel_manager.js index 54cb70d..ab9b0b4 100644 --- a/src/voxel_manager.js +++ b/src/voxel_manager.js @@ -76,6 +76,12 @@ class VoxelManager { ); } + isVoxelAt(vec) { + vec = Vector3.subScalar(vec, this._voxelSize / 2); + const pos = this._voxelCentreToPosition(vec); + return (this.voxelsHash.contains(pos)); + } + addVoxel(vec) { // (0.5, 0.5, 0.5) -> (0, 0, 0); vec = Vector3.subScalar(vec, this._voxelSize / 2);