实现原理: 基本PBR光照与gpu compute计算
尽量用数据化/语义化的松散描述数据的方式来呈现相关对象逻辑
当前示例源码github地址:
https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLife3DPBR.ts
当前示例运行效果:
其他效果截图:
此示例基于此渲染系统实现,当前示例TypeScript源码如下:
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 uniformsconst 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 uniformsconst 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",}} as WGRShderSrcType;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}).setWorkcounts(workgroupCount, workgroupCount);}private initScene(): void {const rc = this.mRscene;const ufvsObjs = this.createUniformValues();// build ping-pong material rendering/computing processconst 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();}
}