mirror of
https://github.com/storytold/spark.git
synced 2026-10-17 00:12:59 +00:00
Splat Texture (#79)
* Add experimental Splat Texture rendering and examples/splat-texture. Added star.png and heart.png to assets.json and to assets repo. Update examples/depth-of-field to compute from metric aperture size. Added SparkRenderer.renderScale debug parameter. Updated examples/editor: set default highDPI to true, added logFocalDistance, apertureAngle, refactored, grouped into debugFolder, default settings open, preset buttons for nonAA and AA settings. * Set default highDpi in examples/editor to not(isMobile).
This commit is contained in:
committed by
GitHub
parent
c0db3576bd
commit
179885595c
@@ -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>
|
||||
Reference in New Issue
Block a user