Added Minecraft-esque ambient occlusion & lighting

This commit is contained in:
Lucas Dower 2021-07-22 17:52:31 +01:00
parent 3f60531fcd
commit 6dedc11c88
7 changed files with 193 additions and 37 deletions

22
shaders/ao_fragment.fs Normal file
View File

@ -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);
}

23
shaders/ao_vertex.vs Normal file
View File

@ -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);
}

View File

@ -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 () => {

View File

@ -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;

View File

@ -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}
]);
}
}

View File

@ -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;

View File

@ -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);