Pre-allocate voxel mesh buffer, ~3x faster

This commit is contained in:
Lucas Dower 2022-04-14 19:10:04 +01:00
parent 081b602493
commit 6dd471bc32
4 changed files with 156 additions and 72 deletions

View File

@ -1,7 +1,8 @@
import { BasicBlockAssigner, OrderedDitheringBlockAssigner } from './block_assigner';
import { Voxel, VoxelMesh } from './voxel_mesh';
import { FACES_PER_VOXEL, VERTICES_PER_FACE, Voxel, VoxelMesh, VoxelMeshBufferComponentOffsets } from './voxel_mesh';
import { BlockAtlas, BlockInfo } from './block_atlas';
import { ColourSpace, AppError } from './util';
import { ColourSpace, AppError, ASSERT } from './util';
import { ComponentSize } from './buffer';
import { Renderer } from './renderer';
interface Block {
@ -70,21 +71,53 @@ export class BlockMesh {
}
public createBuffer() {
const buffer = Renderer.Get._voxelBuffer.copy();
ASSERT(this._blocks.length === this._voxelMesh.getVoxelCount());
const numBlocks = this._blocks.length;
const newBuffer = {
position: {
numComponents: ComponentSize.POSITION,
data: Renderer.Get._voxelBufferRaw!.position.data,
},
colour: {
numComponents: ComponentSize.COLOUR,
data: Renderer.Get._voxelBufferRaw!.colour.data,
},
occlusion: {
numComponents: ComponentSize.OCCLUSION,
data: Renderer.Get._voxelBufferRaw!.occlusion.data,
},
texcoord: {
numComponents: ComponentSize.TEXCOORD,
data: Renderer.Get._voxelBufferRaw!.texcoord.data,
},
normal: {
numComponents: ComponentSize.NORMAL,
data: Renderer.Get._voxelBufferRaw!.normal.data,
},
indices: {
numComponents: ComponentSize.INDICES,
data: Renderer.Get._voxelBufferRaw!.indices.data,
},
blockTexcoord: {
numComponents: ComponentSize.TEXCOORD,
data: new Float32Array(numBlocks * VoxelMeshBufferComponentOffsets.TEXCOORD),
},
};
const blockTexcoords: number[] = [];
for (const block of this._blocks) {
const faceOrder = ['north', 'south', 'up', 'down', 'east', 'west'];
for (const face of faceOrder) {
for (let i = 0; i < 4; ++i) {
const texcoord = block.blockInfo.faces[face].texcoord;
blockTexcoords.push(texcoord.u, texcoord.v);
let insertIndex = 0;
for (let i = 0; i < numBlocks; ++i) {
for (let f = 0; f < FACES_PER_VOXEL; ++f) {
const faceName = faceOrder[f];
const texcoord = this._blocks[i].blockInfo.faces[faceName].texcoord;
for (let v = 0; v < VERTICES_PER_FACE; ++v) {
newBuffer.blockTexcoord.data[insertIndex++] = texcoord.u;
newBuffer.blockTexcoord.data[insertIndex++] = texcoord.v;
}
}
}
buffer.attachNewAttribute({ name: 'blockTexcoord', numComponents: 2 }, blockTexcoords);
buffer.removeAttribute('colour');
return buffer;
return newBuffer;
}
}

View File

@ -3,6 +3,15 @@ import { ASSERT } from './util';
import * as twgl from 'twgl.js';
export namespace ComponentSize {
export const TEXCOORD = 2;
export const POSITION = 3;
export const COLOUR = 3;
export const NORMAL = 3;
export const INDICES = 3;
export const OCCLUSION = 4;
}
export interface Attribute {
name: string,
numComponents: number

View File

@ -48,8 +48,9 @@ export class Renderer {
buffer: RenderBuffer,
material: (SolidMaterial | (TexturedMaterial & { texture: WebGLTexture }))
}>;
public _voxelBuffer: RenderBuffer;
private _blockBuffer: RenderBuffer;
public _voxelBuffer?: twgl.BufferInfo;
public _voxelBufferRaw?: any;
private _blockBuffer?: twgl.BufferInfo;
private _debugBuffers: { [meshType: string]: { [bufferComponent: string]: RenderBuffer } };
private _isGridComponentEnabled: { [bufferComponent: string]: boolean };
@ -67,8 +68,6 @@ export class Renderer {
this._modelsAvailable = 0;
this._materialBuffers = [];
this._voxelBuffer = new RenderBuffer([]);
this._blockBuffer = new RenderBuffer([]);
this._debugBuffers = {};
this._debugBuffers[MeshType.None] = {};
@ -191,7 +190,9 @@ export class Renderer {
LOG('Using voxel mesh');
LOG(voxelMesh);
this._voxelBuffer = voxelMesh.createBuffer(ambientOcclusionEnabled);
this._voxelBufferRaw = voxelMesh.createBuffer(ambientOcclusionEnabled);
this._voxelBuffer = twgl.createBufferInfoFromArrays(this._gl, this._voxelBufferRaw);
this._voxelSize = voxelMesh?.getVoxelSize();
// this._translate = new Vector3(0, voxelMesh.getBounds().getDimensions().y/2 * voxelMesh.getVoxelSize(), 0);
@ -217,7 +218,7 @@ export class Renderer {
LOG('Using block mesh');
LOG(blockMesh);
this._blockBuffer = blockMesh.createBuffer();
this._blockBuffer = twgl.createBufferInfoFromArrays(this._gl, blockMesh.createBuffer());
this._voxelSize = blockMesh.getVoxelMesh().getVoxelSize();
this._atlasTexture = twgl.createTexture(this._gl, {
@ -275,21 +276,35 @@ export class Renderer {
}
private _drawVoxelMesh() {
this._drawRegister(this._voxelBuffer, ShaderManager.Get.voxelProgram, {
const shader = ShaderManager.Get.voxelProgram;
const uniforms = {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_voxelSize: this._voxelSize,
u_gridOffset: this._gridOffset.toArray(),
});
};
if (this._voxelBuffer) {
this._gl.useProgram(shader.program);
twgl.setBuffersAndAttributes(this._gl, shader, this._voxelBuffer);
twgl.setUniforms(shader, uniforms);
this._gl.drawElements(this._gl.TRIANGLES, this._voxelBuffer.numElements, this._gl.UNSIGNED_INT, 0);
}
}
private _drawBlockMesh() {
this._drawRegister(this._blockBuffer, ShaderManager.Get.blockProgram, {
const shader = ShaderManager.Get.blockProgram;
const uniforms = {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_texture: this._atlasTexture,
u_voxelSize: this._voxelSize,
u_atlasSize: BlockAtlas.Get.getAtlasSize(),
u_gridOffset: this._gridOffset.toArray(),
});
};
if (this._blockBuffer) {
this._gl.useProgram(shader.program);
twgl.setBuffersAndAttributes(this._gl, shader, this._blockBuffer);
twgl.setUniforms(shader, uniforms);
this._gl.drawElements(this._gl.TRIANGLES, this._blockBuffer.numElements, this._gl.UNSIGNED_INT, 0);
}
}
// /////////////////////////////////////////////////////////////////////////
@ -306,10 +321,6 @@ export class Renderer {
}
}
private static _getNeighbourIndex(neighbour: Vector3) {
return 9*(neighbour.x+1) + 3*(neighbour.y+1) + (neighbour.z+1);
}
private _setupScene() {
twgl.resizeCanvasToDisplaySize(<HTMLCanvasElement> this._gl.canvas);
this._gl.viewport(0, 0, this._gl.canvas.width, this._gl.canvas.height);

View File

@ -1,5 +1,4 @@
import { RenderBuffer, AttributeData } from './buffer';
import { AppConfig } from './config';
import { RenderBuffer, AttributeData, ComponentSize } from './buffer';
import { GeometryTemplates } from './geometry';
import { HashMap } from './hash_map';
import { Mesh } from './mesh';
@ -8,6 +7,20 @@ import { TextureFiltering } from './texture';
import { Bounds, RGB } from './util';
import { Vector3 } from './vector';
export const FACES_PER_VOXEL = 6;
export const VERTICES_PER_FACE = 4;
const INDICES_PER_VOXEL = 24;
const COMPONENT_PER_SIZE_OFFSET = FACES_PER_VOXEL * VERTICES_PER_FACE;
export namespace VoxelMeshBufferComponentOffsets {
export const TEXCOORD = ComponentSize.TEXCOORD * COMPONENT_PER_SIZE_OFFSET;
export const POSITION = ComponentSize.POSITION * COMPONENT_PER_SIZE_OFFSET;
export const COLOUR = ComponentSize.COLOUR * COMPONENT_PER_SIZE_OFFSET;
export const NORMAL = ComponentSize.NORMAL * COMPONENT_PER_SIZE_OFFSET;
export const INDICES = ComponentSize.INDICES * COMPONENT_PER_SIZE_OFFSET;
export const OCCLUSION = ComponentSize.OCCLUSION * COMPONENT_PER_SIZE_OFFSET;
}
export interface Voxel {
position: Vector3;
colour: RGB;
@ -94,57 +107,75 @@ export class VoxelMesh {
return this._bounds;
}
public getVoxelCount() {
return this._voxels.length;
}
// //////////////////////////////////////////////////////////////////////////
public createBuffer(ambientOcclusionEnabled: boolean) {
const buffer = new RenderBuffer([
{ name: 'position', numComponents: 3 },
{ name: 'colour', numComponents: 3 },
{ name: 'occlusion', numComponents: 4 },
{ name: 'texcoord', numComponents: 2 },
{ name: 'normal', numComponents: 3 },
]);
for (const voxel of this._voxels) {
// Each vertex of a face needs the occlusion data for the other 3 vertices
// in it's face, not just itself. Also flatten occlusion data.
let occlusions: number[];
if (ambientOcclusionEnabled) {
occlusions = OcclusionManager.Get.getOcclusions(voxel.position, this);
} else {
occlusions = OcclusionManager.Get.getBlankOcclusions();
}
const data: AttributeData = GeometryTemplates.getBoxBufferData(voxel.position);
data.custom.occlusion = occlusions;
data.custom.colour = [];
for (let i = 0; i < 24; ++i) {
data.custom.colour.push(voxel.colour.r, voxel.colour.g, voxel.colour.b);
}
const faceNormals = OcclusionManager.Get.getFaceNormals();
if (AppConfig.FACE_CULLING) {
// TODO: Optimise, enabling FACE_CULLING is slower than not bothering
for (let i = 0; i < 6; ++i) {
if (!this.isVoxelAt(Vector3.add(voxel.position, faceNormals[i]))) {
buffer.add({
custom: {
position: data.custom.position.slice(i * 12, (i+1) * 12),
occlusion: data.custom.occlusion.slice(i * 16, (i+1) * 16),
normal: data.custom.normal.slice(i * 12, (i+1) * 12),
texcoord: data.custom.texcoord.slice(i * 8, (i+1) * 8),
colour: data.custom.colour.slice(i * 12, (i+1) * 12),
const numVoxels = this._voxels.length;
const newBuffer = {
position: {
numComponents: ComponentSize.POSITION,
data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.POSITION),
},
indices: data.indices.slice(0, 6),
});
colour: {
numComponents: ComponentSize.COLOUR,
data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.COLOUR),
},
occlusion: {
numComponents: ComponentSize.OCCLUSION,
data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.OCCLUSION).fill(1.0),
},
texcoord: {
numComponents: ComponentSize.TEXCOORD,
data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.TEXCOORD),
},
normal: {
numComponents: ComponentSize.NORMAL,
data: new Float32Array(numVoxels * VoxelMeshBufferComponentOffsets.NORMAL),
},
indices: {
numComponents: ComponentSize.INDICES,
data: new Uint32Array(numVoxels * VoxelMeshBufferComponentOffsets.INDICES),
},
};
const cube: AttributeData = GeometryTemplates.getBoxBufferData(new Vector3(0, 0, 0));
for (let i = 0; i < numVoxels; ++i) {
const voxel = this._voxels[i];
const voxelColourArray = voxel.colour.toArray();
const voxelPositionArray = voxel.position.toArray();
for (let j = 0; j < VoxelMeshBufferComponentOffsets.POSITION; ++j) {
newBuffer.position.data[i * VoxelMeshBufferComponentOffsets.POSITION + j] = cube.custom.position[j] + voxelPositionArray[j % 3];
}
for (let j = 0; j < VoxelMeshBufferComponentOffsets.COLOUR; ++j) {
newBuffer.colour.data[i * VoxelMeshBufferComponentOffsets.COLOUR + j] = voxelColourArray[j % 3];
}
for (let j = 0; j < VoxelMeshBufferComponentOffsets.NORMAL; ++j) {
newBuffer.normal.data[i * VoxelMeshBufferComponentOffsets.NORMAL + j] = cube.custom.normal[j];
}
for (let j = 0; j < VoxelMeshBufferComponentOffsets.TEXCOORD; ++j) {
newBuffer.texcoord.data[i * VoxelMeshBufferComponentOffsets.TEXCOORD + j] = cube.custom.texcoord[j];
}
for (let j = 0; j < VoxelMeshBufferComponentOffsets.INDICES; ++j) {
newBuffer.indices.data[i * VoxelMeshBufferComponentOffsets.INDICES + j] = cube.indices[j] + (i * INDICES_PER_VOXEL);
}
if (ambientOcclusionEnabled) {
const voxelOcclusionArray = OcclusionManager.Get.getOcclusions(voxel.position, this);
for (let j = 0; j < VoxelMeshBufferComponentOffsets.OCCLUSION; ++j) {
newBuffer.occlusion.data[i * VoxelMeshBufferComponentOffsets.OCCLUSION + j] = voxelOcclusionArray[j];
}
} else {
buffer.add(data);
}
}
return buffer;
return newBuffer;
}
}