Closed8
Environment map / Realistic Render
EnvironmentMap
LDR
- LDR - Low Dynamic Range
- HDRI→CubeMap(画像変換ツール)
const cubeTextureLoader = new THREE.CubeTextureLoader()
const environmentMap = cubeTextureLoader.load([
`/environmentMaps/${mapId}/px.png`,
`/environmentMaps/${mapId}/nx.png`,
`/environmentMaps/${mapId}/py.png`,
`/environmentMaps/${mapId}/ny.png`,
`/environmentMaps/${mapId}/pz.png`,
`/environmentMaps/${mapId}/nz.png`,
])
// ライティングに environmentMap を使用する
// MeshStandardMaterial に自動的に環境の反射が設定される
scene.environment = environmentMap
// 背景に environmentMap を表示する
scene.background = environmentMap
HDRI
- HDRI - High Dynamic Range Image
- RGBE - Red Green Blue Exponent
- HDR のエンコーディング形式の名前
import { RGBELoader } from "three/examples/jsm/Addons.js"
const rgbeLoader = new RGBELoader()
rgbeLoader.load("/environmentMaps/2k.hdr", (environmentMap) => {
environmentMap.mapping = THREE.EquirectangularReflectionMapping
scene.background = environmentMap
scene.environment = environmentMap
console.log(environmentMap)
})
EnvironmentMap 自作
-
ライトの設定
-
カメラの設定
-
[n]
position や rotation を編集するパネルを開閉する
-
-
保存
-
[F12]
でアクティブなカメラの視点に切り替え -
[Option + S]
で画像保存画面をひらく - 右のパネルの File Format で
Radiance HDR
を選択 - 画像を保存
-
GroundedSkybox
2024/04/03 現在ちょっと変な挙動っぽい?
const debugObject = {
skyboxHeight: 20,
skyboxScale: 0.8,
}
let skybox: GroundedSkybox
rgbeLoader.load("/environmentMaps/2k.hdr", (environmentMap) => {
const updateSkybox = (skybox: GroundedSkybox) => {
skybox.position.y = debugObject.skyboxHeight * debugObject.skyboxScale
skybox.scale.setScalar(debugObject.skyboxScale)
skybox.matrixWorldNeedsUpdate = true
}
environmentMap.mapping = THREE.EquirectangularReflectionMapping
scene.environment = environmentMap
skybox = new GroundedSkybox(environmentMap, debugObject.skyboxHeight, 100)
updateSkybox(skybox)
scene.add(skybox)
gui
.add(debugObject, "skyboxHeight")
.min(0.1)
.max(50)
.step(0.001)
.onChange(() => {
updateSkybox(skybox)
})
gui
.add(debugObject, "skyboxScale")
.min(0.1)
.max(1)
.step(0.001)
.onChange(() => {
updateSkybox(skybox)
})
})
CubeCamera
- オブジェクトを写し取って
environmentMap
を作成できる - 上下左右前後の6面を写すカメラなので
CubeCamera
- 環境に含めたい
holyDonut
とのみ同じレイヤーを指定する
const holyDonut = new THREE.Mesh(
new THREE.TorusGeometry(8, 0.5),
new THREE.MeshBasicMaterial({
color: "white",
})
)
holyDonut.position.set(0, 3.5, 0)
holyDonut.layers.enable(1)
scene.add(holyDonut)
// Cube render target
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
type: THREE.HalfFloatType,
})
scene.environment = cubeRenderTarget.texture
const cubeCamera = new THREE.CubeCamera(0.1, 100, cubeRenderTarget)
cubeCamera.layers.set(1)
- 光源である
holyDonut
を動かしたあとにcubeCamera.update(renderer, scene)
を呼び出してenvironmentMap
を更新する
const clock = new THREE.Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
// Real time environment map
if (holyDonut) {
holyDonut.rotation.x = Math.sin(elapsedTime) * 2
cubeCamera.update(renderer, scene)
}
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(tick)
}
toneMapping
- HDRはかなり強い光の情報を持っているが、一般的なディスプレイではある程度以上の光は白飛びしてしまう。すなわち、ディスプレイの限界でLDRになる。
- 一方、人間の目などは、明るい屋外や暗い室内など、 ほとんどの景色に関して光の総量が変わってもいい感じに見れるように入ってくる光の量を自動的に調整している
- この目に入る光の調整をレンダリング過程として処理して、LDRの環境でもHDRの広いレンジの光の画像を自然に見れるように調整するのがトーンマッピング
- Three.jsでは5種類のトーンマッピングが用意されている
renderer.toneMapping = THREE.CineonToneMapping
gui.add(renderer, "toneMapping", {
No: THREE.NoToneMapping, // 何もしない
Linear: THREE.LinearToneMapping, // ほとんど変わらない
Reinhard: THREE.ReinhardToneMapping, // ぼんやりした感じになる
Cineon: THREE.CineonToneMapping, // コントラストがはっきりする
ACESFilmic: THREE.ACESFilmicToneMapping, // 質は最高だがパフォーマンス悪い
})
Antialias
-
window.devicePixelRatio
が低いディスプレイではエイリアシングが起きる。 - Super Sampling (SSAA) / Fullscreen Sampling (FSAA)
- canvas サイズを縦横2倍に拡大してから、実際のサイズに縮小する。実際の 1px は拡大した 4px の平均。
- パフォーマンスは悪い(4倍)が、小さなプロジェクトでは有効な場合も
- Multi Sampling (MSAA)
- ピクセルがオブジェクトのエッジ部分なら、周囲のピクセルといい感じに混ぜ合わせる
-
antialias: true
とするだけ
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true
})
- 実際には、
window.devicePixelRatio
が十分な場合は無効にしたほうがよい
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: window.devicePixelRatio === 1,
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
影を追加する
-
environmentMap
の光の強い方向からdirectionalLight
を向ける -
directionalLight.shadow.camera
のfar
やmapSize
を変更するためにCameraHelper
を使う -
directionalLight.target.position
を変更した場合、directionalLight.target
はscene
の中にないのでmatrix
が自動で更新されない。updateMatrixWorld()
を呼び出す - モデルは大抵複数のオブジェクトからなるので、
scene.traverse(cb)
を使って影の設定をする
const directionalLight = new THREE.DirectionalLight("#ffffff", 6)
directionalLight.position.set(-5, 7, 3)
scene.add(directionalLight)
gui.add(directionalLight, "intensity").min(0).max(10).step(0.001)
gui.add(directionalLight.position, "x").min(-10).max(10).step(0.001)
gui.add(directionalLight.position, "y").min(-10).max(10).step(0.001)
gui.add(directionalLight.position, "z").min(-10).max(10).step(0.001)
directionalLight.castShadow = true
gui.add(directionalLight, "castShadow")
directionalLight.shadow.camera.far = 15
directionalLight.shadow.mapSize.set(2 ** 9, 2 ** 9)
// const directionalLightHelper = new THREE.CameraHelper(
// directionalLight.shadow.camera
// )
// scene.add(directionalLightHelper)
directionalLight.target.position.set(0, 4, 0)
// 位置を変更したが、directionalLight.target は scene に追加されていないので matrix を手動で更新する必要がある
directionalLight.target.updateMatrixWorld()
scene.traverse((object) => {
if (
object instanceof THREE.Mesh &&
object.material instanceof THREE.MeshStandardMaterial
) {
object.castShadow = true
object.receiveShadow = true
}
})
Shadow Acne 問題
- 影をcastするオブジェクトにライトを当てると、表面に等高線状の縞模様(Shadow Acne)ができることがある。これは、ピクセル単位で自身の影を投影してしまっているために起こる。
- 影を計算するとき、以下の2つのプロパティを使って問題に対処する
-
normalBias
は、オブジェクトの大きさを実際より大きくまたは小さく調整する -
bias
は、オブジェクトの表面を実際より奥または手前に調整する
-
- どの値が良いかは完全にケースバイケースなので、
gui
を使って確認する - 当然影の形が変わってしまうので、値はできるかぎり小さくする
directionalLight.shadow.normalBias = 0.027
directionalLight.shadow.bias = -0.004
このスクラップは2024/04/03にクローズされました