[Three.js journey 강의노트] 15

9rganizedChaos·2021년 7월 6일
2
post-thumbnail

🙌🏻 해당 글은 Three.js Journey의 강의 노트입니다.

15 Shadows

빛을 비추면 Object의 뒷면이 까매지고, 이를 core shadow라고 한다. 우리에게 현재 없는 것은 drop shadow이다. object 외부에 생기는 shadow. 개발자들에게 합리적인 주사율로 그림자를 렌더링하는 것은 언제나 도전이었다. Three.js에도 방법이 있지만 완벽하다고 하기는 힘들다.

렌더를 수행하면 Three.js는 그림자를 드리울 부분을 렌더한다. 이러한 렌더는 마치 빛이 카메라인 양 시뮬레이트한다. 그 결과는 shadow maps라고 부르는 텍스쳐에 저장된다. shadow map을 직접 볼 수는 없지만, 그림자를 받고 geometry에 투사될 모든 material에 사용된다.

그림자 예시

How to activate shadows

renderer.shadowMap.enabled = true
sphere.castShadow = true
plane.receiveShadow = true
directionalLight.castShadow = true

위와 같이 적용을 해주고 나면 그림자가 scene에 보이는 것을 확인할 수 있다.

이 때 유의해야 할 점은 오직 PointLight, DirectionalLight, SpotLight만이 shadow를 지원한다는 것이다.

Shadow map optimizations

Render size

우리는 light의 shadow 프로퍼티를 통해서 shadow map에 접근할 수 있다.
기본적으로 shadow map의 크기는 512x512이다! 이를 개선하기 위해 아래와 같이 설정해줄 수 있다.

directionalLight.shadow.mapSize.width = 1024
directionalLight.shadow.mapSize.height = 1024

그림자의 테두리가 조금 더 선명해졌다.

Near and far

Three.js는 카메라(여기서는 directionalLight.shadow.camera)를 이용해 shadow map을 렌더링한다. 카메라에서 인자로 근거리와 원거리를 전달하듯이 shadow map에서도 이를 설정해주어야 한다. 그림자의 품질 개선보다는 그림자기 갑자기 보이지 않거나 잘리는 버그를 방지할 수 있다.

directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 6;

Amplitude

위 캡쳐이미지에서도 확인할 수 있듯이, amplitude가 너무 크다. DirectionalLight를 사용하고 있기 때문에 Three.js는 OrthographicCamera를 사용한다. 우리는 카메라의 top, bottom, left, right를 제어할 수 있다. 값이 작아질 수록 그림자가 선명해진다.

directionalLight.shadow.camera.top = 2
directionalLight.shadow.camera.right = 2
directionalLight.shadow.camera.bottom = - 2
directionalLight.shadow.camera.left = - 2

Blur

radius 속성으로 Blur를 컨트롤할 수 있다.
카메라의 근접성을 이용하는 것이 아닌 방식이다.

directionalLight.shadow.radius = 10

Shadow map algorithm

shadow map에 다양한 알고리즘을 적용할 수 있다.

  • THREE.BasicShadowMap: 성능 Good 품질 Bad
  • THREE.PCFShadowMap: 성능 Bad 가장자리 Smooth
  • THREE.PCFSoftShadowMap: 성능 Bad 가장자리 Smoother
  • THREE.VSMShadowMap: 성능 Bad 더 제약적, 예상 못한 결과를 마주할 수 있다.
renderer.shadowMap.type = THREE.PCFSoftShadowMap

SpotLight

  • castShadow 속성을 true로 변경
  • scene에 tartget 프로퍼티 추가

빛을 여러 개 사용하면, 이들이 개별적으로 제어되기 때문에 잘 merge 되지 않는다.
이에 대해 할 수 있는 것은 많지 않다.

  • shadow.mapSize 1024로 변경
  • SpotLight는 Perspective Camera를 활용한다. 떄문에 top, right, bottom, left 말고, fov를 통해 amplitude를 제어해준다!
  • near과 far도 설정한다.
const spotLight = new THREE.SpotLight(0xffffff, 0.4, 10, Math.PI * 0.3);

spotLight.castShadow = true;

spotLight.position.set(0, 2, 2);
scene.add(spotLight);
scene.add(spotLight.target);

const spotLightCameraHelper = new THREE.CameraHelper(spotLight.shadow.camera);
scene.add(spotLightCameraHelper);

spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;

spotLight.shadow.camera.fov = 30;

spotLight.shadow.camera.near = 1;
spotLight.shadow.camera.far = 6;

spotLightCameraHelper.visible = false;

PointLight

  • spotLight와 마찬가지로 PerspectiveCamera를 이용한다. 그러나 아래를 향하고 있다. (?)
  • pointLight는 모든 방향으로 비추기 때문에, Three.js는 큐브 그림자 맵을 만들기 위해 6개 방향을 각각 렌더해야 한다. 이 때 컨트롤 할 수 있는 유일한 속성은 near과 far이다.
const pointLight = new THREE.PointLight(0xffffff, 0.3);

pointLight.castShadow = true;

pointLight.shadow.mapSize.width = 1024;
pointLight.shadow.mapSize.height = 1024;

pointLight.shadow.camera.near = 0.1;
pointLight.shadow.camera.far = 5;

pointLight.position.set(-1, 1, 0);
scene.add(pointLight);

const pointLightCameraHelper = new THREE.CameraHelper(pointLight.shadow.camera);
pointLightCameraHelper.visible = false;
scene.add(pointLightCameraHelper);

Baking shadows

Three.js shadow는 단순한 단면에는 유용하지만, 반대의 경우 지저분해지기 십상이다.
Light에서도 이야기한 바와 같이 shadow도 texture에 Baking이 가능하다!

우선 아래와 같이 모든 그림자를 없애보자!

renderer.shadowMap.enabled = false

그리고 아예 이미지 파일을 로드해 plane에 입혀버린다.

/**
 * Textures
 */
const textureLoader = new THREE.TextureLoader()
const bakedShadow = textureLoader.load('/textures/bakedShadow.jpg')

const plane = new THREE.Mesh(
    new THREE.PlaneGeometry(5, 5),
    new THREE.MeshBasicMaterial({
        map: bakedShadow
    })
)

딱 봐도 훨씬 퀄리티가 좋지만, 문제는 dynamic한 것이 아니라는 점!
sphere의 위치를 조금만 바꿔주면 엉성함이 탄로난다.

Baking shadows alternative

덜 realistic하지만, 더 dynamic한 대안이 존재한다.
그림자(이미지)를 구와 함께 이동하도록 만드는 것이다.

// 구 그림자
const sphereShadow = new THREE.Mesh(
    new THREE.PlaneGeometry(1.5, 1.5),
    new THREE.MeshBasicMaterial({
        color: 0x000000,
        transparent: true,
        alphaMap: simpleShadow
    })
)
sphereShadow.rotation.x = - Math.PI * 0.5
sphereShadow.position.y = plane.position.y + 0.01

scene.add(sphere, sphereShadow, plane)


// 애니메이션 적용
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update the sphere
    sphere.position.x = Math.cos(elapsedTime) * 1.5
    sphere.position.z = Math.sin(elapsedTime) * 1.5
    sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))

    // Update the shadow
    sphereShadow.position.x = sphere.position.x
    sphereShadow.position.z = sphere.position.z
    sphereShadow.material.opacity = (1 - sphere.position.y) * 0.3

    // ...
}

tick()

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글