Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@
<a href="/examples/glsl/index.html" class="example-link">GLSL Shaders</a>
<a href="/examples/debug-color/index.html" class="example-link">Debug Coloring</a>
<a href="/examples/depth-of-field/index.html" class="example-link">Depth of Field</a>
<a href="/examples/splat-texture/index.html" class="example-link">Splat Texture</a>
<a href="/examples/editor/index.html" class="example-link">Editor</a>
</div>
<div class="content">
Expand Down
8 changes: 8 additions & 0 deletions examples/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,13 @@
"valley.spz": {
"url": "https://sparkjs.dev/assets/splats/valley.spz",
"directory": "splats"
},
"star.png": {
"url": "https://sparkjs.dev/assets/images/star.png",
"directory": "images"
},
"heart.png": {
"url": "https://sparkjs.dev/assets/images/heart.png",
"directory": "images"
}
}
24 changes: 19 additions & 5 deletions examples/depth-of-field/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,29 @@
document.body.appendChild(renderer.domElement)

const spark = new SparkRenderer({
renderer,
apertureAngle: 0.02,
focalDistance: 5.0,
renderer,
apertureAngle: 0.02,
focalDistance: 5.0,
});
scene.add(spark);

const apertureSize = {
apertureSize: 0.1,
};
function updateApertureAngle() {
if (spark.focalDistance > 0) {
spark.apertureAngle = 2 * Math.atan(0.5 * apertureSize.apertureSize / spark.focalDistance);
} else {
spark.apertureAngle = 0.0;
}
}
updateApertureAngle();

const gui = new GUI({ title: "DoF settings" });
gui.add(spark, "focalDistance", 0.1, 15, 0.1);
gui.add(spark, "apertureAngle", 0.0, 0.01 * Math.PI, 0.001);
gui.add(spark, "focalDistance", 0, 15, 0.01).name("Focal plane dist")
.onChange(updateApertureAngle);
gui.add(apertureSize, "apertureSize", 0, 0.4, 0.01).name("Aperture size")
.onChange(updateApertureAngle);

const splatURL = await getAssetFileURL("valley.spz");
const background = new SplatMesh({ url: splatURL });
Expand Down
75 changes: 50 additions & 25 deletions examples/editor/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GUI } from "lil-gui";
import { constructGrid, SparkControls, SparkRenderer, SplatMesh, textSplats, dyno, transcodeSpz, isPcSogs } from "@sparkjsdev/spark";
import { constructGrid, SparkControls, SparkRenderer, SplatMesh, textSplats, dyno, transcodeSpz, isMobile, isPcSogs } from "@sparkjsdev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";

const scene = new THREE.Scene();
Expand Down Expand Up @@ -113,7 +113,7 @@
const gui = new GUI({
title: "Settings",
container: document.getElementById("main-gui")
}).close();
});
const secondGui = new GUI({
title: "Splats",
container: document.getElementById("second-gui")
Expand All @@ -134,7 +134,7 @@
};

const guiOptions = {
highDevicePixel: false,
highDevicePixel: !isMobile(),
stats: false,
resetOnLoad: true,
loadOffset: 0,
Expand Down Expand Up @@ -462,30 +462,55 @@
controls.pointerControls.reverseSwipe = value;
});

gui.add(guiOptions, "highDevicePixel").name("High DPI").onChange((value) => {
function setHighDpi(value) {
renderer.setPixelRatio(value ? window.devicePixelRatio : 1);
const width = canvas.clientWidth;
const height = canvas.clientHeight;
renderer.setSize(width, height, false);
console.log("Render size", canvas.width, canvas.height);
}
setHighDpi(guiOptions.highDevicePixel);

gui.add(guiOptions, "highDevicePixel").name("High DPI").onChange((value) => {
setHighDpi(value);
});
gui.add(guiOptions, "stats").name("Show frame stats").onChange((value) => {
stats.dom.style.display = value ? "block" : "none";
});
gui.add(spark.defaultView, "sortRadial").name("Radial sort").listen();
gui.add(grid, "opacity", 0, 1, 0.01).name("Grid opacity").listen();
gui.add({
logFocalDistance: 0.0,
}, "logFocalDistance", -2, 2, 0.01).name("Ln(Focal distance)").onChange((value) => {
spark.focalDistance = Math.exp(value);
});
gui.add(spark, "apertureAngle", 0, 0.01 * Math.PI, 0.001).name("Aperture angle").listen();

const debugFolder = gui.addFolder("Debug").close();
const normalColor = dyno.dynoBool(false);
gui.add(normalColor, "value").name("Normal color").onChange(() => updateFrameSplats());

gui.add(spark, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
gui.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
gui.add(spark, "preBlurAmount", 0, 2, 0.1).name("Blur amount (no AA)");
gui.add(spark, "blurAmount", 0, 2, 0.1).name("Blur amount (AA)");
debugFolder.add(normalColor, "value").name("Normal color").onChange(() => updateFrameSplats());

debugFolder.add(spark, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
debugFolder.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
debugFolder.add(spark, "preBlurAmount", 0, 2, 0.1).name("Blur amount (no AA)").listen();
debugFolder.add(spark, "blurAmount", 0, 2, 0.1).name("Blur amount (AA)").listen();
debugFolder.add({
nonAA: () => {
spark.preBlurAmount = 0.3;
spark.blurAmount = 0.0;
},
}, "nonAA").name("Non-AA preset");
debugFolder.add({
AA: () => {
spark.preBlurAmount = 0.0;
spark.blurAmount = 0.3;
},
}, "AA").name("AA preset");
debugFolder.add(spark, "renderScale", 0.1, 2.0, 0.1).name("Render scale");

const splatsFolder = secondGui.addFolder("Files");

const editFolder = gui.addFolder("Edit Splats").close();
const clipFolder = gui.addFolder("Clip Splats").close();

function updateFrameSplats() {
frame.children.forEach((child) => {
Expand All @@ -502,13 +527,13 @@
const clipMaxY = dyno.dynoFloat(5);
const clipMinZ = dyno.dynoFloat(-5);
const clipMaxZ = dyno.dynoFloat(5);
editFolder.add(clipEnable, "value").name("Enable clip").onChange(() => updateFrameSplats());
editFolder.add(clipMinX, "value", -5, 5, 0.01).name("Min X").onChange(() => updateFrameSplats());
editFolder.add(clipMaxX, "value", -5, 5, 0.01).name("Max X").onChange(() => updateFrameSplats());
editFolder.add(clipMinY, "value", -5, 5, 0.01).name("Min Y").onChange(() => updateFrameSplats());
editFolder.add(clipMaxY, "value", -5, 5, 0.01).name("Max Y").onChange(() => updateFrameSplats());
editFolder.add(clipMinZ, "value", -5, 5, 0.01).name("Min Z").onChange(() => updateFrameSplats());
editFolder.add(clipMaxZ, "value", -5, 5, 0.01).name("Max Z").onChange(() => updateFrameSplats());
clipFolder.add(clipEnable, "value").name("Enable clip").onChange(() => updateFrameSplats());
clipFolder.add(clipMinX, "value", -50, 50, 0.01).name("Min X").onChange(() => updateFrameSplats());
clipFolder.add(clipMaxX, "value", -50, 50, 0.01).name("Max X").onChange(() => updateFrameSplats());
clipFolder.add(clipMinY, "value", -50, 50, 0.01).name("Min Y").onChange(() => updateFrameSplats());
clipFolder.add(clipMaxY, "value", -50, 50, 0.01).name("Max Y").onChange(() => updateFrameSplats());
clipFolder.add(clipMinZ, "value", -50, 50, 0.01).name("Min Z").onChange(() => updateFrameSplats());
clipFolder.add(clipMaxZ, "value", -50, 50, 0.01).name("Max Z").onChange(() => updateFrameSplats());

function makeWorldModifier(mesh) {
const context = mesh.context;
Expand Down Expand Up @@ -548,7 +573,7 @@
});
}

const writeFolder = secondGui.addFolder("Write Gsplats").close();
const exportFolder = secondGui.addFolder("Export Gsplats").close();
const writeOptions = {
filename: "gsplats",
trimOpacity: true,
Expand Down Expand Up @@ -591,12 +616,12 @@
URL.revokeObjectURL(url);
},
};
writeFolder.add(writeOptions, "filename").name("Filename").listen();
writeFolder.add(writeOptions, "trimOpacity").name("Trim low opacity");
writeFolder.add(writeOptions, "trimOpacityThreshold").name("Trim opacity <= 0..1");
writeFolder.add(writeOptions, "maxSh", 0, 3, 1).name("Max spherical harmonics");
writeFolder.add(writeOptions, "fractionalBits", 6, 24, 1).name("Fractional bits");
writeFolder.add(writeOptions, "writeSpz").name("Create .spz and download");
exportFolder.add(writeOptions, "filename").name("Filename").listen();
exportFolder.add(writeOptions, "trimOpacity").name("Trim low opacity");
exportFolder.add(writeOptions, "trimOpacityThreshold").name("Trim opacity <= 0..1");
exportFolder.add(writeOptions, "maxSh", 0, 3, 1).name("Max spherical harmonics");
exportFolder.add(writeOptions, "fractionalBits", 6, 24, 1).name("Fractional bits");
exportFolder.add(writeOptions, "writeSpz").name("Create .spz and download");

function makeInstructions() {
const instructions = textSplats({
Expand Down
166 changes: 166 additions & 0 deletions examples/splat-texture/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spark • Splat Texture</title>
<style>
body {
margin: 0;
}
canvas {
touch-action: none;
}
</style>
</head>

<body>
<script type="importmap">
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"lil-gui": "/examples/js/vendor/lil-gui/dist/lil-gui.esm.js",
"@sparkjsdev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SparkRenderer, SplatMesh, SparkControls } from "@sparkjsdev/spark";
import GUI from "lil-gui";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement)

function imgToRgba(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, img.width, img.height);
const rgba = new Uint8Array(imageData.data.buffer);
return { rgba, width: img.width, height: img.height };
}

const textureLoader = new THREE.TextureLoader();
const starUrl = await getAssetFileURL("star.png");
const star = imgToRgba((await textureLoader.loadAsync(starUrl)).image);
const heartUrl = await getAssetFileURL("heart.png");
const heart = imgToRgba((await textureLoader.loadAsync(heartUrl)).image);

const splatTexLayers = 32;
const texData = new Uint8Array(4 * star.width * star.height * splatTexLayers);
const texData2 = new Uint8Array(4 * star.width * star.height * splatTexLayers);
for (let z = 0; z < splatTexLayers; z++) {
const t = z / (splatTexLayers - 1);
const starOpacity = Math.max((t - 0.5) / 0.5, 0.0);
const heartOpacity = Math.max((0.5 - t) / 0.5, 0.0);
const gaussOpacity = Math.max(1 - Math.abs(t - 0.5) / 0.5, 0.0);
for (let y = 0; y < star.height; y++) {
for (let x = 0; x < star.width; x++) {
const inIndex = (y * star.width) + x;
const outIndex = ((z * star.height) + y) * star.width + x;
const o4 = outIndex * 4;
const i4 = inIndex * 4;
const deltaX = x / star.width - 0.5;
const deltaY = y / star.height - 0.5;
const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 0.5;
const inside = (dist < 1.0) ? 255 : 0;
let r = t * star.rgba[i4] + (1 - t) * inside;
let g = t * star.rgba[i4 + 1] + (1 - t) * inside;
let b = t * star.rgba[i4 + 2] + (1 - t) * inside;
let a = t * star.rgba[i4 + 3] + (1 - t) * inside;
texData[o4] = Math.max(0, Math.min(255, Math.round(r)));
texData[o4 + 1] = Math.max(0, Math.min(255, Math.round(g)));
texData[o4 + 2] = Math.max(0, Math.min(255, Math.round(b)));
texData[o4 + 3] = Math.max(0, Math.min(255, Math.round(a)));

r = starOpacity * star.rgba[i4] + heartOpacity * heart.rgba[i4] + inside * gaussOpacity;
g = starOpacity * star.rgba[i4 + 1] + heartOpacity * heart.rgba[i4 + 1] + inside * gaussOpacity;
b = starOpacity * star.rgba[i4 + 2] + heartOpacity * heart.rgba[i4 + 2] + inside * gaussOpacity;
a = starOpacity * star.rgba[i4 + 3] + heartOpacity * heart.rgba[i4 + 3] + inside * gaussOpacity;
texData2[o4] = Math.max(0, Math.min(255, Math.round(r)));
texData2[o4 + 1] = Math.max(0, Math.min(255, Math.round(g)));
texData2[o4 + 2] = Math.max(0, Math.min(255, Math.round(b)));
texData2[o4 + 3] = Math.max(0, Math.min(255, Math.round(a)));
}
}
}
const texture = new THREE.Data3DTexture(texData, star.width, star.height, splatTexLayers);
texture.needsUpdate = true;
const texture2 = new THREE.Data3DTexture(texData2, star.width, star.height, splatTexLayers);
texture2.needsUpdate = true;

const spark = new SparkRenderer({
renderer,
maxStdDev: 1.0,
focalDistance: 0,
});
const splatTexture = {
enable: true,
texture: texture,
near: 1.0,
far: 15,
mid: 5,
};
spark.splatTexture = splatTexture;
scene.add(spark);

const gui = new GUI({ title: "DoF settings" });
const selectedTexture = {
selection: "texture",
};
gui.add(selectedTexture, "selection", ["none", "texture", "texture2"]).name("Splat texture").onChange(() => {
splatTexture.enable = true;
if (selectedTexture.selection === "texture") {
splatTexture.texture = texture;
} else if (selectedTexture.selection === "texture2") {
splatTexture.texture = texture2;
} else {
splatTexture.enable = false;
}
});
gui.add(spark, "maxStdDev", 0.1, 3, 0.1);
gui.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff");

const apertureSize = {
apertureSize: 0.1,
};
function updateApertureAngle() {
splatTexture.mid = spark.focalDistance;
splatTexture.near = spark.focalDistance / 5.0;
splatTexture.far = spark.focalDistance * 5.0;
if (spark.focalDistance > 0) {
spark.apertureAngle = 2 * Math.atan(0.5 * apertureSize.apertureSize / spark.focalDistance);
} else {
spark.apertureAngle = 0.0;
}
}
updateApertureAngle();

gui.add(spark, "focalDistance", 0, 15, 0.01).name("Focal plane dist")
.onChange(updateApertureAngle);
gui.add(apertureSize, "apertureSize", 0, 1, 0.01).name("Aperture size")
.onChange(updateApertureAngle);

const splatURL = await getAssetFileURL("valley.spz");
const background = new SplatMesh({ url: splatURL });
background.quaternion.set(1, 0, 0, 0);
background.scale.setScalar(0.5);
scene.add(background);

const controls = new SparkControls({ canvas: renderer.domElement });
renderer.setAnimationLoop(function animate(time) {
controls.update(camera);
renderer.render(scene, camera);
});
</script>
</body>

</html>
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ <h2>Examples</h2>
<li><a href="/examples/glsl/">GLSL Shaders</a></li>
<li><a href="/examples/debug-color/">Debug Coloring</a></li>
<li><a href="/examples/depth-of-field/">Depth of Field</a></li>
<li><a href="/examples/splat-texture/">Splat Texture</a></li>
<li><a href="/examples/editor/">Editor</a></li>
<li><a href="/examples/viewer/">Viewer</a></li>
</ul>
Expand Down
Loading