314 lines
6.2 KiB
JavaScript
314 lines
6.2 KiB
JavaScript
import Shader from "../framework/WebGpu.js";
|
|
|
|
import Matrix4 from "../framework/Matrix4.js";
|
|
|
|
import Vector3 from "../framework/Vector3.js";
|
|
|
|
import Camera from "../framework/Camera.js";
|
|
|
|
import EventManager from "../framework/eventManager.js";
|
|
|
|
import ShaderInpector from "../framework/ShaderInpector.js";
|
|
|
|
import WasmModule from "./wasm_particles.js";
|
|
|
|
|
|
|
|
export class ParticleSimulation {
|
|
|
|
canvas;
|
|
|
|
device;
|
|
|
|
camera;
|
|
|
|
eventManager = new EventManager();
|
|
|
|
frameCount = 0;
|
|
|
|
wasm;
|
|
|
|
framebufferPtr;
|
|
|
|
framebufferUint8;
|
|
|
|
frameBufferTexture;
|
|
|
|
fbSampler;
|
|
|
|
width;
|
|
|
|
height;
|
|
|
|
renderShader;
|
|
|
|
time = 0;
|
|
|
|
lastTime = 0;
|
|
|
|
lastFrameTime;
|
|
|
|
|
|
setCanvas ( canvas ) {
|
|
|
|
this.canvas = canvas;
|
|
|
|
this.eventManager.setCanvas( canvas );
|
|
|
|
}
|
|
|
|
createPlane ( width, height, repeatU, repeatV ) {
|
|
|
|
const vertices = new Float32Array( new Array(
|
|
-width / 2, -height / 2, 0,
|
|
width / 2, -height / 2, 0,
|
|
-width / 2, height / 2, 0,
|
|
width / 2, -height / 2, 0,
|
|
width / 2, height / 2, 0,
|
|
-width / 2, height / 2, 0
|
|
) );
|
|
|
|
const uvs = new Float32Array( new Array(
|
|
0, 0,
|
|
repeatU, 0,
|
|
0, repeatV,
|
|
repeatU, 0,
|
|
repeatU, repeatV,
|
|
0, repeatV
|
|
) );
|
|
|
|
const result = {
|
|
vertices: vertices,
|
|
normals: null,
|
|
uvs: uvs
|
|
};
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
async setup ( offscreenCanvas, width, height ) {
|
|
|
|
this.fpsElement = document.getElementById( "fps" );
|
|
|
|
this.width = width;
|
|
|
|
this.height = height;
|
|
|
|
offscreenCanvas.width = width;
|
|
|
|
offscreenCanvas.height = height;
|
|
|
|
this.canvas = offscreenCanvas;
|
|
|
|
const context = offscreenCanvas.getContext( "webgpu" );
|
|
|
|
this.camera = new Camera( new Array( 0, 0, 1115 ), new Array( 0, -0.3, 0 ), new Array( 0, 1, 0 ) );
|
|
|
|
this.eventManager.setup( offscreenCanvas, this.camera );
|
|
|
|
const adapter = await navigator.gpu.requestAdapter();
|
|
|
|
if ( adapter === null ) {
|
|
|
|
throw new Error( "Failed to get GPU adapter" );
|
|
|
|
}
|
|
|
|
this.device = await adapter.requestDevice();
|
|
|
|
this.wasm = await WasmModule();
|
|
|
|
this.wasm._init_particles();
|
|
|
|
this.particleCount = this.wasm._get_particle_count();
|
|
|
|
this.particlePtr = this.wasm._get_particles_ptr();
|
|
|
|
|
|
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
|
|
context.configure({
|
|
device: this.device,
|
|
format: presentationFormat,
|
|
alphaMode: "opaque"
|
|
});
|
|
|
|
this.particleSize = 8 * 4;
|
|
|
|
this.particleBufferSize = this.particleCount * this.particleSize;
|
|
|
|
this.renderShader = new Shader( this.device );
|
|
|
|
this.renderShader.setCanvas( this.canvas );
|
|
|
|
this.particleFloatArray = new Float32Array(
|
|
this.wasm.HEAPU8.buffer,
|
|
this.wasm._get_particles_ptr(),
|
|
this.particleCount * 8
|
|
);
|
|
|
|
await this.renderShader.setup( "../shaders/simplePoint.wgsl" );
|
|
|
|
this.renderShader.setVariable("particles", this.particleFloatArray);
|
|
|
|
this.lastFrameTime = performance.now();
|
|
|
|
this.bindRenderLoop();
|
|
}
|
|
|
|
|
|
|
|
updateTimeDelta () {
|
|
|
|
const now = performance.now();
|
|
|
|
this.deltaTimeValue = ( now - this.lastFrameTime ) / 1000;
|
|
|
|
this.time += this.deltaTimeValue;
|
|
|
|
this.lastFrameTime = now;
|
|
|
|
this.frameCount++;
|
|
|
|
if ( now - this.lastTime >= 1000 ) {
|
|
|
|
const fps = this.frameCount / ( ( now - this.lastTime ) / 1000 );
|
|
|
|
const fpsText = "FPS: " + fps.toFixed( 1 );
|
|
|
|
this.fpsElement.textContent = fpsText;
|
|
|
|
this.frameCount = 0;
|
|
|
|
this.lastTime = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async render () {
|
|
|
|
this.updateTimeDelta();
|
|
|
|
this.wasm._update_particles( this.deltaTimeValue );
|
|
|
|
let buffer = this.renderShader.buffers.get( "particles" );
|
|
|
|
this.device.queue.writeBuffer(
|
|
buffer,
|
|
0,
|
|
this.particleFloatArray.buffer,
|
|
this.particleFloatArray.byteOffset,
|
|
this.particleFloatArray.byteLength
|
|
);
|
|
|
|
//this.renderShader.setVariable("particles", this.particleFloatArray);
|
|
|
|
|
|
const viewMatrix = this.camera.getViewMatrix();
|
|
|
|
const projectionMatrix = Matrix4.createProjectionMatrix( this.camera, this.canvas );
|
|
|
|
const viewProjectionMatrix = Matrix4.multiply( projectionMatrix, viewMatrix );
|
|
|
|
const cameraWorldMatrix = Matrix4.invert( viewMatrix );
|
|
|
|
const cameraPosition = Matrix4.getColumn( cameraWorldMatrix, 3 );
|
|
|
|
const cameraRight = Matrix4.getColumn( cameraWorldMatrix, 0 );
|
|
|
|
const cameraUp = Matrix4.getColumn( cameraWorldMatrix, 1 );
|
|
|
|
|
|
this.renderShader.setVariable( "cameraRight", cameraRight );
|
|
|
|
this.renderShader.setVariable( "cameraUp", cameraUp );
|
|
|
|
this.renderShader.setVariable("viewProjectionMatrix", viewProjectionMatrix);
|
|
|
|
this.renderShader.renderToCanvas( 6, this.particleCount, 0 ); // 6 vertices per quad
|
|
|
|
//this.fxaaShader.renderToCanvas(6, 1, 0);
|
|
|
|
}
|
|
|
|
renderTaa() {
|
|
|
|
const prevIndex = this.currentHistoryIndex;
|
|
const nextIndex = 1 - prevIndex;
|
|
|
|
// Set framebuffer into history
|
|
this.device.queue.writeTexture(
|
|
{ texture: this.historyTextures[nextIndex] },
|
|
this.framebufferUint8,
|
|
{ bytesPerRow: this.width * 4 },
|
|
{ width: this.width, height: this.height, depthOrArrayLayers: 1 }
|
|
);
|
|
|
|
// Apply TAA blend
|
|
this.taaShader.setVariable("currentFrame", this.historyTextures[nextIndex]);
|
|
this.taaShader.setVariable("historyFrame", this.historyTextures[prevIndex]);
|
|
|
|
this.taaShader.renderToCanvas(6, 1, 0);
|
|
|
|
// Swap history
|
|
this.currentHistoryIndex = nextIndex;
|
|
|
|
}
|
|
|
|
|
|
async setupTaa() {
|
|
|
|
this.taaShader = new Shader(this.device);
|
|
|
|
this.taaShader.setCanvas(this.canvas);
|
|
|
|
const quad = this.createPlane(2, 2, 1, 1);
|
|
|
|
this.taaShader.setAttribute("position", quad.vertices);
|
|
|
|
this.taaShader.setAttribute("uv", quad.uvs);
|
|
|
|
this.taaShader.topology = "triangle-list";
|
|
|
|
await this.taaShader.setup("../shaders/taa.wgsl");
|
|
|
|
this.taaShader.setVariable("mySampler", this.fbSampler);
|
|
|
|
this.taaShader.setVariable("blendAmount", new Float32Array([0.1])); // you can tweak this
|
|
|
|
this.historyTextures = [
|
|
this.frameBufferTexture,
|
|
this.device.createTexture({
|
|
size: [this.width, this.height, 1],
|
|
format: "rgba8unorm",
|
|
usage:
|
|
GPUTextureUsage.COPY_DST |
|
|
GPUTextureUsage.TEXTURE_BINDING |
|
|
GPUTextureUsage.RENDER_ATTACHMENT
|
|
})
|
|
];
|
|
|
|
this.currentHistoryIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
bindRenderLoop () {
|
|
|
|
requestAnimationFrame( this.loop.bind( this ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
async loop () {
|
|
|
|
await this.render();
|
|
|
|
this.bindRenderLoop();
|
|
|
|
}
|
|
}
|