Closed8

Environment map / Realistic Render

t12ut12u

EnvironmentMap

https://threejs-journey.com/lessons/environment-map
https://threejs-journey.com/lessons/realistic-render

LDR

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)
})
t12ut12u

EnvironmentMap 自作

  • ライトの設定

  • カメラの設定

    • [n]position や rotation を編集するパネルを開閉する


  • 保存

    • [F12]でアクティブなカメラの視点に切り替え
    • [Option + S]で画像保存画面をひらく
    • 右のパネルの File Format でRadiance HDRを選択
    • 画像を保存
t12ut12u

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)
    })
})
t12ut12u

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)
}
t12ut12u

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, // 質は最高だがパフォーマンス悪い
})
t12ut12u

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))
t12ut12u

影を追加する

  • environmentMapの光の強い方向からdirectionalLightを向ける
  • directionalLight.shadow.camerafarmapSizeを変更するためにCameraHelperを使う
  • directionalLight.target.positionを変更した場合、directionalLight.targetsceneの中にないので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
  }
})
t12ut12u

Shadow Acne 問題

  • 影をcastするオブジェクトにライトを当てると、表面に等高線状の縞模様(Shadow Acne)ができることがある。これは、ピクセル単位で自身の影を投影してしまっているために起こる。
  • 影を計算するとき、以下の2つのプロパティを使って問題に対処する
    • normalBiasは、オブジェクトの大きさを実際より大きくまたは小さく調整する
    • biasは、オブジェクトの表面を実際より奥または手前に調整する
  • どの値が良いかは完全にケースバイケースなので、guiを使って確認する
  • 当然影の形が変わってしまうので、値はできるかぎり小さくする
directionalLight.shadow.normalBias = 0.027
directionalLight.shadow.bias = -0.004
このスクラップは1ヶ月前にクローズされました