Untuk lighting yang mengikuti aturan fisika, berikut beberapa property yang harus diatur:
- renderer.physicallyCorrectLights = true, defaulnya adalah false.
- Mengatur toneMappingExposure.
- Mengatur nilai Gamma input dan output.
- Mengatur toneMapping Type.
- Mengatur nilai power untuk setiap lighting.
File HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2666677/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2666677/RectAreaLightUniformsLib.js"></script>
File CSS
body{
padding: 0;
margin: 0;
}
File Javascript
var scene, camera, renderer, clock, params, lights, materials;
init();
function init(){
const assetPath = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/2666677/";
clock = new THREE.Clock();
scene = new THREE.Scene();
const envMap = new THREE.CubeTextureLoader()
.setPath(`${assetPath}skybox3_`)
.load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
scene.background = envMap;
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set(0, 6, 15);//wide position
camera.lookAt(0,0,0);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.physicallyCorrectLights = true;
renderer.toneMappingExposure = Math.pow( 0.84, 5.0 );
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild( renderer.domElement );
const controls = new THREE.OrbitControls(camera, renderer.domElement);
const geometry = new THREE.SphereGeometry(1, 20, 15);
const material = new THREE.MeshStandardMaterial({envMap: envMap});
const sphere = new THREE.Mesh(geometry, material);
sphere.castShadow = true;
sphere.receiveShadow = true;
const planeGeometry = new THREE.PlaneGeometry(15, 15);
const planeMaterial = new THREE.MeshStandardMaterial();
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = - Math.PI/2;
plane.position.y = -2;
plane.receiveShadow = true;
scene.add(plane);
materials = [material, planeMaterial];
let ball;
let y = 0;
for(let x=-3; x<=3; x+=2){
for(let z=-3; z<=3; z+=2){
ball = sphere.clone();
ball.position.set(x,y,z);
scene.add(ball);
}
}
const ambient = new THREE.HemisphereLight(0xffffff, 0xaaaa66, 0.35);
scene.add(ambient);
//Add lights here
lights = {};
lights.ambient = ambient;
lights.spot = new THREE.SpotLight(0xffffff,1,20,0.88,0.5,2);
lights.spot.power = 1700;
lights.spot.position.set(1,10,1);
lights.spot.castShadow = true;
lights.spot.shadow.camera.near = 3;
lights.spot.shadow.camera.far = 30;
lights.spot.shadow.mapSize.width = 1024;
lights.spot.shadow.mapSize.height = 1024;
lights.spotCameraHelper = new THREE.CameraHelper( lights.spot.shadow.camera );
lights.spotCameraHelper.visible = false;
scene.add(lights.spotCameraHelper);
lights.spotHelper = new THREE.SpotLightHelper(lights.spot);
lights.spotHelper.visible = false;
scene.add(lights.spotHelper);
scene.add(lights.spot);
params = {
exposure: 0.84,
luminance: 1700,
ambient: 0.1,
toneMapping: THREE.ReinhardToneMapping,
luminanceOpts: {
"110000 lm (1000W)": 110000,
"10000 lm (500W)": 10000,
"3500 lm (300W)": 3500,
"1700 lm (100W)": 1700,
"800 lm (60W)": 800,
"400 lm (40W)": 400,
"180 lm (25W)": 180,
"20 lm (4W)": 20,
"Off": 0
},
ambientOpts: {
"0.0001 lx (Moonless Night)": 0.0001,
"0.002 lx (Night Airglow)": 0.002,
"0.1 lx (Half moon)": 0.1,
"0.2 lx (Full Moon)": 0.2,
"0.3 lx (City Twilight)": 0.3,
"0.4 lx (Living Room)": 0.4,
"0.5 lx (Very Overcast)": 0.5,
"1 lx (Office Room)": 1,
"2 lx (Sunrise/Sunset)": 2,
"5 lx (Overcast)": 5,
"30 lx (Daylight)": 30,
"50 lx (Direct Sun)": 50
},
toneMappingOpts:{
"None" : THREE.NoToneMapping,
"Linear" : THREE.LinearToneMapping,
"Rienhard" : THREE.ReinhardToneMapping,
"Uncharted2" : THREE.Uncharted2ToneMapping,
"Cineon" : THREE.CineonToneMapping,
"ACESFilmic" : THREE.ACESFilmicToneMapping
}
}
const gui = new dat.GUI();
gui.add(params, 'exposure', 0, 1).onChange( value => renderer.toneMappingExposure = Math.pow( value, 5.0 ) );
gui.add(params, 'luminance', params.luminanceOpts).onChange(value => { lights.spot.power = value });
gui.add(params, 'ambient', params.ambientOpts).onChange(value => { lights.ambient.intensity = value });
gui.add(params, 'toneMapping', params.toneMappingOpts).onChange(value => {
renderer.toneMapping = Number(value);
materials.forEach( material =>{
material.needsUpdate = true;
});
});
window.addEventListener( 'resize', resize, false);
update();
}
function update(){
requestAnimationFrame( update );
renderer.render( scene, camera );
const time = clock.getElapsedTime();
const delta = Math.sin(time)*5;
lights.spot.position.x = delta;
lights.spotHelper.update();
lights.spotCameraHelper.update();
}
function resize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
Pembahasan Code
Mengatur property pada renderer untuk physically correct lights.
renderer.physicallyCorrectLights = true;
renderer.toneMappingExposure = Math.pow( 0.84, 5.0 );
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.toneMapping = THREE.ReinhardToneMapping;
Untuk dokumentasi property gamma, kunjungi https://learnopengl.com/Advanced-Lighting/Gamma-Correction
Mengatur Power dari Light
lights.spot.power = 1700;
code diatas akan mengatur power dalam lumen, atau setara dengan 100w.
Untuk tabel perbandingan lumen dan watt, silakan kunjungi https://www.rapidtables.com/calc/light/lumen-to-watt-calculator.html
Silakan bereksperimen dengan mengubah parameter pada widget dibawah.
Untuk code lengkap kunjungi https://codepen.io/SkillPlus/pen/eYRbYBK