轻量封装WebGPU渲染系统示例<21>- 3D呈现元胞自动机之生命游戏(源码)

82 篇文章 4 订阅
75 篇文章 3 订阅

实现原理: 基本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 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();
	}
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值