캔버스 초기화를 했으니, 간단한 도형을 그려보자
지난번에 캔버스 초기화를 완료하였다.
생각보다 조금 복잡하였는데 어찌저찌 캔버스를 초기화 하였으니 이제 cnavas에 간단한 도형을 그려보도록 하자.
https://codelabs.developers.google.com/your-first-webgpu-app?hl=ko#3
위 내용을 참고하면 좋을 것 같다.
일단, 가장 중요한 내용은 Canvas 2D와는 달리 GPU에서는 점, 선, 삼각형만 처리가 가능하다는 것.
잠시 대학생 때 다룬 이론이 지나가는데, 짧은 상식으로는 삼각형 형태로 대부분의 3D 렌더링이 가능하다는 기억이 난다.
유의하고, 넘어가 보도록 하자.
일단 코드는 아래와 같은 drawRect를 추가해 주었다.
drawRect() {
// 삼각형 두개로, 정사각형의 좌표 지정.
const vertices = new Float32Array([
// X, Y,
-0.8, -0.8, // Triangle 1 (Blue)
0.8, -0.8,
0.8, 0.8,
-0.8, -0.8, // Triangle 2 (Red)
0.8, 0.8,
-0.8, 0.8,
])
// GPU 측 메모리는 GPUBuffer를 통해 관리 됨.
const vertexBuffer = this.device.createBuffer({
label: "Cell vertices",
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
})
this.device.queue.writeBuffer(vertexBuffer, 0, vertices);
const vertexBufferLayout: GPUVertexBufferLayout = {
arrayStride: 8, // byte 단위. GPU에서 다음 꼭짓점을 찾을 때 버퍼에서 앞으로 건너뛰어야 하는 바이트 수. Float32Array를 이용하여, 각 꼭지점은 32bit + 32bit, 8byte로 구현
// 따라서 다음 꼭지점을 찾으려면 8byte 뒤의 버퍼를 찾아야 하기 때문에 8으로 초기화 해준다.
attributes: [{
format: "float32x2", //float32, 2개의 값을 이용한다는 정의
offset: 0, // 어디서 부터 버퍼를 읽을지에 대한 설정. 지금은 사각형 하나만 그릴 것이기 때문에 0이 입력된다. 두개 이상이 있을 때는 이전 버퍼의 합을 넣어주면 된다.
shaderLocation: 0, // Position, see vertex shader
}],
};
const cellShaderModule = this.device.createShaderModule({
label: 'Cell shader',
code: `
@vertex
fn vertexMain(@location(0) pos: vec2f) ->
@builtin(position) vec4f {
return vec4f(pos, 0, 1);
}
@fragment
fn fragmentMain() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
`
});
const cellPipeline = this.device.createRenderPipeline({
label: "Cell pipeline",
layout: "auto",
vertex: {
module: cellShaderModule,
entryPoint: "vertexMain",
buffers: [vertexBufferLayout]
},
fragment: {
module: cellShaderModule,
entryPoint: "fragmentMain",
targets: [{
format: this.canvasFormat
}]
}
});
// 뭔가 새로 그리려면, beginRenderPass가 시작되고, pass.end가 호출 되어야 함
this.pass = this.encoder.beginRenderPass({
colorAttachments: [{
view: this.context.getCurrentTexture().createView(),
loadOp: "clear",
clearValue: [0, 0, 0.4, 1],
storeOp: "store",
}]
})
// 그리는 작업을 이 안에서 처리한다.
this.pass.setPipeline(cellPipeline)
this.pass.setVertexBuffer(0, vertexBuffer)
this.pass.draw(vertices.length / 2)
this.pass.end()
// 명령 버퍼 제출
this.device.queue.submit([this.encoder.finish()])
}
대부분은 주석으로 설명했지만, sahderModule, cellpieline은 관련해서는 따로 설명해보려고 한다.
test.js에서 clearCanvas 대신, drawRect를 호출하고 결과를 확인
createShaderModule & createRenderPipeline
예제 코드에서 가장 특이한 부분이 보이는건 createShaderModule쪽이다.
각 꼭지점 버퍼마다 실행할 함수를 작성하는 곳인데, 에제 코드에서는 6개의 좌표값 (삼각형 두개)를 넘겨주었으므로 총 해당 코드가 6번 호출된다. (비동기 적으로 실행된다)
각 꼭지점의 셰이더 단계를 표시하는 부분이 @vertex이다.
함수 명은 아무렇게나 지정이 가능하다.
@vertex
fn vertexMain(@location(0) pos: vec2f) ->
@builtin(position) vec4f {
return vec4f(pos, 0, 1);
}
먼저 위 코드를 해석해 보자.
@vertex => 꼭지점의 셰이더단게라는 선언
fn vertexMain => vertexMain이라는 함수 선언.
vec4f => 4차원 백터. (x, y, z, w)
vec2f => 2차원 백터 (x,y)
pos에 x,y 값을 받아서 vec4f로 넘겨 준다. (현재는 2D 상태로 그려주기 때문. 각 좌표값을 float32Array를 통해 지정했었고, format을 float32x2로 지정했었다.)
@location(0) 현재 버텍스 데이터에서 가져오는 값.
8byte씩 가져오니, 꼭지점 좌표값을 가져오게 된다.
다음은 아래, 프레그맨트 셰이더 단계이다.
@fragment
fn fragmentMain() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
fragment shader는 모든 픽셀에 대하여 처리된다.
여기서 사용한 vec4f 는 (r,g,b,a)에 대해 대응하고, 모든 삼각형의 픽셀을 빨간색으로 지정한다는 얘기.
모든 vertex함수가 호출 되고 난 뒤, fragment 함수가 호출되기 때문에 꼭지점 안쪽의 데이터만 빨간색으로 칠해지게 된다.
여기까지 이해하게 되면 createRenderPipeline쪽은 조금 이해가 수월해 진다.
const cellPipeline = this.device.createRenderPipeline({
label: "Cell pipeline",
layout: "auto",
vertex: {
module: cellShaderModule,
entryPoint: "vertexMain",
buffers: [vertexBufferLayout]
},
fragment: {
module: cellShaderModule,
entryPoint: "fragmentMain",
targets: [{
format: this.canvasFormat
}]
}
});
layout의 경우에는 현재는 넘어가야 하는 단계 이다.
아래쪽이 일단 현재는 중요한데,
vertex 쪽은 vertex 단계에 관한 세부 정보를 제공하는 부분이다.
위에서 만든 모듈을 사용한다는 명시를 하고, vertexMain 함수를 이용한다는 함수 명을 전달 하였다.
따로 함수명을 지정하는 이유는, 단일 셰이더 모듈에 여러개의 vertex를 지정 할 수 있기 때문이다.
buffers 역시 위에서 제공한 vertexBufferlayout을 사용한다고 지정하였다.
fragment 쪽도 마찬가지이며, 다른 부분은 target 부분이다.
넘겨주는 format값은 파이프라인이 사용되는 랜더 패스의 colorAttachments와 지정된 텍스처와 일치해야 한다.
기존 colorAttachments에 view 부분에서 this.context.getCurrenttextrue() 에는 canvasFormat 값이 포함 되어 있다.
'JAVASCRIPT > vanilla' 카테고리의 다른 글
[javascript] DOM 변화를 관측하여 observer 패턴 사용 (0) | 2023.11.02 |
---|---|
WebGPU(4) - cell 색 지정 (1) | 2023.05.17 |
WebGPU(3) - bindGroup (0) | 2023.05.16 |
WebGPU(1) - 개념과 캔버스 초기화 (1) | 2023.05.16 |