WebGPU는 최첨단 GPU 컴퓨팅 기능을 웹에 도입하여 공유 코드 기반을 사용하는 모든 소비자 플랫폼에 혜택을 제공하는 글로벌 기술입니다.
이전 버전인 WebGL은 강력하지만 컴퓨팅 셰이더 기능이 심각하게 부족하여 적용 범위가 제한됩니다.
WGSL(WebGPU 셰이더/컴퓨팅 언어)은 Rust 및 GLSL과 같은 분야의 모범 사례를 활용합니다.
WebGPU 사용법을 배우면서 문서에서 몇 가지 공백을 발견했습니다. 컴퓨팅 셰이더를 사용하여 버텍스 및 프래그먼트 셰이더의 데이터를 계산하는 간단한 시작점을 찾고 싶었습니다.
이 튜토리얼의 모든 코드에 대한 단일 파일 HTML은 https://www.php.cn/link/2e5281ee978b78d6f5728aad8f28fedb에서 찾을 수 있습니다. 자세한 내용은 계속 읽어보세요.
다음은 내 도메인에서 실행되는 이 HTML에 대한 한 번의 클릭 데모입니다. https://www.php.cn/link/bed827b4857bf056d05980661990ccdc Chrome 또는 Edge와 같은 WebGPU 기반 브라우저 https://www.php.cn/link/bae00fb8b4115786ba5dbbb67b9b177a).
이것은 입자 시뮬레이션입니다. 시간이 지남에 따라 시간 단계에 따라 발생합니다.
시간은 JS/CPU에서 추적되어 (부동) 유니폼으로 GPU에 전달됩니다.
입자 데이터는 전적으로 GPU에서 관리됩니다. CPU와 계속 상호작용하면서도 메모리를 할당하고 초기 값을 설정할 수 있습니다. 데이터를 다시 CPU로 읽는 것도 가능하지만 이 튜토리얼에서는 생략합니다.
이 설정의 마법은 각 입자가 다른 모든 입자와 병렬로 업데이트되어 브라우저에서 놀라운 계산 및 렌더링 속도를 가능하게 한다는 것입니다(병렬화는 GPU의 코어 수를 최대화합니다. 입자 수를 다음과 같이 나눌 수 있습니다). 코어당 업데이트 단계당 실제 주기 수를 얻기 위한 코어 수).
CPU와 GPU 간의 데이터 교환을 위해 WebGPU가 사용하는 메커니즘은 바인딩입니다. JS 배열(예: Float32Array)은 WebGPU 버퍼를 사용하여 WGSL의 메모리 위치에 "바인딩"될 수 있습니다. WGSL 메모리 위치는 두 개의 정수, 즉 그룹 번호와 바인딩 번호로 식별됩니다.
우리의 경우 컴퓨팅 셰이더와 버텍스 셰이더는 모두 시간과 입자 위치라는 두 가지 데이터 바인딩에 의존합니다.
컴퓨팅 셰이더(https://www.php.cn/link/2e5281ee978b78d6f5728aad8f28fedb#L43) 및 버텍스 셰이더에 균일한 정의가 존재합니다. (https://www.php.cn/link/2e5281ee978b78d6f5728aad8f28fedb#L69) 중간 - 셰이더 업데이트 위치를 계산하고, 버텍스 셰이더는 시간에 따라 색상을 업데이트합니다.
컴퓨팅 셰이더부터 시작하여 JS 및 WGSL의 바인딩 설정을 살펴보겠습니다.
<code>const computeBindGroup = device.createBindGroup({ /* 参见 computePipeline 定义,网址为 https://www.php.cn/link/2e5281ee978b78d6f5728aad8f28fedb#L102 它允许将 JS 字符串与 WGSL 代码链接到 WebGPU */ layout: computePipeline.getBindGroupLayout(0), // 组号 0 entries: [{ // 时间绑定在绑定号 0 binding: 0, resource: { /* 作为参考,缓冲区声明为: const timeBuffer = device.createBuffer({ size: Float32Array.BYTES_PER_ELEMENT, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST}) }) https://www.php.cn/link/2e5281ee978b78d6f5728aad8f28fedb#L129 */ buffer: timeBuffer } }, { // 粒子位置数据在绑定号 1(仍在组 0) binding: 1, resource: { buffer: particleBuffer } }] });</code>
및 컴퓨팅 셰이더의 해당 선언
<code>// 来自计算着色器 - 顶点着色器中也有类似的声明 @group(0) @binding(0) var<uniform> t: f32; @group(0) @binding(1) var<storage read_write=""> particles : array<particle>; </particle></storage></uniform></code>
중요하게, JS와 WGSL의 그룹 번호와 바인딩 번호를 일치시켜 JS 측의 timeBuffer를 WGSL에 바인딩합니다.
이를 통해 JS에서 변수 값을 제어할 수 있습니다.
<code>/* 数组中只需要 1 个元素,因为时间是单个浮点值 */ const timeJs = new Float32Array(1) let t = 5.3 /* 纯 JS,只需设置值 */ timeJs.set([t], 0) /* 将数据从 CPU/JS 传递到 GPU/WGSL */ device.queue.writeBuffer(timeBuffer, 0, timeJs);</code>
우리는 GPU 액세스 가능 메모리에 입자 위치를 직접 저장하고 업데이트하므로 GPU의 대규모 멀티 코어 아키텍처를 활용하여 병렬로 업데이트할 수 있습니다.
병렬화는 컴퓨팅 셰이더에 선언된 작업 그룹 크기의 도움으로 조정됩니다.
<code>@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) global_id : vec3<u32>) { // ... } </u32></code>
@builtin(global_invocation_id) global_id : vec3
정의에 따르면 global_invocation_id = workgroup_id * workgroup_size local_invocation_id - 이는 입자 인덱스로 사용할 수 있음을 의미합니다.
예를 들어 10,000개의 파티클이 있고 workgroup_size가 64인 경우 Math.ceil(10000/64) 작업 그룹을 예약해야 합니다. JS에서 컴퓨팅 패스가 트리거될 때마다 GPU에 이 작업량을 수행하도록 명시적으로 지시합니다.
<code>computePass.dispatchWorkgroups(Math.ceil(PARTICLE_COUNT / WORKGROUP_SIZE));</code>
PARTICLE_COUNT == 10000이고 WORKGROUP_SIZE == 64인 경우 157개의 작업 그룹(10000/64 = 156.25)을 시작하고 각 작업 그룹의 계산된 local_invocation_id 범위는 0~63입니다(workgroup_id의 범위는 0~157입니다). ). 157 * 64 = 1048이므로 작업 그룹에서 약간 더 많은 계산을 수행하게 됩니다. 중복된 호출을 폐기하여 오버플로를 처리합니다.
다음 요소를 고려한 후 셰이더를 계산한 최종 결과는 다음과 같습니다.
<code>@compute @workgroup_size(${WORKGROUP_SIZE}) fn main(@builtin(global_invocation_id) global_id : vec3<u32>) { let index = global_id.x; // 由于工作组网格未对齐,因此丢弃额外的计算 if (index >= arrayLength(&particles)) { return; } /* 将整数索引转换为浮点数,以便我们可以根据索引(和时间)计算位置更新 */ let fi = f32(index); particles[index].position = vec2<f32>( /* 公式背后没有宏伟的意图 - 只不过是用时间+索引的例子 */ cos(fi * 0.11) * 0.8 + sin((t + fi)/100)/10, sin(fi * 0.11) * 0.8 + cos((t + fi)/100)/10 ); } </f32></u32></code>
입자가 저장 변수로 정의되기 때문에 이러한 값은 계산 단계 전반에 걸쳐 유지됩니다.
컴퓨팅 셰이더에서 정점 셰이더의 입자 위치를 읽으려면 읽기 전용 뷰가 필요합니다. 컴퓨팅 셰이더만 스토리지에 쓸 수 있기 때문입니다.
다음은 WGSL의 입장문 전문입니다.
<code>@group(0) @binding(0) var<uniform> t: f32; @group(0) @binding(1) var<storage> particles : array<vec2>>; /* 或等效: @group(0) @binding(1) var<storage read=""> particles : array<vec2>>; */ </vec2></storage></vec2></storage></uniform></code>
컴퓨팅 셰이더에서 동일한 읽기_쓰기 스타일을 재사용하려고 하면 오류가 발생합니다.
<code>var with 'storage' address space and 'read_write' access mode cannot be used by vertex pipeline stage</code>
정점 셰이더의 바인딩 번호는 컴퓨팅 셰이더 바인딩 번호와 일치할 필요는 없으며 꼭지점 셰이더의 바인딩 그룹 선언과만 일치하면 됩니다.
<code>const renderBindGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: timeBuffer } }, { binding: 1, resource: { buffer: particleBuffer } }] });</code>
GitHub 샘플 코드에서 바인딩:2를 선택했습니다. https://www.php.cn/link/2e5281ee978b78d6f5728aad8f28fedb#L70 - WebGPU에 의해 부과된 제약의 경계를 탐색하기 위해
모든 설정이 완료되면 업데이트 및 렌더링 루프가 JS에서 조정됩니다.
<code>/* 从 t = 0 开始模拟 */ let t = 0 function frame() { /* 为简单起见,使用恒定整数时间步 - 无论帧速率如何,都会一致渲染。 */ t += 1 timeJs.set([t], 0) device.queue.writeBuffer(timeBuffer, 0, timeJs); // 计算传递以更新粒子位置 const computePassEncoder = device.createCommandEncoder(); const computePass = computePassEncoder.beginComputePass(); computePass.setPipeline(computePipeline); computePass.setBindGroup(0, computeBindGroup); // 重要的是要调度正确数量的工作组以处理所有粒子 computePass.dispatchWorkgroups(Math.ceil(PARTICLE_COUNT / WORKGROUP_SIZE)); computePass.end(); device.queue.submit([computePassEncoder.finish()]); // 渲染传递 const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, loadOp: 'clear', storeOp: 'store', }] }); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, renderBindGroup); passEncoder.draw(PARTICLE_COUNT); passEncoder.end(); device.queue.submit([commandEncoder.finish()]); requestAnimationFrame(frame); } frame();</code>
WebGPU는 브라우저에서 대규모 병렬 GPU 컴퓨팅의 성능을 발휘합니다.
패스로 실행됩니다. 각 패스에는 메모리 바인딩(CPU 메모리와 GPU 메모리 브리징)이 있는 파이프라인을 통해 활성화된 로컬 변수가 있습니다.
컴퓨팅 딜리버리를 사용하면 작업 그룹을 통해 병렬 워크로드를 조정할 수 있습니다.
일부 무거운 설정이 필요하기는 하지만 로컬 바인딩/상태 스타일은 WebGL의 전역 상태 모델에 비해 크게 개선되었다고 생각합니다. 사용이 더 쉬워지는 동시에 GPU 컴퓨팅의 성능을 웹에 도입하는 것이기도 합니다.
위 내용은 WebGPU 튜토리얼: 웹의 컴퓨팅, 정점 및 조각 셰이더의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!