mirror of
https://github.com/storytold/spark.git
synced 2026-10-09 00:09:53 +00:00
252 lines
7.0 KiB
HTML
252 lines
7.0 KiB
HTML
<!DOCTYPE html>
|
|
<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);
|
|
|
|
window.addEventListener('resize', onWindowResize, false);
|
|
function onWindowResize() {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
}
|
|
|
|
// 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> |