| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- String
- Hash
- Stored Procedure
- Two Points
- Dijkstra
- 다익스트라
- Trie
- union find
- Brute Force
- 그래프
- 이진탐색
- binary search
- SQL
- 스토어드 프로시저
- MYSQL
- DP
- two pointer
- Today
- Total
codingfarm
Post Process 본문
EffectComposer 는 후처리 파이프라인으로, 여러 개의 Pass가 순서대로 실행되어, 이전 Pass 결과를 다음 Pass가 입력으로 사용하며 덧그리는 방식으로 동작한다.
동작 방식
- RenderTarget A에 렌더
- 다음 Pass는 A를 입력(texture) 으로 사용
- 결과를 RenderTarget B에 출력
- A ↔ B를 계속 스왑 (ping-pong)
- 마지막 Pass만 화면(framebuffer)에 출력
Pass의 공통 인터페이스
pass.render(renderer, writeBuffer, readBuffer, deltaTime);
| renderer | WebGLRenderer |
|---|---|
| readBuffer | 이전 Pass의 결과 텍스처 |
| writeBuffer | 이번 Pass가 쓸 렌더 타겟 |
| deltaTime | 프레임 간 시간 |
작성 예
import * as THREE from 'three';
import { Pass } from 'three/examples/jsm/postprocessing/Pass.js';
export class SimpleColorPass extends Pass {
constructor() {
super();
// Fullscreen quad용 shader
this.material = new THREE.ShaderMaterial({
uniforms: {
tDiffuse: { value: null },
strength: { value: 0.8 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
// gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float strength;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
color.rgb *= strength;
gl_FragColor = color;
}
`
});
// Pass에서 제공하는 fullscreen quad helper
this.fsQuad = new Pass.FullScreenQuad(this.material);
}
render(renderer, writeBuffer, readBuffer /*, deltaTime */) {
// 이전 패스 결과를 입력으로 설정
this.material.uniforms.tDiffuse.value = readBuffer.texture;
// 화면 출력인지, 다음 버퍼인지
if (this.renderToScreen) {
renderer.setRenderTarget(null);
} else {
renderer.setRenderTarget(writeBuffer);
}
this.fsQuad.render(renderer);
}
dispose() {
this.material.dispose();
this.fsQuad.dispose();
}
}
전체적으로 위와같은 뼈대를 바탕으로 구현된다.
1. readBuffer.texture를 통해 이전 pass까지 그려낸 화면 정보를 얻을 수 있다.
2. 화면 출력이라면 setRenderTaget을 null로 설정
3. 다음 pass로 전달해야 한다면 writeBuffer를 target으로 설정
4. fsQuad.render(renderer)를 통해 출력 대상에 렌더링 결과를 반영
RenderTarget
Renderer가 그려낸 결과는 계속 GPU 내부 자원으로 남게되며, Three.js에서 코드로 다루는 RenderTarget은 GPU 메모리(VRAM)에 존재하는 렌더 타겟 리소스에 대한 JS 측 핸들(참조 객체)이다.
gl.readPixels 같은 명시적인 read 명령을 호출하지 않는 이상, cpu로 전송되지 않는다.
또한 RenderTarget에 변화를 주기 위해선 shader를 작성하고, draw.render 명령을 호출하는것 뿐이다.
Full Screen Quad
화면 전체를 덮는 4각형(삼각형 2개)를 GPU에 그리는 기법에 사용되는 렌더링 대상
3d 모델들의 geometry 정보를 반영해야 하는 material shader와 달리, postprocess shader는 2차원 화면위에 덧그리는 용도로 주로 사용되며 대부분의 작업은 사실상 fragment shader에서 이루어질때가 많다.
그렇기에 vertex shader는 아래와 같이 간단하게 작성된다.
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
이때 코드에서 사용된 position과 uv는 scene의 mesh가 아니라 quad의 geometry attribute이다.
그러므로 후처리 pass의 본질은 사실상 아래의 한줄이다.
vec4 color = texture2D(tDiffuse, vUv);
- tDiffuse = 이전 패스의 결과 텍스처(= readBuffer.texture)
- vUv = 현재 픽셀에 해당하는 UV
즉, post process도 본질은 shader인 이상 그려내는 대상은 필요하며, 그 역할을 full screen quad가 담당한다.
마치며
수많은 post process 관련 예제들의 vertex shader에서 modelViewMatrix를 사용하였기에,
post process 쉐이더에서 각 모델들의 attribute에 접근 가능할지 모른다는 기대감으로 조사해 보았지만 아니었다.
attribute 데이터를 post process에서 연계적으로 반영하기 위해선 크게 아래와같은 방법들로 나뉜다.
1. scene.overrideMaterial 로 쉐이더 일괄 적용 후 별도 렌더링 수행
2. MRT (Multi Render Target)
1번의 경우 최소 한번의 렌더링이 추가로 수행되어야 하며, 이를 활용한 예제도 풍부하다.
2번은 기본 렌더링의 출력에 묶어낼 수 있다고는 나오는데, Three.js 공식문서에도 별다른 정보가 안보인다.
