| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- DP
- 다익스트라
- MYSQL
- Stored Procedure
- union find
- Trie
- two pointer
- Hash
- binary search
- 스토어드 프로시저
- 이진탐색
- Dijkstra
- Brute Force
- String
- SQL
- Two Points
- 그래프
- Today
- Total
codingfarm
customDepthMaterial을 이용한 그림자 렌더링 본문
1.소개

Three.js 메인 페이지에 소개된 장작패기 프로젝트이다.
mp4 동영상 파일과 VideoTexture를 이용하여, 바람에 나무가 움직이는 연출이 인상적이며,
해당 프로젝트를 참고하여, 움직이는 그림자를 렌더링하는 방법을 학습하였다.
const video = document.createElement("video");
video.src = "video/gobo.mp4";
video.loop = true;
video.muted = true;
video.playsInline = true;
video.preload = "auto";
video.crossOrigin = `anonymous`;
video.addEventListener("loadeddata", () => {
video.play();
});
video.load();
const videoTexture = new THREE.VideoTexture(video);
videoTexture.colorSpace = THREE.SRGBColorSpace;
const colorMaterial = new THREE.MeshBasicMaterial({
map: videoTexture,
transparent: true,
alphaTest: 0.5,
side: THREE.DoubleSide,
depthWrite: false,
});
const depthMaterial = new THREE.MeshBasicMaterial({
map: videoTexture,
alphaTest: 0.5,
side: THREE.DoubleSide,
});
Video Texture를 사용하여, colorMaterial과 depthMaterial을 각각 생성한다.
const geometry = new THREE.PlaneGeometry(10, 10);
const mesh = new THREE.Mesh(geometry, colorMaterial);
mesh.customDepthMaterial = depthMaterial;
mesh.visible = true;
mesh.receiveShadow = false;
mesh.castShadow = true;
mesh.material.colorWrite = false;
그리고 plane geometry를 사용하여 mesh를 생성하는데, customDepthMaterial에 앞서 생성한 depthMaterial을 할당한다.
그러면 나무 모양의 그림자가 렌더링됨을 확인할 수 있다.
2. 문제


- 상 : 나무 그림자 active
- 하 : 나무 그림자 inactive
하지만 plane mesh 아래에 존재하는 모델의 그림자 생성을 막아버린다.
나무와 장작의 그림자를 동시에 렌더링 하기위해서, three.js가 그림자를 만드는 방법에 대해 간략히 알아보았다.
- Light의 시점에서 scene을 한번 렌더링 한다.
- 각 픽셀마다 라이트에서 가장 가까운 depth를 shadow map에 저장한다.
- 메인 렌더의 fragment shader에서, 특정 표면이 해당 depth 보다 뒤에 있으면 그림자라고 판단한다.
결국 그림자가 만들어지지 않는 이유는, 광원의 shadow camera 시점에서 plane이 사물보다 앞에 있어 가장 가까운 depth로 기록된. 그 결과 뒤쪽 사물의 fragment는 shadow map에 자신의 depth를 기록할 수 없게된다.
3. 해결
plane을 shadow map에 렌더링 할때, 빛을 통과시켜야하는 영역의 fragment를 discard 시키면 된다.
three.js에서는 material 생성시 alphaTest 수치를 설정할 수 있으며, fragment shader에서 이 alpha 값이 지정된 수치보다 낮다면, discard를 발생시켜 shadow map에 depth를 기록하지 않게 된다.
그리고 three.js는 해당 판정코드를 수월하게 작성 할 수 있도록, alphatest_fragment 청크를 제공하며, 아래와 같이 쉐이더 코드를 삽입하면 된다.
const ALPHA_TEST_FRAGMENT_CHUNK = "#include <alphatest_fragment>";
const GOBO_LUMINANCE_ALPHA_MASK = `
float goboLuminance = dot(diffuseColor.rgb, vec3(0.299, 0.587, 0.114));
diffuseColor.a = 1.0 - goboLuminance;
${ALPHA_TEST_FRAGMENT_CHUNK}
`;
4. 결과

모든 그림자 렌더링된다.
이러한 문제가 발생한 원인은 아래와 같이 정리할 수 있다.
- customDepthMaterial에 의 shadow pass에서 어떤 fragment가 유효한 caster로 남을지 결정된다
- 원본 plane의 형상에 맞춰 shadowMap의 depth가 기록된다.
- 분명 그림자가 없는 fragment 임에도, discard 판정이 없지만 depth 판정은 발생하는 비직관적인 현상이 발생
결국 시각적으로는 비어 있거나 빛이 통과해야 하는 영역인데도, shadow map에는 plane의 가까운 depth가 기록되어 그림자가 있는 것으로 잘못 판정된 현상이다.
본문에서는 alphaTest와 fragmentShader의 alpha 값을 통한 discard로 해결하는 방법을 사용했지만, rgb값을 통해 마스크를 재해석 하는 방법이 아니라, alphaMap을 사용하는 방식이 더 안정적이라는데, 다음에 테스트 해봐야겠다.
depthMaterial.alphaMap = maskTexture;
depthMaterial.alphaTest = 0.5;
아울러 three.js의 렌더링 파이프라인에 대해서 심도있게 학습하고, Shadow Map, Depth Test, Discard에 대해서도 제대로 정리해야겠다.
'computer graphics > Three.js' 카테고리의 다른 글
| WebGL과 GLSL (0) | 2026.05.30 |
|---|---|
| PostProcessing EffectComposer (0) | 2026.01.01 |
| R3F의 3D 컴포넌트 (0) | 2026.01.01 |
| Post Process (0) | 2025.12.27 |
