You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
8.2 KiB
271 lines
8.2 KiB
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>three.js webgl - progressive lightmap accumulation</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
<link type="text/css" rel="stylesheet" href="main.css">
|
|
</head>
|
|
<body>
|
|
<div id="container"></div>
|
|
<div id="info">
|
|
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Progressive Lightmaps by <a href="https://github.com/zalo" target="_blank" rel="noopener">zalo</a><br/>
|
|
[Inspired by <a href="http://madebyevan.com/shaders/lightmap/" target="_blank" rel="noopener">evanw's Lightmap Generation</a>]
|
|
</div>
|
|
|
|
<!-- Import maps polyfill -->
|
|
<!-- Remove this when import maps will be widely supported -->
|
|
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
|
|
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "../build/three.module.js",
|
|
"three/addons/": "./jsm/"
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script type="module">
|
|
import * as THREE from 'three';
|
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
import { TransformControls } from 'three/addons/controls/TransformControls.js';
|
|
import { ProgressiveLightMap } from 'three/addons/misc/ProgressiveLightMap.js';
|
|
|
|
// ShadowMap + LightMap Res and Number of Directional Lights
|
|
const shadowMapRes = 512, lightMapRes = 1024, lightCount = 8;
|
|
let camera, scene, renderer, controls, control, control2,
|
|
object = new THREE.Mesh(), lightOrigin = null, progressiveSurfacemap;
|
|
const dirLights = [], lightmapObjects = [];
|
|
const params = { 'Enable': true, 'Blur Edges': true, 'Blend Window': 200,
|
|
'Light Radius': 50, 'Ambient Weight': 0.5, 'Debug Lightmap': false };
|
|
init();
|
|
createGUI();
|
|
animate();
|
|
|
|
function init() {
|
|
|
|
// renderer
|
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
renderer.shadowMap.enabled = true;
|
|
document.body.appendChild( renderer.domElement );
|
|
|
|
// camera
|
|
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
|
|
camera.position.set( 0, 100, 200 );
|
|
camera.name = 'Camera';
|
|
|
|
// scene
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color( 0x949494 );
|
|
scene.fog = new THREE.Fog( 0x949494, 1000, 3000 );
|
|
|
|
// progressive lightmap
|
|
progressiveSurfacemap = new ProgressiveLightMap( renderer, lightMapRes );
|
|
|
|
// directional lighting "origin"
|
|
lightOrigin = new THREE.Group();
|
|
lightOrigin.position.set( 60, 150, 100 );
|
|
scene.add( lightOrigin );
|
|
|
|
// transform gizmo
|
|
control = new TransformControls( camera, renderer.domElement );
|
|
control.addEventListener( 'dragging-changed', ( event ) => {
|
|
|
|
controls.enabled = ! event.value;
|
|
|
|
} );
|
|
control.attach( lightOrigin );
|
|
scene.add( control );
|
|
|
|
// create 8 directional lights to speed up the convergence
|
|
for ( let l = 0; l < lightCount; l ++ ) {
|
|
|
|
const dirLight = new THREE.DirectionalLight( 0xffffff, 1.0 / lightCount );
|
|
dirLight.name = 'Dir. Light ' + l;
|
|
dirLight.position.set( 200, 200, 200 );
|
|
dirLight.castShadow = true;
|
|
dirLight.shadow.camera.near = 100;
|
|
dirLight.shadow.camera.far = 5000;
|
|
dirLight.shadow.camera.right = 150;
|
|
dirLight.shadow.camera.left = - 150;
|
|
dirLight.shadow.camera.top = 150;
|
|
dirLight.shadow.camera.bottom = - 150;
|
|
dirLight.shadow.mapSize.width = shadowMapRes;
|
|
dirLight.shadow.mapSize.height = shadowMapRes;
|
|
lightmapObjects.push( dirLight );
|
|
dirLights.push( dirLight );
|
|
|
|
}
|
|
|
|
// ground
|
|
const groundMesh = new THREE.Mesh(
|
|
new THREE.PlaneGeometry( 600, 600 ),
|
|
new THREE.MeshPhongMaterial( { color: 0xffffff, depthWrite: true } )
|
|
);
|
|
groundMesh.position.y = - 0.1;
|
|
groundMesh.rotation.x = - Math.PI / 2;
|
|
groundMesh.name = 'Ground Mesh';
|
|
lightmapObjects.push( groundMesh );
|
|
scene.add( groundMesh );
|
|
|
|
// model
|
|
function loadModel() {
|
|
|
|
object.traverse( function ( child ) {
|
|
|
|
if ( child.isMesh ) {
|
|
|
|
child.name = 'Loaded Mesh';
|
|
child.castShadow = true;
|
|
child.receiveShadow = true;
|
|
child.material = new THREE.MeshPhongMaterial();
|
|
|
|
// This adds the model to the lightmap
|
|
lightmapObjects.push( child );
|
|
progressiveSurfacemap.addObjectsToLightMap( lightmapObjects );
|
|
|
|
} else {
|
|
|
|
child.layers.disableAll(); // Disable Rendering for this
|
|
|
|
}
|
|
|
|
} );
|
|
scene.add( object );
|
|
object.scale.set( 2, 2, 2 );
|
|
object.position.set( 0, - 16, 0 );
|
|
control2 = new TransformControls( camera, renderer.domElement );
|
|
control2.addEventListener( 'dragging-changed', ( event ) => {
|
|
|
|
controls.enabled = ! event.value;
|
|
|
|
} );
|
|
control2.attach( object );
|
|
scene.add( control2 );
|
|
const lightTarget = new THREE.Group();
|
|
lightTarget.position.set( 0, 20, 0 );
|
|
for ( let l = 0; l < dirLights.length; l ++ ) {
|
|
|
|
dirLights[ l ].target = lightTarget;
|
|
|
|
}
|
|
|
|
object.add( lightTarget );
|
|
|
|
if ( typeof TESTING !== 'undefined' ) {
|
|
|
|
for ( let i = 0; i < 300; i ++ ) {
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const manager = new THREE.LoadingManager( loadModel );
|
|
const loader = new GLTFLoader( manager );
|
|
loader.load( 'models/gltf/ShadowmappableMesh.glb', function ( obj ) {
|
|
|
|
object = obj.scene.children[ 0 ];
|
|
|
|
} );
|
|
|
|
// controls
|
|
controls = new OrbitControls( camera, renderer.domElement );
|
|
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
|
|
controls.dampingFactor = 0.05;
|
|
controls.screenSpacePanning = true;
|
|
controls.minDistance = 100;
|
|
controls.maxDistance = 500;
|
|
controls.maxPolarAngle = Math.PI / 1.5;
|
|
controls.target.set( 0, 100, 0 );
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
|
|
}
|
|
|
|
function createGUI() {
|
|
|
|
const gui = new GUI( { name: 'Accumulation Settings' } );
|
|
gui.add( params, 'Enable' );
|
|
gui.add( params, 'Blur Edges' );
|
|
gui.add( params, 'Blend Window', 1, 500 ).step( 1 );
|
|
gui.add( params, 'Light Radius', 0, 200 ).step( 10 );
|
|
gui.add( params, 'Ambient Weight', 0, 1 ).step( 0.1 );
|
|
gui.add( params, 'Debug Lightmap' );
|
|
|
|
}
|
|
|
|
function onWindowResize() {
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
}
|
|
|
|
function render() {
|
|
|
|
// Update the inertia on the orbit controls
|
|
controls.update();
|
|
|
|
// Accumulate Surface Maps
|
|
if ( params[ 'Enable' ] ) {
|
|
|
|
progressiveSurfacemap.update( camera, params[ 'Blend Window' ], params[ 'Blur Edges' ] );
|
|
|
|
if ( ! progressiveSurfacemap.firstUpdate ) {
|
|
|
|
progressiveSurfacemap.showDebugLightmap( params[ 'Debug Lightmap' ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Manually Update the Directional Lights
|
|
for ( let l = 0; l < dirLights.length; l ++ ) {
|
|
|
|
// Sometimes they will be sampled from the target direction
|
|
// Sometimes they will be uniformly sampled from the upper hemisphere
|
|
if ( Math.random() > params[ 'Ambient Weight' ] ) {
|
|
|
|
dirLights[ l ].position.set(
|
|
lightOrigin.position.x + ( Math.random() * params[ 'Light Radius' ] ),
|
|
lightOrigin.position.y + ( Math.random() * params[ 'Light Radius' ] ),
|
|
lightOrigin.position.z + ( Math.random() * params[ 'Light Radius' ] ) );
|
|
|
|
} else {
|
|
|
|
// Uniform Hemispherical Surface Distribution for Ambient Occlusion
|
|
const lambda = Math.acos( 2 * Math.random() - 1 ) - ( 3.14159 / 2.0 );
|
|
const phi = 2 * 3.14159 * Math.random();
|
|
dirLights[ l ].position.set(
|
|
( ( Math.cos( lambda ) * Math.cos( phi ) ) * 300 ) + object.position.x,
|
|
Math.abs( ( Math.cos( lambda ) * Math.sin( phi ) ) * 300 ) + object.position.y + 20,
|
|
( Math.sin( lambda ) * 300 ) + object.position.z
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Render Scene
|
|
renderer.render( scene, camera );
|
|
|
|
}
|
|
|
|
function animate() {
|
|
|
|
requestAnimationFrame( animate );
|
|
render();
|
|
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|