UNPKG

matrix-engine-wgpu

Version:

Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas

204 lines (186 loc) 7.64 kB
import {mat4} from "wgpu-matrix"; import {flameEffectInstance} from "../../shaders/flame-effect/flame-instanced"; import {randomFloatFromTo, randomIntFromTo} from "../utils"; export class FlameEmitter { constructor(device, format, maxParticles = 20) { this.device = device; this.format = format; this.time = 0; this.intensity = 3.0; this.enabled = true; this.maxParticles = maxParticles; this.instanceTargets = []; this.floatsPerInstance = 28; this.instanceData = new Float32Array(maxParticles * this.floatsPerInstance); this.smoothFlickeringScale = 0.1; this.maxY = 1.9; this.minY = 0; this.swap0 = 0; this.swap1 = 1; this.swap2 = 2; for(let i = 0;i < maxParticles;i++) { this.instanceTargets.push({ position: [0, 0, 0], currentPosition: [0, 0, 0], scale: [1, 1, 1], currentScale: [1, 1, 1], rotation: 0.1, color: [1, 0.3, 0, 0.1], time: 1, intensity: 1, riseSpeed: 1 }); } this._initPipeline(); } recreateVertexData(S) { const vertexData = new Float32Array([ -0.4 * S, 0.5 * S, 0.0 * S, 0.4 * S, 0.5 * S, 0.0 * S, -0.2 * S, -0.5 * S, 0.0 * S, 0.2 * S, -0.5 * S, 0.0 * S, ]); this.device.queue.writeBuffer(this.vertexBuffer, 0, vertexData); } recreateVertexDataRND(S) { const vertexData = new Float32Array([ -randomFloatFromTo(0.1, 0.8) * S, randomFloatFromTo(0.4, 0.6) * S, 0.0 * S, randomFloatFromTo(0.1, 0.8) * S, randomFloatFromTo(0.4, 0.6) * S, 0.0 * S, -randomFloatFromTo(0.1, 0.4) * S, -randomFloatFromTo(0.4, 0.6) * S, 0.0 * S, randomFloatFromTo(0.1, 0.4) * S, -randomFloatFromTo(0.4, 0.6) * S, 0.0 * S, ]); this.device.queue.writeBuffer(this.vertexBuffer, 0, vertexData); } _initPipeline() { const S = 5; const vertexData = new Float32Array([ -0.2 * S, -0.5 * S, 0.0 * S, 0.2 * S, -0.5 * S, 0.0 * S, -0.4 * S, 0.5 * S, 0.0 * S, 0.4 * S, 0.5 * S, 0.0 * S, ]); const uvData = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]); const indexData = new Uint16Array([0, 2, 1, 1, 2, 3]); this.vertexBuffer = this.device.createBuffer({ size: vertexData.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST }); this.device.queue.writeBuffer(this.vertexBuffer, 0, vertexData); this.uvBuffer = this.device.createBuffer({ size: uvData.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST }); this.device.queue.writeBuffer(this.uvBuffer, 0, uvData); this.indexBuffer = this.device.createBuffer({ size: Math.ceil(indexData.byteLength / 4) * 4, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST }); this.device.queue.writeBuffer(this.indexBuffer, 0, indexData); this.indexCount = indexData.length; // --- Uniforms this.cameraBuffer = this.device.createBuffer({size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST}); this.modelBuffer = this.device.createBuffer({ size: this.maxParticles * this.floatsPerInstance * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST }); const bindGroupLayout = this.device.createBindGroupLayout({ entries: [ {binding: 0, visibility: GPUShaderStage.VERTEX, buffer: {}}, {binding: 1, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: {type: "read-only-storage"}}, ] }); this.bindGroup = this.device.createBindGroup({ layout: bindGroupLayout, entries: [ {binding: 0, resource: {buffer: this.cameraBuffer}}, {binding: 1, resource: {buffer: this.modelBuffer}}, ] }); const shaderModule = this.device.createShaderModule({code: flameEffectInstance}); const pipelineLayout = this.device.createPipelineLayout({bindGroupLayouts: [bindGroupLayout]}); this.pipeline = this.device.createRenderPipeline({ layout: pipelineLayout, vertex: { module: shaderModule, entryPoint: "vsMain", buffers: [ {arrayStride: 3 * 4, attributes: [{shaderLocation: 0, offset: 0, format: "float32x3"}]}, {arrayStride: 2 * 4, attributes: [{shaderLocation: 1, offset: 0, format: "float32x2"}]} ] }, fragment: { module: shaderModule, entryPoint: "fsMain", targets: [{format: this.format}] }, primitive: {topology: "triangle-list"}, depthStencil: {depthWriteEnabled: false, depthCompare: "less", format: "depth24plus"}, blend: { // color: {srcFactor: "src-alpha", dstFactor: "one", operation: "add"}, // alpha: {srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add"} color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add', }, alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add', }, } }); } updateInstanceData = (baseModelMatrix) => { const count = Math.min(this.instanceTargets.length, this.maxParticles); for(let i = 0;i < count;i++) { const t = this.instanceTargets[i]; // Smooth interpolation for(let j = 0;j < 3;j++) { t.currentPosition[j] += (t.position[j] - t.currentPosition[j]) * 0.12; t.currentScale[j] += (t.scale[j] - t.currentScale[j]) * 0.12; } // Build local matrix: translate → rotate → scale const local = mat4.identity(); mat4.translate(local, t.currentPosition, local); mat4.rotateY(local, t.rotation, local); mat4.scale(local, t.currentScale, local); const finalMat = mat4.identity(); mat4.multiply(baseModelMatrix, local, finalMat); const offset = i * this.floatsPerInstance; this.instanceData.set(finalMat, offset); // 0..15 this.instanceData.set([t.time, 0, 0, 0], offset + 16); // 16..19 this.instanceData.set([t.intensity, 0, 0, 0], offset + 20); // 20..23 this.instanceData.set([t.color[0], t.color[1], t.color[2], t.color[3] ?? 1.0], offset + 24); // 24..27 } this.device.queue.writeBuffer( this.modelBuffer, 0, this.instanceData.subarray(0, count * this.floatsPerInstance) ); } render(pass, mesh, viewProjMatrix, dt = 0.1) { // update global time this.time += dt; for(const p of this.instanceTargets) { p.position[this.swap1] += dt * p.riseSpeed; // Reset if too high if(p.position[this.swap1] > this.maxY) { p.position[this.swap1] = this.minY + Math.random() * 0.5; p.position[this.swap0] = (Math.random() - 0.5) * 0.2; p.position[this.swap2] = (Math.random() - 0.5) * 0.2 + 0.1; p.riseSpeed = 0.2 + Math.random() * 1.0; } p.scale[0] = p.scale[1] = this.smoothFlickeringScale + Math.sin(this.time * 2.0 + p.position[this.swap1]) * 0.1; p.rotation += dt * randomIntFromTo(3, 15); } this.device.queue.writeBuffer(this.cameraBuffer, 0, viewProjMatrix); pass.setPipeline(this.pipeline); pass.setBindGroup(0, this.bindGroup); pass.setVertexBuffer(0, this.vertexBuffer); pass.setVertexBuffer(1, this.uvBuffer); pass.setIndexBuffer(this.indexBuffer, "uint16"); pass.drawIndexed(this.indexCount, this.instanceTargets.length); } setIntensity(v) {this.intensity = v;} }