Implementation principle: basic PBR lighting and gpu compute calculation
Try to use a data-based/semantic loose description of data to present relevant object logic.
Current sample source code github address:
https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLife3DPBR.ts
Current sample running effect:
Other effect screenshots:
This example is implemented based on this rendering system. The current example TypeScript source code is as follows:
const gridSize = 256; const shdWorkGroupSize = 8; const compShdCode = ` @group(0) @binding(0) var<uniform> grid: vec2f; @group(0) @binding(1) var<storage> cellStateIn: array<u32>; @group(0) @binding(2) var<storage, read_write> cellStateOut: array<u32>; @group(0) @binding(3) var<storage, read_write> lifeState: array<f32>; fn cellIndex(cell: vec2u) -> u32 { return (cell.y % u32(grid.y)) * u32(grid.x) + (cell.x % u32(grid.x)); } fn cellActive(x: u32, y: u32) -> u32 { return cellStateIn[cellIndex(vec2(x, y))]; } @compute @workgroup_size(${shdWorkGroupSize}, ${shdWorkGroupSize}) fn compMain(@builtin(global_invocation_id) cell: vec3u) { // Determine how many active neighbors this cell has. let activeNeighbors = cellActive(cell.x + 1, cell.y + 1) + cellActive(cell.x + 1, cell.y) + cellActive(cell.x + 1, cell.y-1) + cellActive(cell.x, cell.y-1) + cellActive(cell.x-1, cell.y-1) + cellActive(cell.x-1, cell.y) + cellActive(cell.x-1, cell.y + 1) + cellActive(cell.x, cell.y + 1); let i = cellIndex(cell.xy); // Conway's game of life rules: switch activeNeighbors { case 2: { // Active cells with 2 neighbors stay active. cellStateOut[i] = cellStateIn[i]; if(cellStateOut[i] > 0) { lifeState[i] + = 0.05; } else { lifeState[i] -= 0.05; } } case 3: { // Cells with 3 neighbors become or stay active. cellStateOut[i] = 1; lifeState[i] + = 0.1; } default: { // Cells with < 2 or > 3 neighbors become inactive. cellStateOut[i] = 0; lifeState[i] -= 0.05; } } lifeState[i] = max(lifeState[i], 0.01); }`; export class GameOfLife3DPBR { private mRscene = new RendererScene(); initialize(): void { console.log("GameOfLife3DPBR::initialize() ..."); this.initEvent(); this.initScene(); } private initEvent(): void { const rc = this.mRscene; rc.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDown); new RenderStatusDisplay(this.mRscene, true); new MouseInteraction().initialize(rc, 0, false).setAutoRunning(true); } private mouseDown = (evt: MouseEvent): void => {} private createUniformValues(): { ufvs0: WGRBufferData[], ufvs1: WGRBufferData[] }[] { const gridsSizesArray = new Float32Array([gridSize, gridSize]); const cellStateArray0 = new Uint32Array(gridSize * gridSize); for (let i = 0; i < cellStateArray0.length; i + + ) { cellStateArray0[i] = Math.random() > 0.6 ? 1 : 0; } const cellStateArray1 = new Uint32Array(gridSize * gridSize); for (let i = 0; i < cellStateArray1.length; i + + ) { cellStateArray1[i] = i % 2; } const lifeStateArray3 = new Float32Array(gridSize * gridSize); for (let i = 0; i < lifeStateArray3.length; i + + ) { lifeStateArray3[i] = 0.01; } const posisitonArray4 = new Float32Array(gridSize * gridSize * 4); let sizeV = new Vector3(40, 1, 40); let posV = new Vector3().copyFrom(sizeV); posV.scaleBy(gridSize); posV.scaleBy(-0.5); let k = 0; for (let i = 0; i < gridSize; i + + ) { for (let j = 0; j < gridSize; j + + ) { let pv = new Vector3(j * sizeV.x, 0, i * sizeV.z).addBy(posV); posisitonArray4[k] = pv.x; posisitonArray4[k + 1] = pv.y; posisitonArray4[k + 2] = pv.z; k + = 4; } } let shared = true; let sharedData0 = { data: cellStateArray0, shared }; let sharedData1 = { data: cellStateArray1, shared }; let sharedData3 = { data: lifeStateArray3, shared }; let sharedData4 = { data: posisitonArray4, shared }; const v0 = { data: gridsSizesArray, stride: 2, shared, layout: { visibility: 'all' } }; // build rendering uniforms const va1 = {storage: { bufData: sharedData0, stride: 1, shared }, layout: { visibility: 'vert_comp' }}; const vb1 = {storage: { bufData: sharedData1, stride: 1, shared }, layout: { visibility: 'vert_comp' }}; const vc1 = {storage: { bufData: sharedData3, stride: 1, shared, layout: { visibility: 'all' } }}; const v4 = {storage: { bufData: sharedData4, stride: 3, shared, layout: { visibility: 'vert_comp' } }}; // build computing uniforms const compva1 = {storage: { bufData: sharedData0, stride: 1, shared, layout: { visibility: 'vert_comp' } }}; const compva2 = {storage: { bufData: sharedData1, stride: 1, shared, layout: { visibility: 'comp' } }}; const compvb1 = {storage: { bufData: sharedData1, stride: 1, shared, layout: { visibility: 'vert_comp' } }}; const compvb2 = {storage: { bufData: sharedData0, stride: 1, shared, layout: { visibility: 'comp', access: "read_write" } }}; const compv3 = {storage: { bufData: sharedData3, stride: 1, shared, layout: { visibility: 'comp', access: "read_write" } }}; return [ { ufvs0: [v0, va1, vc1, v4], ufvs1: [v0, vb1, vc1, v4] }, { ufvs0: [v0, compva1, compva2, compv3], ufvs1: [v0, compvb1, compvb2, compv3] } ]; } private mEntity: Entity3D; private mStep = 0; private createMaterial(uniformValues: WGRBufferData[]): WGMaterial { const instanceCount = gridSize * gridSize; let shaderCodeSrc = { vert: { code: vertWGSL, uuid: "vert-gameOfLife" }, frag: { code: fragWGSL, uuid: "frag-gameOfLife" } }; return new WGMaterial({ shadinguuid: 'rendering', shaderCodeSrc, instanceCount, uniformValues, uniformAppend: false }); } private createCompMaterial(uniformValues: WGRBufferData[]): WGCompMaterial { const workgroupCount = Math.ceil(gridSize / shdWorkGroupSize); let shaderCodeSrc = { code: compShdCode, uuid: "shader-computing" }; return new WGCompMaterial({ shadinguuid: 'computing', shaderCodeSrc, uniformValues, uniformAppend: false, workcounts:[workgroupCount, workgroupCount] }); } private initScene(): void { const rc = this.mRscene; const ufvsObjs = this.createUniformValues(); // build ping-pong material rendering/computing process const materials: WGMaterial[] = [ this.createMaterial(ufvsObjs[0].ufvs0), this.createMaterial(ufvsObjs[0].ufvs1), this.createCompMaterial(ufvsObjs[1].ufvs1), this.createCompMaterial(ufvsObjs[1].ufvs0), ]; let entity = new CylinderEntity({ radius: 20, height: 38, longitudeNumSegments: 10, latitudeNumSegments: 10, alignYRatio : 0.0, materials }); rc.addEntity(entity); this.mEntity = entity; } private mFrameDelay = 3; run(): void { let flag = this.mEntity.isRendering(); const ms = this.mEntity.materials; if (flag) { for (let i = 0; i < ms.length; i + + ) { ms[i].visible = (this.mStep % 2 + i) % 2 == 0; } if (this.mFrameDelay > 0) { this.mFrameDelay--; flag = false; }else { this.mFrameDelay = 3; this.mStep + + ; } } if(!flag) { ms[2].visible = false; ms[3].visible = false; } this.mRscene.run(); } }