Files
spark/examples/dynamic-lighting/index.html
T
2025-06-10 13:41:16 -07:00

244 lines
6.7 KiB
HTML

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spark • VFX Dynamic Lighting</title>
<style>
html,
body {
margin: 0;
height: 100%;
background-color: black;
}
#canvas {
position: absolute;
width: 100%;
height: 100%;
touch-action: none;
}
header {
position: absolute;
z-index: 10;
margin: 1rem 2rem;
}
label {
font-family: sans-serif;
font-size: 20px;
color: white;
cursor: pointer;
margin-right: 2rem;
margin-bottom: 0.5rem;
background-color: black;
border-radius: 10px;
}
header {
width: 100%;
padding: 0;
margin-left: 0;
padding-top: 20px;
text-align: center;
}
@media screen and (max-width: 60em) {
label {
font-size: 18px;
}
header {
margin-left: 1.2rem;
}
}
</style>
</head>
<body>
<header>
<label><input type="checkbox" id="debug" autocomplete="off">Debug Light SDFs</label>
<label><input type="checkbox" id="daynight" checked="checked" autocomplete="off"> Enable Lighting</label>
</header>
<canvas id="canvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"@sparkjsdev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import {
SplatMesh,
SplatEdit,
SparkRenderer,
SplatEditSdf,
SplatEditSdfType,
SplatEditRgbaBlendMode,
FpsMovement,
PointerControls,
} from "@sparkjsdev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
const canvas = document.getElementById("canvas");
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0.3, -2.5);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement)
// Create a SparkRenderer and add it to the scene to render all the Gsplats.
const spark = new SparkRenderer({ renderer });
scene.add(spark);
const splatURL = await getAssetFileURL("fireplace.spz");
const fireplace = new SplatMesh({ url: splatURL });
fireplace.quaternion.set(1, 0, 0, 0);
fireplace.position.set(0, -1, -10);
scene.add(fireplace);
const fpsMovement = new FpsMovement({ moveSpeed: 2 });
const pointerControls = new PointerControls({ canvas });
let night = true;
const lights = [];
// Debug SDF spheres
const helpers = [];
function createLight(scene, lightingLayer, position, color, radius, opacity) {
const light = new SplatEditSdf({
type: SplatEditSdfType.SPHERE,
color: color,
radius: radius,
opacity: opacity,
});
light.position.copy(position);
// create a wireframe helper
const helper = new THREE.Mesh(
new THREE.SphereGeometry(radius, 16, 16),
new THREE.MeshBasicMaterial({ wireframe: true, color: color }),
);
helper.position.copy(light.position);
helper.visible = false;
scene.add(helper);
helpers.push(helper);
lightingLayer.add(light);
lights.push(light);
}
// Create lighting layer
const emberLayer = new SplatEdit({
rgbaBlendMode: SplatEditRgbaBlendMode.ADD_RGBA,
sdfSmooth: 0.1,
softEdge: 0.8,
});
scene.add(emberLayer);
const lightingLayer = new SplatEdit({
rgbaBlendMode: SplatEditRgbaBlendMode.ADD_RGBA,
sdfSmooth: 0.1,
softEdge: 1.4,
});
scene.add(lightingLayer);
const ambientLayer = new SplatEdit({
rgbaBlendMode: SplatEditRgbaBlendMode.DARKEN,
sdfSmooth: 0.1,
softEdge: 0.05,
});
scene.add(ambientLayer);
// glowing embers at the base of the fire
createLight(
scene,
emberLayer,
new THREE.Vector3(0.5, -1.0, -10.5),
new THREE.Color(1, 0.6, 0.4),
0.75,
1,
);
// light in the fireplace
createLight(
scene,
lightingLayer,
new THREE.Vector3(0.3, -1.1, -10.6),
new THREE.Color(1, 0.95, 0.2),
1.6,
0,
);
// ambient light throughout the full room
createLight(
scene,
ambientLayer,
new THREE.Vector3(0, 1, -11),
new THREE.Color(1, 0.8, 0.6),
6,
0.8,
);
console.log(helpers);
let lastTime;
renderer.setAnimationLoop(time => {
const timeSeconds = time * 0.0005;
const deltaTime = timeSeconds - (lastTime ?? timeSeconds);
lastTime = timeSeconds;
// fpsMovement.update(deltaTime, camera);
pointerControls.update(deltaTime, camera);
camera.position.z = -2.5 - Math.sin(timeSeconds * 0.1) * 2;
renderer.render(scene, camera);
// Create flickering fire effect
const baseHue = 0.04; // Orange-red base hue
const fireHue = 0.03; // Yellow base hue
const hueVariation = 0.03; // Slight variation in hue
// Add some randomness to the flicker
const randomFlicker = Math.sin(timeSeconds * 4) * 0.5 + 0.5;
// we'll combine these to make a more natural flickering effect
const mediumFlicker = Math.sin(timeSeconds * 13) * 0.1 + 0.1; // Medium flicker
const fastFlicker = Math.sin(timeSeconds * 20) * 0.1 + 0.1; // Fast flicker
const slowFlicker = Math.sin(timeSeconds * 6) * 0.04 + 0.5; // Slow base flicker
// Combine the flickers
const combinedFlicker = (slowFlicker + mediumFlicker + fastFlicker) / 3;
for (let i = 0; i < lights.length - 1; i++) {
const h = baseHue + combinedFlicker * hueVariation; // Slightly varying orange-red hue
const s = 0.5 + randomFlicker * 0.3; // High saturation with slight variation
const l = 0.5 + combinedFlicker * 0.2; // Varying brightness
lights[i].color.setHSL(h, s, l);
lights[i].visible = night;
}
lights[lights.length - 1].visible = night;
lights[lights.length - 1].color.setHSL(baseHue, 0.5, combinedFlicker);
});
document.getElementById('debug').addEventListener('change', ev => {
const visible = ev.target.checked;
for (let helper of helpers) {
helper.visible = visible;
}
});
document.getElementById('daynight').addEventListener('change', ev => {
night = !night;
});
</script>
</body>
</html>