이 시리즈에서는 WebGPU와 컴퓨터 그래픽 전반에 대해 소개합니다.
먼저 우리가 무엇을 만들 것인지 살펴보겠습니다.
인생게임
3D 렌더링
3D 렌더링, 조명 포함
3D 모델 렌더링
JS에 대한 기본 지식 외에는 사전 지식이 필요하지 않습니다.
소스 코드와 함께 내 github에서 튜토리얼이 이미 완료되었습니다.
WebGPU는 비교적 새로운 GPU용 API입니다. WebGPU로 명명되었지만 실제로는 Vulkan, DirectX 12, Metal, OpenGL 및 WebGL 위에 있는 레이어로 간주될 수 있습니다. 로우레벨 API로 설계되었으며, 게임, 시뮬레이션 등 고성능 애플리케이션에 사용하도록 제작되었습니다.
이번 장에서는 화면에 무언가를 그려보겠습니다. 첫 번째 부분에서는 Google Codelabs 튜토리얼을 참조합니다. 화면 속 인생 게임을 만들어 드립니다.
typescript가 활성화된 vite에서 빈 바닐라 JS 프로젝트를 생성하겠습니다. 그런 다음 main.ts만 남기고 모든 추가 코드를 지웁니다.
const main = async () => { console.log('Hello, world!') } main()
실제 코딩하기 전에 브라우저에 WebGPU가 활성화되어 있는지 확인하세요. WebGPU 샘플에서 확인하실 수 있습니다.
Chrome은 이제 기본적으로 활성화되어 있습니다. Safari에서는 개발자 설정으로 이동하여 설정에 플래그를 지정하고 WebGPU를 활성화해야 합니다.
또한 WebGPU에 대해 세 가지 유형을 활성화하고 @webgpu/types를 설치한 다음 tsc 컴파일러 옵션에 "types"를 추가해야 합니다: ["@webgpu/types"].
또한
WebGPU에는 많은 상용구 코드가 있으며 그 모양은 다음과 같습니다.
먼저 GPU에 액세스해야 합니다. WebGPU에서는 GPU와 브라우저 사이의 가교 역할을 하는 어댑터라는 개념으로 이루어집니다.
const adapter = await navigator.gpu.requestAdapter();
그런 다음 어댑터에서 기기를 요청해야 합니다.
const device = await adapter.requestDevice(); console.log(device);
캔버스에 삼각형을 그립니다. 캔버스 요소를 가져와서 구성해야 합니다.
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
여기서는 getContext를 사용하여 캔버스에 대한 관련 정보를 가져옵니다. webgpu를 지정하면 WebGPU로 렌더링을 담당하는 컨텍스트를 얻게 됩니다.
CanvasFormat은 실제로 색상 모드입니다(예: srgb). 우리는 일반적으로 선호하는 형식을 사용합니다.
마지막으로 장치와 형식으로 컨텍스트를 구성합니다.
엔지니어링 세부 사항을 자세히 알아보기 전에 먼저 GPU가 렌더링을 처리하는 방법을 이해해야 합니다.
GPU 렌더링 파이프라인은 GPU가 이미지를 렌더링하기 위해 수행하는 일련의 단계입니다.
GPU에서 실행되는 애플리케이션을 셰이더라고 합니다. 셰이더는 GPU에서 실행되는 프로그램입니다. 셰이더에는 나중에 논의할 특별한 프로그래밍 언어가 있습니다.
렌더링 파이프라인에는 다음 단계가 있습니다.
GPU가 렌더링할 수 있는 가장 작은 단위인 프리미티브에 따라 파이프라인의 단계가 다를 수 있습니다. 일반적으로 우리는 GPU에 신호를 보내 3개의 정점 그룹을 모두 삼각형으로 처리하는 삼각형을 사용합니다.
Render Pass는 전체 GPU 렌더링의 한 단계입니다. 렌더 패스가 생성되면 GPU는 장면 렌더링을 시작하고, 완료되면 그 반대의 경우도 마찬가지입니다.
렌더 패스를 생성하려면 렌더 패스를 GPU 코드로 컴파일하는 인코더를 생성해야 합니다.
const main = async () => { console.log('Hello, world!') } main()그런 다음 렌더 패스를 생성합니다.
const adapter = await navigator.gpu.requestAdapter();여기서 색상 첨부가 포함된 렌더 패스를 만듭니다. Attachment는 렌더링될 이미지를 나타내는 GPU의 개념입니다. 이미지에는 GPU가 처리해야 하는 여러 측면이 있을 수 있으며 각 측면은 첨부 파일입니다.
여기에는 하나의 첨부 파일, 즉 색상 첨부 파일만 있습니다. 뷰는 GPU가 렌더링할 패널이며, 여기서는 캔버스의 텍스처로 설정합니다.
loadOp는 렌더링 패스 전에 GPU가 수행하는 작업이고, Clear는 GPU가 먼저 마지막 프레임에서 이전 데이터를 모두 지운다는 의미이며, storeOp는 렌더링 패스 후에 GPU가 수행하는 작업이고, Store는 GPU를 의미합니다. 데이터를 텍스처에 저장합니다.
loadOp는 마지막 프레임의 데이터를 보존하는 로드 또는 마지막 프레임의 데이터를 지우는 클리어가 될 수 있습니다. storeOp는 데이터를 텍스처에 저장하는 store 또는 데이터를 버리는 폐기일 수 있습니다.
이제 pass.end()를 호출하여 렌더 패스를 종료합니다. 이제 명령이 GPU의 명령 버퍼에 저장됩니다.
컴파일된 명령을 얻으려면 다음 코드를 사용하세요.
const device = await adapter.requestDevice(); console.log(device);그리고 마지막으로 GPU의 렌더링 대기열에 명령을 제출합니다.
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });이제 흉측한 검정색 캔버스가 보일 것입니다.
3D에 대한 우리의 고정관념을 바탕으로 우리는 빈 공간이 파란색일 것으로 예상합니다. 선명한 색상을 설정하면 됩니다.
const encoder = device.createCommandEncoder();셰이더를 사용하여 삼각형 그리기
이제 캔버스에 삼각형을 그려보겠습니다. 이를 위해 셰이더를 사용할 것입니다. 셰이더 언어는 wgsl, WebGPU Shading Language입니다.
이제 다음 좌표로 삼각형을 그리고 싶다고 가정해 보겠습니다.
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", storeOp: "store", }] });앞서 언급했듯이 렌더 파이프라인을 완성하려면 버텍스 셰이더와 프래그먼트 셰이더가 필요합니다.
버텍스 셰이더
셰이더 모듈을 생성하려면 다음 코드를 사용하세요.
const commandBuffer = encoder.finish();여기서 레이블은 단순히 디버깅을 위한 이름입니다. code는 실제 셰이더 코드입니다.
정점 셰이더는 임의의 매개변수를 사용하여 정점의 위치를 반환하는 함수입니다. 그러나 우리가 예상하는 것과는 달리 정점 셰이더는 3차원 벡터가 아닌 4차원 벡터를 반환합니다. 네 번째 차원은 관점 분할에 사용되는 w 차원입니다. 나중에 논의하겠습니다.
이제 간단히 4차원 벡터(x, y, z, w)를 3차원 벡터(x/w, y/w, z/w)로 간주하면 됩니다.
그러나 또 다른 문제가 있습니다. 데이터를 셰이더에 전달하는 방법과 셰이더에서 데이터를 가져오는 방법입니다.
데이터를 셰이더에 전달하기 위해 정점 데이터가 포함된 버퍼인 vertexBuffer를 사용합니다. 다음 코드를 사용하여 버퍼를 생성할 수 있습니다.
const main = async () => { console.log('Hello, world!') } main()여기서 정점 크기인 24바이트, 6개의 부동 소수점 크기의 버퍼를 생성합니다.
usage는 버텍스 데이터용 VERTEX인 버퍼의 사용량입니다. GPUBufferUsage.COPY_DST는 이 버퍼가 복사 대상으로 유효함을 의미합니다. CPU에서 데이터를 쓰는 모든 버퍼에 대해 이 플래그를 설정해야 합니다.
여기서 맵이란 버퍼를 CPU에 매핑한다는 의미로, CPU가 버퍼를 읽고 쓸 수 있다는 의미입니다. 매핑 해제는 버퍼 매핑을 해제하는 것을 의미하며, 이는 CPU가 더 이상 버퍼를 읽고 쓸 수 없으므로 GPU에서 콘텐츠를 사용할 수 있음을 의미합니다.
이제 데이터를 버퍼에 쓸 수 있습니다.
const adapter = await navigator.gpu.requestAdapter();여기에서는 버퍼를 CPU에 매핑하고 버퍼에 데이터를 씁니다. 그런 다음 버퍼 매핑을 해제합니다.
vertexBuffer.getMappedRange()는 CPU에 매핑된 버퍼의 범위를 반환합니다. 이를 사용하여 버퍼에 데이터를 쓸 수 있습니다.
그러나 이는 원시 데이터일 뿐이며 GPU는 이를 해석하는 방법을 모릅니다. 버퍼의 레이아웃을 정의해야 합니다.
const device = await adapter.requestDevice(); console.log(device);여기서 arrayStride는 GPU가 다음 입력을 찾을 때 버퍼에서 앞으로 건너뛰어야 하는 바이트 수입니다. 예를 들어 arrayStride가 8이면 GPU는 다음 입력을 얻기 위해 8바이트를 건너뜁니다.
여기서는 float32x2를 사용하므로 스트라이드는 8바이트, 각 부동소수점당 4바이트, 각 정점당 부동소수점 2개입니다.
이제 정점 셰이더를 작성할 수 있습니다.
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });여기서 @vertex는 버텍스 셰이더라는 의미입니다. @location(0)은 앞서 정의한 대로 속성의 위치인 0을 의미합니다. 셰이더 언어에서는 버퍼의 레이아웃을 처리하므로 값을 전달할 때마다 필드가 @location을 정의한 구조체 또는 @location이 포함된 값만 전달해야 합니다.
vec2f는 2차원 부동 벡터이고, vec4f는 4차원 부동 벡터입니다. 버텍스 셰이더는 vec4f 위치를 반환해야 하므로 @builtin(position)으로 주석을 달아야 합니다.
조각 셰이더
마찬가지로 프래그먼트 셰이더는 보간된 정점 출력을 가져와서 첨부 파일, 이 경우 색상을 출력하는 것입니다. 보간이란 정점의 특정 픽셀만 값을 결정하지만 다른 모든 픽셀에 대해서는 값이 선형, 평균 또는 기타 수단으로 보간된다는 의미입니다. 프래그먼트의 색상은 4차원 벡터로, 각각 빨간색, 녹색, 파란색, 알파의 조각 색상입니다.
색상은 0~255가 아닌 0~1 범위에 있다는 점에 유의하세요. 또한 프래그먼트 셰이더는 삼각형의 색상이 아닌 모든 정점의 색상을 정의합니다. 삼각형의 색상은 정점의 색상과 보간에 의해 결정됩니다.
현재는 프래그먼트의 색상을 제어할 필요가 없으므로 단순히 상수 색상을 반환하면 됩니다.
const main = async () => { console.log('Hello, world!') } main()렌더 파이프라인
그런 다음 정점 셰이더와 프래그먼트 셰이더를 교체하여 맞춤형 렌더 파이프라인을 정의합니다.
const adapter = await navigator.gpu.requestAdapter();프래그먼트 셰이더에서는 캔버스의 형식인 대상 형식을 지정해야 합니다.
드로우 콜
렌더링 패스가 끝나기 전에 드로우 콜을 추가합니다.
const device = await adapter.requestDevice(); console.log(device);여기서 setVertexBuffer에서 첫 번째 매개변수는 파이프라인 정의 필드 버퍼에서 버퍼의 인덱스이고, 두 번째 매개변수는 버퍼 자체입니다.
draw를 호출할 때 매개변수는 그릴 정점 수입니다. 정점이 3개 있으므로 3개를 그립니다.
이제 캔버스에 노란색 삼각형이 보일 것입니다.
생활 게임 세포 그리기
이제 코드를 약간 수정합니다. 인생 게임을 만들고 싶기 때문에 삼각형 대신 사각형을 그려야 합니다.
정사각형은 실제로 두 개의 삼각형이므로 6개의 꼭지점을 그려야 합니다. 여기서의 변경사항은 간단하므로 자세한 설명이 필요하지 않습니다.
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });이제 캔버스에 노란색 사각형이 표시됩니다.
좌표계
GPU의 좌표계에 대해서는 논의하지 않았습니다. 그것은 오히려 간단합니다. GPU의 실제 좌표계는 오른쪽 좌표계로 x축은 오른쪽, y축은 위, z축은 화면 밖을 가리킵니다.
좌표계의 범위는 -1부터 1까지입니다. 원점은 화면 중앙입니다. z축은 0부터 1까지이며, 0은 가까운 평면, 1은 먼 평면입니다. 그러나 z축은 깊이를 나타냅니다. 3D 렌더링을 할 때 객체의 위치를 결정하기 위해 z축만 사용할 수는 없고 원근 분할을 사용해야 합니다. 이를 NDC, 정규화된 장치 좌표라고 합니다.
예를 들어 화면 왼쪽 상단에 사각형을 그리려는 경우 정점은 (-1, 1), (-1, 0), (0, 1), (0, 0)입니다. , 하지만 그리려면 두 개의 삼각형을 사용해야 합니다.
위 내용은 웹에서 뭔가를 그리는 삼각형의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!