Three.js Lights

강정우·2025년 1월 6일
0

three.js

목록 보기
8/24
post-thumbnail

Lights

Ambient light

엠비언트 라이트는 모든 방향을 동일하게 비춘다.
따라서 phong 과 standard 모두 어떻게 shading 을 계산해줘야할지 모른다.
따라서 MeshStandardMaterial 의 정교한 장점들을 하나도 살리지 못한다.

바닥 만들기

const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial())
// PlaneGeometry 를 생성하면 수직으로 서있기 때문에 90(-2/PI 라디안)도(라디안으로 표기) 뒤로 눞혀주는 코드
plane.rotation.x = -Math.PI / 2
scene.add(plane)

앞서 Material 에서 언급했듯 Standard 가 표준이고 eventMap 과 light 에 더 현실적으로 반응하기 때문에 Material 은 Standard 를 선택하였다.

Ambient light property

const ambientLight = new THREE.AmbientLight(data.lightColor, Math.PI)
ambientLight.visible = false
scene.add(ambientLight)

const ambientLightFolder = gui.addFolder('AmbientLight')
ambientLightFolder.add(ambientLight, 'visible')
ambientLightFolder.addColor(data, 'lightColor').onChange(() => {
    ambientLight.color.set(data.lightColor)
})
ambientLightFolder.add(ambientLight, 'intensity', 0, Math.PI)

위 코드를 보면 AmbientLight 생성자에 intensity 가 Math.PI로 들어가있는 것을 볼 수 있다.
여기서 오일러를 기준으로 사용하기 때문에 Math.PI가 최대값으로 들어간다는 것을 인지하면 된다.

Directional light

말 그대로 방향성이 있는 광원이다. 이는 엠비언트 라이트와 다르게 Phong 과 Standard Material 에 모두 영향을 준다.
여기서 주의할 점은 helper 가 광원이 아닌, helper 가 가르키는 방향의 무한대 방향이 광원이라는 것을 인지하면 된다. 마치 🌞 처럼

Directional light property

const directionalLight = new THREE.DirectionalLight(data.lightColor, Math.PI)
directionalLight.position.set(1, 1, 1)
scene.add(directionalLight)

const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight)
directionalLightHelper.visible = false
scene.add(directionalLightHelper)

const directionalLightFolder = gui.addFolder('DirectionalLight')
directionalLightFolder.add(directionalLight, 'visible')
directionalLightFolder.addColor(data, 'lightColor').onChange(() => {
    directionalLight.color.set(data.lightColor)
})
directionalLightFolder.add(directionalLight, 'intensity', 0, Math.PI * 10)

const directionalLightFolderControls = directionalLightFolder.addFolder('Controls')
directionalLightFolderControls.add(directionalLight.position, 'x', -1, 1, 0.001).onChange(() => {
    directionalLightHelper.update()
})
directionalLightFolderControls.add(directionalLight.position, 'y', -1, 1, 0.001).onChange(() => {
    directionalLightHelper.update()
})
directionalLightFolderControls.add(directionalLight.position, 'z', -1, 1, 0.001).onChange(() => {
    directionalLightHelper.update()
})
directionalLightFolderControls.add(directionalLightHelper, 'visible').name('Helper Visible')
directionalLightFolderControls.close()

위 코드를 보면 DirectionalLight 말고도 DirectionalLightHelper 가 하나 더 있다. 이 헬퍼는 위 사진처럼 어느 방향에서 빛을 쏘는 지 육안으로 확인할 수 있도록 도와준다. 그리고 생성자의 파라미터로는 DirectionalLight 을 받는다.

그리고 helper 는 변경사항이 존재하면 .update() 를 반드시 사용해서 반영된다.
대신 DirectionalLight 은 따로 update() 를 하지 않아도 된다.

참고로 속도는 Ambient > Directional 이다.

Point light

Point light property

const pointLight = new THREE.PointLight(data.lightColor, Math.PI)
pointLight.position.set(2, 0, 0)
pointLight.visible = false
scene.add(pointLight)

const pointLightHelper = new THREE.PointLightHelper(pointLight)
pointLightHelper.visible = false
scene.add(pointLightHelper)

const pointLightFolder = gui.addFolder('Pointlight')
pointLightFolder.add(pointLight, 'visible')
pointLightFolder.addColor(data, 'lightColor').onChange(() => {
    pointLight.color.set(data.lightColor)
})
pointLightFolder.add(pointLight, 'intensity', 0, Math.PI * 10)

const pointLightFolderControls = pointLightFolder.addFolder('Controls')
pointLightFolderControls.add(pointLight.position, 'x', -10, 10)
pointLightFolderControls.add(pointLight.position, 'y', -10, 10)
pointLightFolderControls.add(pointLight.position, 'z', -10, 10)
pointLightFolderControls.add(pointLight, 'distance', 0, 20).onChange(() => {
    spotLightHelper.update()
})
pointLightFolderControls.add(pointLight, 'decay', 0, 10).onChange(() => {
    spotLightHelper.update()
})
pointLightFolderControls.add(pointLightHelper, 'visible').name('Helper Visible')
pointLightFolderControls.close()

intentisy: 빛의 세기 (defualt=1)
distance: 최대 거리 (default=0 무한대임)
decay: 빛의 거리에 따라 조명이 어두워지는 양. 숫자가 낮을 수록 빛이 더 강해짐(default=2)

더 많은 속성, 메서드는 docs 참조

Spot light

스팟 라이트는 포인트 라이트와 동일하지만 원뿔형이라는 차이가 있다.

Spot light property

const spotLight = new THREE.SpotLight(data.lightColor, Math.PI)
spotLight.position.set(3, 2.5, 1)
spotLight.visible = false
//spotLight.target.position.set(5, 0, -5)
scene.add(spotLight)

const spotLightHelper = new THREE.SpotLightHelper(spotLight)
spotLightHelper.visible = false
scene.add(spotLightHelper)

const spotLightFolder = gui.addFolder('Spotlight')
spotLightFolder.add(spotLight, 'visible')
spotLightFolder.addColor(data, 'lightColor').onChange(() => {
    spotLight.color.set(data.lightColor)
})
spotLightFolder.add(spotLight, 'intensity', 0, Math.PI * 10)

const spotLightFolderControls = spotLightFolder.addFolder('Controls')
spotLightFolderControls.add(spotLight.position, 'x', -10, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight.position, 'y', -10, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight.position, 'z', -10, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'distance', 0, 20).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'decay', 0, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'angle', 0, 1).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'penumbra', 0, 1, 0.001).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLightHelper, 'visible').name('Helper Visible')
spotLightFolderControls.close()

intensity - (optional) 빛의 강도 (default=1)
distance - 조명의 최대 범위 (default=0, infinity)
angle - 방향에서 빛이 분산되는 최대 각도 (상한은 Math.PI/2)
penumbra - 스포트라이트 원뿔의 선명도(의역, docs) (default=0, 0~1)
decay - 빛의 거리에 따라 빛이 어두워지는 양

참고(legacy 를 사용한다면)
thee.js 버전이 165 이후에 WebGLRenderer.useLegacyLights 이 더 이상 사용되지 않는다.
이에 따라 intensity 의 값을 부여할 때 기존에 사용하던 값에 PI 를 곱하여 부여해야 기존에 같은 강도를 갖을 수 있다.

💻 전체 코드

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/addons/libs/stats.module.js'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'

const scene = new THREE.Scene()

const gridHelper = new THREE.GridHelper()
scene.add(gridHelper)

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.set(-1, 4, 2.5)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
})

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial())
plane.rotation.x = -Math.PI / 2
scene.add(plane)

const data = { color: 0x00ff00, lightColor: 0xffffff }

const geometry = new THREE.IcosahedronGeometry(1, 1)

const meshes = [
    new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: data.color })),
    new THREE.Mesh(geometry, new THREE.MeshNormalMaterial({ flatShading: true })),
    new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ color: data.color, flatShading: true })),
    new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: data.color, flatShading: true })),
]

meshes[0].position.set(-3, 1, 0)
meshes[1].position.set(-1, 1, 0)
meshes[2].position.set(1, 1, 0)
meshes[3].position.set(3, 1, 0)

scene.add(...meshes)

const gui = new GUI()

// AmbientLight

const ambientLight = new THREE.AmbientLight(data.lightColor, Math.PI)
ambientLight.visible = false
scene.add(ambientLight)

const ambientLightFolder = gui.addFolder('AmbientLight')
ambientLightFolder.add(ambientLight, 'visible')
ambientLightFolder.addColor(data, 'lightColor').onChange(() => {
    ambientLight.color.set(data.lightColor)
})
ambientLightFolder.add(ambientLight, 'intensity', 0, Math.PI)


// DirectionalLight

const directionalLight = new THREE.DirectionalLight(data.lightColor, Math.PI)
directionalLight.position.set(1, 1, 1)
scene.add(directionalLight)

const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight)
directionalLightHelper.visible = false
scene.add(directionalLightHelper)

const directionalLightFolder = gui.addFolder('DirectionalLight')
directionalLightFolder.add(directionalLight, 'visible')
directionalLightFolder.addColor(data, 'lightColor').onChange(() => {
    directionalLight.color.set(data.lightColor)
})
directionalLightFolder.add(directionalLight, 'intensity', 0, Math.PI * 10)

const directionalLightFolderControls = directionalLightFolder.addFolder('Controls')
directionalLightFolderControls.add(directionalLight.position, 'x', -1, 1, 0.001).onChange(() => {
    directionalLightHelper.update()
})
directionalLightFolderControls.add(directionalLight.position, 'y', -1, 1, 0.001).onChange(() => {
    directionalLightHelper.update()
})
directionalLightFolderControls.add(directionalLight.position, 'z', -1, 1, 0.001).onChange(() => {
    directionalLightHelper.update()
})
directionalLightFolderControls.add(directionalLightHelper, 'visible').name('Helper Visible')
directionalLightFolderControls.close()


// Pointlight

const pointLight = new THREE.PointLight(data.lightColor, Math.PI)
pointLight.position.set(2, 0, 0)
pointLight.visible = false
scene.add(pointLight)

const pointLightHelper = new THREE.PointLightHelper(pointLight)
pointLightHelper.visible = false
scene.add(pointLightHelper)

const pointLightFolder = gui.addFolder('Pointlight')
pointLightFolder.add(pointLight, 'visible')
pointLightFolder.addColor(data, 'lightColor').onChange(() => {
    pointLight.color.set(data.lightColor)
})
pointLightFolder.add(pointLight, 'intensity', 0, Math.PI * 10)

const pointLightFolderControls = pointLightFolder.addFolder('Controls')
pointLightFolderControls.add(pointLight.position, 'x', -10, 10)
pointLightFolderControls.add(pointLight.position, 'y', -10, 10)
pointLightFolderControls.add(pointLight.position, 'z', -10, 10)
pointLightFolderControls.add(pointLight, 'distance', 0, 20).onChange(() => {
    spotLightHelper.update()
})
pointLightFolderControls.add(pointLight, 'decay', 0, 10).onChange(() => {
    spotLightHelper.update()
})
pointLightFolderControls.add(pointLightHelper, 'visible').name('Helper Visible')
pointLightFolderControls.close()


// Spotlight

const spotLight = new THREE.SpotLight(data.lightColor, Math.PI)
spotLight.position.set(3, 2.5, 1)
spotLight.visible = false
//spotLight.target.position.set(5, 0, -5)
scene.add(spotLight)

const spotLightHelper = new THREE.SpotLightHelper(spotLight)
spotLightHelper.visible = false
scene.add(spotLightHelper)

const spotLightFolder = gui.addFolder('Spotlight')
spotLightFolder.add(spotLight, 'visible')
spotLightFolder.addColor(data, 'lightColor').onChange(() => {
    spotLight.color.set(data.lightColor)
})
spotLightFolder.add(spotLight, 'intensity', 0, Math.PI * 10)

const spotLightFolderControls = spotLightFolder.addFolder('Controls')
spotLightFolderControls.add(spotLight.position, 'x', -10, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight.position, 'y', -10, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight.position, 'z', -10, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'distance', 0, 20).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'decay', 0, 10).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'angle', 0, 1).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLight, 'penumbra', 0, 1, 0.001).onChange(() => {
    spotLightHelper.update()
})
spotLightFolderControls.add(spotLightHelper, 'visible').name('Helper Visible')
spotLightFolderControls.close()


const stats = new Stats()
document.body.appendChild(stats.dom)

const labels = document.querySelectorAll<HTMLDivElement>('.label')

let x, y
const v = new THREE.Vector3()

function animate() {
    requestAnimationFrame(animate)

    controls.update()

    for (let i = 0; i < 4; i++) {
        v.copy(meshes[i].position)
        v.project(camera)

        x = ((1 + v.x) / 2) * innerWidth - 50
        y = ((1 - v.y) / 2) * innerHeight

        labels[i].style.left = x + 'px'
        labels[i].style.top = y + 'px'
    }

    renderer.render(scene, camera)

    stats.update()
}

animate()
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글