轻量封装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();
		}
	}
}
相关推荐
道可云1 小时前
道可云人工智能&元宇宙每日资讯|2024国际虚拟现实创新大会将在青岛举办
大数据·人工智能·3d·机器人·ar·vr
Sitarrrr4 小时前
【Unity】ScriptableObject的应用和3D物体跟随鼠标移动:鼠标放置物体在场景中
3d·unity
starsongda18 小时前
VR科技展厅重塑科技展示新风貌,引领未来展示潮流
科技·3d·vr
兔老大的胡萝卜19 小时前
threejs 数字孪生,制作3d炫酷网页
前端·3d
CV-X.WANG1 天前
【详细 工程向】基于Smart3D的五镜头相机三维重建
数码相机·3d
JoeyKo1 天前
国内版Sketchfab平台 - CG美术之家(3D编辑发布篇)
3d·3d建模·3dsmax·3d渲染·模型·3d模型·cg模型
mirrornan1 天前
3D互动+AR试戴,重塑线上珠宝营销新体验!
3d·ar
DisonTangor2 天前
腾讯混元3D-1.0:文本到三维和图像到三维生成的统一框架
人工智能·3d·aigc
坠金3 天前
【3D】基础概念
3d
小白菜学前端3 天前
ThreeJS创建一个3D物体的基本流程
3d·three.js