Skip to content
Open
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
49 changes: 44 additions & 5 deletions examples/debug-color/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh, PackedSplats, dyno, modifiers } from "@sparkjsdev/spark";
import { SplatLoader } from "@sparkjsdev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";

const scene = new THREE.Scene();
Expand All @@ -42,19 +42,58 @@
renderer.setSize(window.innerWidth, window.innerHeight);
}

const loader = new SplatLoader();

let splatURL = await getAssetFileURL("butterfly.spz");
const butterfly = new SplatMesh({ url: splatURL });
const butterfly = await loader.loadAsync(splatURL);
butterfly.scale.setScalar(0.5);
butterfly.quaternion.set(1, 0, 0, 0);
butterfly.position.set(-0.5, 0, -1.5);
modifiers.setWorldNormalColor(butterfly);
butterfly.setShaderHooks({
vertex: {
global: /*glsl*/`
vec3 gsplatNormal(vec3 scales, vec4 quaternion) {
float minScale = min(scales.x, min(scales.y, scales.z));
vec3 normal;
if (scales.z == minScale) {
normal = vec3(0.0, 0.0, 1.0);
} else if (scales.y == minScale) {
normal = vec3(0.0, 1.0, 0.0);
} else {
normal = vec3(1.0, 0.0, 0.0);
}
return quatVec(quaternion, normal);
}

vec3 objectNormal;
`,
objectModifier: /*glsl*/`
// Determine normal in object space
objectNormal = gsplatNormal(scales, quaternion);
`,
splatColor: /*glsl*/`
// Transform normal into world space
vec3 worldNormal = normalize(mat3(modelMatrix) * objectNormal);
// TODO: Handle flipped normals
rgba.rgb = 0.5 + objectNormal * 0.5;
`
}
})
scene.add(butterfly);

splatURL = await getAssetFileURL("butterfly-ai.spz");
const butterfly2 = new SplatMesh({ url: splatURL });
const butterfly2 = await loader.loadAsync(splatURL);
butterfly2.quaternion.set(1, 0, 0, 0);
butterfly2.position.set(0.5, 0, -1.5);
modifiers.setDepthColor(butterfly2, 1, 2, true);
butterfly2.setShaderHooks({
vertex: {
splatColor: /*glsl*/`
float clamped = clamp(-viewCenter.z, 1.0, 2.0);
float depth = 1.0 - (log2(clamped + 1.0) - log2(1.0 + 1.0)) / (log2(2.0 + 1.0) - log2(1.0 + 1.0));
rgba.rgb = vec3(depth);
`
}
})
scene.add(butterfly2);

let lastTime;
Expand Down
62 changes: 44 additions & 18 deletions examples/depth-of-field/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
</script>
<script type="module">
import * as THREE from "three";
import { SparkRenderer, SplatMesh, SparkControls } from "@sparkjsdev/spark";
import { SplatLoader } from "@sparkjsdev/spark";
import GUI from "lil-gui";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
import { SparkControls } from "/examples/js/controls.js";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
Expand All @@ -44,37 +45,62 @@
renderer.setSize(window.innerWidth, window.innerHeight);
}

const spark = new SparkRenderer({
renderer,
apertureAngle: 0.02,
focalDistance: 5.0,
const splatLoader = new SplatLoader();
const splatURL = await getAssetFileURL("valley.spz");
const background = await splatLoader.loadAsync(splatURL);
background.quaternion.set(1, 0, 0, 0);
background.scale.setScalar(0.5);
scene.add(background);

const depthOfFieldUniforms = {
// Depth-of-field distance to focal plane
focalDistance: { type: 'float', value: 0.0 },
// Full-width angle of aperture opening (in radians)
apertureAngle: { type: 'float', value: 0.0 },
};
background.setShaderHooks({
vertex: {
uniforms: depthOfFieldUniforms
},
onBeforeCompile: (program, renderer) => {
program.vertexShader = program.vertexShader.replace(/a \+= blurAmount;\n\s+d \+= blurAmount;/,
/*glsl*/`
float fullBlurAmount = blurAmount;
if ((focalDistance > 0.0) && (apertureAngle > 0.0)) {
float focusRadius = maxPixelRadius;
if (viewCenter.z < 0.0) {
float focusBlur = abs((-viewCenter.z - focalDistance) / viewCenter.z);
float apertureRadius = focal.x * tan(0.5 * apertureAngle);
focusRadius = focusBlur * apertureRadius;
}
fullBlurAmount = clamp(sqr(focusRadius), blurAmount, sqr(maxPixelRadius));
}
a += fullBlurAmount;
d += fullBlurAmount;
`);
}
});
scene.add(spark);

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

const gui = new GUI({ title: "DoF settings" });
gui.add(spark, "focalDistance", 0, 15, 0.01).name("Focal plane dist")
gui.add(settings, "focalDistance", 0, 15, 0.01).name("Focal plane dist")
.onChange(updateApertureAngle);
gui.add(apertureSize, "apertureSize", 0, 0.4, 0.01).name("Aperture size")
gui.add(settings, "apertureSize", 0, 0.4, 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);
Expand Down
113 changes: 64 additions & 49 deletions examples/dynamic-lighting/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,9 @@
</script>
<script type="module">
import * as THREE from "three";
import {
SplatMesh,
SplatEdit,
SparkRenderer,
SplatEditSdf,
SplatEditSdfType,
SplatEditRgbaBlendMode,
FpsMovement,
PointerControls,
} from "@sparkjsdev/spark";
import { SplatLoader } from "@sparkjsdev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
import { FpsMovement, PointerControls } from "/examples/js/controls.js";

const canvas = document.getElementById("canvas");
const scene = new THREE.Scene();
Expand All @@ -104,12 +96,9 @@
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 loader = new SplatLoader();
const splatURL = await getAssetFileURL("fireplace.spz");
const fireplace = new SplatMesh({ url: splatURL });
const fireplace = await loader.loadAsync(splatURL);
fireplace.quaternion.set(1, 0, 0, 0);
fireplace.position.set(0, -1, -10);
scene.add(fireplace);
Expand All @@ -122,14 +111,17 @@
// 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);
function createLight(scene, position, color, radius, opacity, sdfSmooth, sdfEdge, darken) {
const light = {
color,
radius,
opacity,
position: position.clone(),
visible: true,
sdfSmooth,
sdfEdge,
darken,
};

// create a wireframe helper
const helper = new THREE.Mesh(
Expand All @@ -141,63 +133,86 @@
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,
0.1,
0.8,
false
);

// 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,
0.1,
1.4,
false
);

// 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,
0.1,
0.05,
true
);
console.log(helpers);

fireplace.setShaderHooks({
vertex: {
uniforms: {
'edits': {
type: 'SplatEdit',
value: lights
},
},
global: /*glsl*/`
struct SplatEdit {
vec3 color;
float radius;
float opacity;
vec3 position;
bool visible;
float sdfSmooth;
float sdfEdge;
bool darken;
};
`,
splatColor: /*glsl*/`
for(int i = 0; i < 3; i++) {
if(!edits[i].visible) { continue; }

// TODO: Properly recreate original smoothing and modulation logic
float dist = distance(edits[i].position, center) - edits[i].radius;

if(dist <= edits[i].radius) {
float factor = clamp(-dist / edits[i].sdfEdge + 0.5, 0.0, 1.0);
if(edits[i].darken) {
rgba.rgb *= edits[i].color * edits[i].opacity * factor;
} else {
rgba.rgb += edits[i].color * edits[i].opacity * factor;
}
}
}
`
}
});

let lastTime;

renderer.setAnimationLoop(time => {
Expand Down
Loading