轻量封装WebGPU渲染系统示例<17>- 使用GPU Compute之元胞自动机(源码)

当前示例源码github地址:

https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifeTest.ts

系统特性:

  1. 用户态与系统态隔离。

  2. 高频调用与低频调用隔离。

  3. 面向用户的易用性封装。

  4. 渲染数据(内外部相关资源)和渲染机制分离。

  5. 用户操作和渲染系统调度并行机制。

  6. 数据/语义驱动。

  7. 异步并行的场景/模型载入。

  8. computing与rendering用法机制一致性。

1). 构造过程一致性。

2). 启用过程一致性。

3). 自动兼容到material多pass以及material graph机制中。

当前示例运行效果:

WGSL顶点与片段shader:

javascript 复制代码
struct VertexInput {
	@location(0) pos: vec3f,
	@builtin(instance_index) instance: u32,
};

struct VertexOutput {
	@builtin(position) pos: vec4f,
	@location(0) cell: vec2f,
};
@group(0) @binding(0) var<uniform> grid: vec2f;
@group(0) @binding(1) var<storage> cellState: array<u32>;
@vertex
fn vertMain(input: VertexInput) -> VertexOutput {
    let i = f32(input.instance);
    let cell = vec2f(i % grid.x, floor(i / grid.x));
    let cellOffset = cell / grid * 2.0;

    var state = f32(cellState[input.instance]);
    let gridPos = (input.pos.xy * state + 1.0) / grid - 1.0 + cellOffset;

    var output: VertexOutput;
    output.pos = vec4f(gridPos, 0.0, 1.0);
    output.cell = cell;
    return output;
}

@fragment
fn fragMain(input: VertexOutput) -> @location(0) vec4f {
    let c = input.cell / grid;
    return vec4f(c, 1.0 - c.x, 1.0);
}

此示例基于此渲染系统实现,当前示例TypeScript源码如下:

javascript 复制代码
type NodeType = { rendEntity: FixScreenPlaneEntity; compEntity?: ComputeEntity };

const gridSize = 64;
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>;

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];
		}
		case 3: { // Cells with 3 neighbors become or stay active.
			cellStateOut[i] = 1;
		}
		default: { // Cells with < 2 or > 3 neighbors become inactive.
			cellStateOut[i] = 0;
		}
	}
}`;
export class GameOfLifeTest {
	private mRscene = new RendererScene();

	initialize(): void {
		console.log("GameOfLifeTest::initialize() ...");

		const rc = this.mRscene;
		rc.initialize();
		this.initEvent();
		this.initScene();
	}
	private mFlag = 6;
	private initEvent(): void {
		const rc = this.mRscene;
		rc.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDown);
		new MouseInteraction().initialize(rc, 0, false).setAutoRunning(true);
	}

	private mouseDown = (evt: MouseEvent): void => {
		this.mFlag = 1;
	};
	private createUniformValues(): { ufvs0: WGRUniformValue[]; ufvs1: WGRUniformValue[] }[] {
		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;
		}

		let shared = true;
		let sharedData0 = { data: cellStateArray0 };
		let sharedData1 = { data: cellStateArray1 };

		const v0 = new WGRUniformValue({ data: gridsSizesArray, stride: 2, shared });
		v0.toVisibleAll();

		// build rendering uniforms
		const va1 = new WGRStorageValue({ sharedData: sharedData0, stride: 1, shared }).toVisibleVertComp();
		const vb1 = new WGRStorageValue({ sharedData: sharedData1, stride: 1, shared }).toVisibleVertComp();

		// build computing uniforms
		const compva1 = new WGRStorageValue({ sharedData: sharedData0, stride: 1, shared }).toVisibleVertComp();
		const compva2 = new WGRStorageValue({ sharedData: sharedData1, stride: 1, shared }).toVisibleComp();
		compva2.toBufferForStorage();
		const compvb1 = new WGRStorageValue({ sharedData: sharedData1, stride: 1, shared }).toVisibleVertComp();
		const compvb2 = new WGRStorageValue({ sharedData: sharedData0, stride: 1, shared }).toVisibleComp();
		compvb2.toBufferForStorage();

		let objs = [
			{ ufvs0: [v0, va1], ufvs1: [v0, vb1] },
			{ ufvs0: [v0, compva1, compva2], ufvs1: [v0, compvb1, compvb2] }
		];
		return objs;
	}
	private mNodes: NodeType[] = [];
	private mStep = 0;
	private initScene(): void {
		const rc = this.mRscene;

		let ufvsObjs = this.createUniformValues();

		// build ping-pong rendering process
		let shaderSrc = {
			shaderSrc: {
				code: shaderWGSL,
				uuid: "shader-gameOfLife",
				vertEntryPoint: "vertMain",
				fragEntryPoint: "fragMain"
			}
		} as WGRShderSrcType;
		let instanceCount = gridSize * gridSize;
		let uniformValues = ufvsObjs[0].ufvs0;
		let entity = new FixScreenPlaneEntity({
			x: -0.8, y: -0.8, width: 1.6, height: 1.6,
			shadinguuid: "rshd0", shaderSrc, uniformValues, instanceCount
		});
		rc.addEntity(entity);
		this.mNodes = [{ rendEntity: entity, compEntity: null }];
		entity.rstate.visible = false;
		const geometry = this.mNodes[0].rendEntity.geometry;
		uniformValues = ufvsObjs[0].ufvs1;
		entity = new FixScreenPlaneEntity({ shadinguuid: "rshd1", shaderSrc, uniformValues, instanceCount, geometry });
		rc.addEntity(entity);
		this.mNodes.push({ rendEntity: entity, compEntity: null });

		// build ping-pong computing process
		shaderSrc = {
			compShaderSrc: {
				code: compShdCode,
				uuid: "shader-computing",
				compEntryPoint: "compMain"
			}
		};

		const workgroupCount = Math.ceil(gridSize / shdWorkGroupSize);
		uniformValues = ufvsObjs[1].ufvs1;
		let compEentity = new ComputeEntity({ shadinguuid: "compshd0", shaderSrc, uniformValues }).setWorkcounts(workgroupCount, workgroupCount);
		rc.addEntity(compEentity);
		compEentity.rstate.visible = false;
		this.mNodes[0].compEntity = compEentity;
		uniformValues = ufvsObjs[1].ufvs0;
		compEentity = new ComputeEntity({ shadinguuid: "compshd1", shaderSrc, uniformValues }).setWorkcounts(workgroupCount, workgroupCount);
		rc.addEntity(compEentity);
		this.mNodes[1].compEntity = compEentity;
	}
	private mFrameDelay = 3;
	run(): void {
		if (this.mRscene.renderer.isEnabled()) {
			if (this.mFrameDelay > 0) {
				this.mFrameDelay--;
				return;
			}
			this.mFrameDelay = 3;

			for (let i = 0; i < this.mNodes.length; i++) {
				const t = this.mNodes[i];
				t.rendEntity.setVisible(false);
				if (t.compEntity) t.compEntity.setVisible(false);
			}
			let index = this.mStep % 2;
			this.mNodes[index].rendEntity.setVisible(true);
			if (this.mNodes[index].compEntity) this.mNodes[index].compEntity.setVisible(true);
			this.mStep++;

			this.mRscene.run();
		}
	}
}
相关推荐
Avicli14 小时前
Gemini3 生成的基于手势控制3D粒子圣诞树
前端·javascript·3d
二狗哈15 小时前
Cesium快速入门15:图元Primitive创建图像物体
开发语言·javascript·3d·webgl·cesium·地图可视化
zl_vslam16 小时前
SLAM中的非线性优-3D图优化之相对位姿Between Factor右扰动(八)
人工智能·算法·计算机视觉·3d
小白狮ww18 小时前
挥手点亮圣诞:AI 3D 魔法树教程
人工智能·深度学习·机器学习·3d·音视频·图片处理·动作识别
姓林君19 小时前
ZW3D二次开发_获取造型的投影面积及最大长宽
3d
GIS遥遥19 小时前
2025cesium进阶教程(6)| webgis智慧城市开发,3DTiles 卷帘对比效果(附完整源码)
microsoft·3d·智慧城市·cesium·gis开发·webgis开发
NueXini19 小时前
Unity 3D MMO RPG手游征服2GB设备之历程
3d·unity·性能优化·游戏引擎·优化·rpg·mmo
元境1 天前
“3D数字内容创新发展高峰论坛”在京成功举办,多项重要合作与课题启动
3d
song5011 天前
鸿蒙 Flutter 图像识别进阶:物体分类与花卉识别(含离线模型)
人工智能·分布式·python·flutter·3d·华为·分类
中维ZWPD2 天前
工程行业数智化转型:挑战与破局之路
大数据·人工智能·科技·物联网·3d