轻量封装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();
		}
	}
}
相关推荐
AI_RSER8 小时前
第一篇:【Python-geemap教程(三)上】3D地形渲染与Landsat NDVI计算
开发语言·python·3d·信息可视化·遥感·gee
-dzk-8 小时前
【论文精读】3D Gaussian Splatting for Real-Time Radiance Field Rendering
数码相机·opencv·计算机视觉·3d·三维重建·3dgs·高斯
VSORender_Farm8 小时前
3ds Max:建筑效果图制作的核心利器与全流程解析
3d·渲染·云渲染·设计·建筑·效果图
格调UI成品17 小时前
[特殊字符] 数据可视化结合 three.js:让 3D 呈现更精准,3 个优化经验谈
javascript·3d·信息可视化
The moon forgets18 小时前
ALOcc: Adaptive Lifting-based 3D Semantic Occupancy and
深度学习·3d
向宇it2 天前
【unity小技巧】封装unity适合2D3D进行鼠标射线检测,获取鼠标位置信息检测工具类
游戏·3d·unity·游戏引擎
中科米堆2 天前
中科米堆CASAIM金属件自动3d测量外观尺寸三维检测解决方案
人工智能·3d·视觉检测
山楂树の2 天前
模型优化——在MacOS 上使用 Python 脚本批量大幅度精简 GLB 模型(通过 Blender 处理)
python·macos·3d·图形渲染·blender
Blossom.1183 天前
基于深度学习的医学图像分析:使用BERT实现医学文本分类
人工智能·深度学习·机器学习·3d·分类·cnn·bert
dllmayday3 天前
3D空间中的变换矩阵
线性代数·3d·矩阵