3D요소를 좀 더 사실감, 입체감 있게 표현해주는 중요한 요소임. three.js의 그림자를 지원하는 조명 3가지와 Deri의 그림자 컴포넌트 3가지가 있음.
그림자를 생성할지 여부, 즉 객체가 다른 객체에 그림자를 생성할지 여부를 설정함. 위 이미지에서 빨간공의 그림자를 생성함.
<mesh castShadow>
...
</mesh>
그림자를 받을지 여부, 즉 scene에서 그림자를 만들고 있는 객체가 해당 객체에 그림자를 드리울지 여부를 설정함. 위 이미지에서 빨간공은 노란 토러스의 그림자를 받고있음.
<mesh receiveShadow>
...
</mesh>
three.js 의 조명을 사용하여 그림자를 생성하기 위해서는 R3F의 Canvas 클래스에 shadows
옵션을 주어 scene에서 그림자를 활성화할지 여부를 설정할 수 있음.
<Canvas shadows camera={{ near: 1, far: 100, position: [7, 7, 0] }}>
...
</Canvas>
조명 요소에 castShadow
옵션을 주어 그림자 생성 활성화해주어야 함.
<directionalLight
ref={light}
castShadow
color={0xffffff}
intensity={0.9}
position={[0, 5, 0]}
target-position={[0, 0, 0]}
/>
자세히 보면 노란 토러스의 그림자가 끊긴 것이 보임. 이는 그림자 요소가 DirectionalLight의 절구체를 벗어났기 때문임.
<directionalLight
ref={light}
shadow-camera-top={6}
shadow-camera-bottom={-6}
shadow-camera-left={-6}
shadow-camera-right={6}
castShadow
color={0xffffff}
intensity={0.9}
position={[0, 5, 0]}
target-position={[0, 0, 0]}
/>
이를 해결하기 위해 DirectionalLight의 절구체 크기를 키워줌. 절구체의 크기를 너무 크게하면 품질이 떨어지는데, 그림자는 텍스쳐 이미지이고 이미지 크기는 이미 정해져있기 때문임.
<directionalLight
ref={light}
shadow-camera-top={6}
shadow-camera-bottom={-6}
shadow-camera-left={-6}
shadow-camera-right={6}
shadow-mapSize={[512, 512]}
castShadow
color={0xffffff}
intensity={0.9}
position={[0, 5, 0]}
target-position={[0, 0, 0]}
/>
절구체의 크기를 키우는 동시에 그림자의 품질도 높이고 싶다면 shadow-mapSize
의 값을 키워주면 되며, 이값은 [512, 512]
가 기본값임.
조명 요소에 castShadow
옵션을 주어 그림자 생성 활성화해주어야 함.
<pointLight
castShadow
color='#ffffff'
intensity={10}
position={[0, 5, 0]}
/>
<spotLight
shadow-mapSize={[1024 * 4, 1024 * 4]}
castShadow
color='#ffffff'
intensity={20}
position={[0, 5, 0]}
angle={THREE.MathUtils.degToRad(60)}
/>
그림자 속성 중 shadow-radius
, shadow-blurSample
을 설정하기 위해서는 위의 알고리즘 중에서 VSMShadowMap 알고리즘을 적용해야함. R3F의 Canvas 클래스에 shadows
속성을 사용하여 알고리즘을 선택할 수 있음.
<Canvas
shadows='variance'
camera={{
near: 1,
far: 100,
position: [7, 7, 0]
}}
>
...
</Canvas>
하지만 위 이미지 처럼 그림자가 깨지는 현상이 발생하기 때문에 shadow-bias
속성값을 작게 설정하여 노이즈를 없앨 수 있음.
<spotLight
...
shadow-bias={-0.0001}
...
/>
그림자 경계도 shadow-radius
와 그림자 흐림도 shadow-blurSample
를 설정하여 자연스러운 연출 가능.
<spotLight
...
shadow-radius={32}
shadow-blurSample={32}
...
/>
정적인 그림자. 즉 메시가 움직여도 그림자는 변경되지 않음. 해당 컴포넌트가 동적인 그림자도 지원은 하지만 정적 그림자로 사용하는 것을 추천함.
해당 컴포넌트는 그림자가 표현될 평면 메시를 자동으로 생성해줌.
해당 컴포넌트가 만든 그림자는 다른 메시에 표현되지 않음. 즉 다른 메시의 receiveShadow
는 필요없음.
정적 그림자는 첫 렌더링 시 한번 계산되기 때문에 렌더링 속도가 빠르고 그 이후에는 CPU, GPU 사용량이 0에 가까움. 하지만 아래 이미지처럼 빨간 공의 그림자가 첫 렌더링 위치에 멈춰있음.
AccumulativeShadows
는 RandomizedLight
를 자식 컴포넌트로 받아야 그림자가 생성되고, 여러 개의 자식 컴포넌트로 받을 수 있음.
<AccumulativeShadows
position={[0, 0.01, 0]} // 그림자가 표현될 평면 메시의 위치 좌표
scale={10} // 그림자가 표현될 평면 메시의 크기
color='#000000' // 그림자 색상
opacity={0.7} // 그림자 투명도
alphaTest={1} // 그림자에 대한 픽셀의 알파값이 설정값보다 작을 때만 표현
frames={60} // 처음 렌더링될 때의 프레임 수 (초기에 그림자가 페이드인 되는 속도)
>
<RandomizedLight
radius={0.5} // 그림자 경계도
ambient={0.21} // Ambient Occlusion 에 대한 속성값, 값이 작아질 수록 그림자와 반사가 약해짐.
intensity={1.5} // 광원 강도
position={[5, 3, 0]} /// 광원 위치 좌표
/>
<RandomizedLight
amount={4} // 광원의 개수 (기본값: 8)
radius={0.5} // 그림자 경계도
ambient={0.21} // Ambient Occlusion 에 대한 속성값, 값이 작아질 수록 그림자와 반사가 약해짐.
intensity={1.5} // 광원 강도
position={[-5, 3, 0]} /// 광원 위치 좌표
/>
</AccumulativeShadows>
frames
를 Infinity로 설정하여 동적 렌더링 설정 가능함. 렌더링되는 프레임 수를 제한하지 않으므로서 그림자가 계속해서 렌더링 됨. 하지만 빨간공의 속도가 빨라서 그림자가 잔상처럼 남음.
<AccumulativeShadows
...
frames={Infinity}
temporal // 그림자를 부드럽게 함.
blend={30} // 그림자의 부드러움 정도
...
>
<RandomizedLight
radius={0.5}
ambient={0.21}
intensity={1.5}
position={[5, 3, 0]}
/>
</AccumulativeShadows>
directionalLight
조명이 생성해준 그림자를 계산하여 표현하기 때문에 directionalLight
을 필요로 함.
const config = useControls({
size: { value: 25, min: 0, max: 100 }, // 광원의 크기
focus: { value: 0, min: 0, max: 10 }, // 깊이 포커스
samples: { value: 10, min: 1, max: 100, step: 1 }, // 그림자 샘플링 수
});
<SoftShadows {...config} />
R3F의 기본적인 그림자 기능을 전혀 사용하지 않는 독립적인 그림자. 즉 Canvas
클래스의 shadows
속성과 다른 메시의 castShadow
, receiveShadow
속성을 제거해도 해도 그림자를 생성할 수 있음.
해당 컴포넌트는 그림자가 표현될 평면 메시를 자동으로 생성하며, 다른 메시에는 그림자가 표현되지 않음.
<ContactShadows
position={[0, 0, 0]} // 그림자가 표현될 평면 메시의 위치 좌표
scale={10} // 그림자가 표현될 평면 메시의 크기
resolution={512} // 그림자 이미지 크기
color='#000000' // 그림자 색상
opacity={0.4} // 그림자 투명도
blur={0.5} // 그림자 흐림도
// frames={1} // 1: 정적 그림자
/>