From f384514243eb256439edea23dac689b38fb12abb Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 9 Nov 2023 16:02:07 +0000 Subject: [PATCH] update model viewer code --- assets/js/three/CustomOutlinePass.js | 18 ++++ assets/js/three/index.js | 151 +++++++++++++++++++++++++-- 2 files changed, 161 insertions(+), 8 deletions(-) diff --git a/assets/js/three/CustomOutlinePass.js b/assets/js/three/CustomOutlinePass.js index 472e550..f1b8db3 100644 --- a/assets/js/three/CustomOutlinePass.js +++ b/assets/js/three/CustomOutlinePass.js @@ -118,6 +118,7 @@ class CustomOutlinePass extends Pass { uniform vec4 screenSize; uniform vec3 outlineColor; uniform vec2 multiplierParameters; + uniform int debugVisualize; varying vec2 vUv; @@ -196,6 +197,22 @@ class CustomOutlinePass extends Pass { // Combine outline with scene color. vec4 outlineColor = vec4(outlineColor, 1.0); gl_FragColor = vec4(mix(sceneColor, outlineColor, outline)); + + //// For debug visualization of the different inputs to this shader. + if (debugVisualize == 2) { + gl_FragColor = sceneColor; + } + if (debugVisualize == 3) { + gl_FragColor = vec4(vec3(depth), 1.0); + } + if (debugVisualize == 4) { + // 4 visualizes the surfaceID buffer + gl_FragColor = vec4(surfaceValue, 1.0); + } + if (debugVisualize == 5) { + // Outlines only + gl_FragColor = mix(vec4(0,0,0,0), outlineColor, outline); + } } `; } @@ -203,6 +220,7 @@ class CustomOutlinePass extends Pass { createOutlinePostProcessMaterial() { return new THREE.ShaderMaterial({ uniforms: { + debugVisualize: { value: 0 }, sceneColorBuffer: {}, depthBuffer: {}, surfaceBuffer: {}, diff --git a/assets/js/three/index.js b/assets/js/three/index.js index be4f81b..88b2093 100644 --- a/assets/js/three/index.js +++ b/assets/js/three/index.js @@ -1,5 +1,6 @@ import * as THREE from "three"; +import * as dat from 'dat.gui'; import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; @@ -12,6 +13,123 @@ import { CustomOutlinePass } from "./CustomOutlinePass.js"; import FindSurfaces from "./FindSurfaces.js"; +// Todo: +// Swap in the version of this code that has a debug GUI behind a flag +// Consider support for transparent objects by rendering them as a wireframe in the color and excluding them from the edge pass. +// Fix the cetnering and scaling +// Switch to an angled isometric camera to match the style from the main page. + +const fitCameraToCenteredObject = function (camera, object, offset, orbitControls ) { + const boundingBox = new THREE.Box3(); + boundingBox.setFromObject( object ); + + var middle = new THREE.Vector3(); + var size = new THREE.Vector3(); + boundingBox.getSize(size); + + // figure out how to fit the box in the view: + // 1. figure out horizontal FOV (on non-1.0 aspects) + // 2. figure out distance from the object in X and Y planes + // 3. select the max distance (to fit both sides in) + // + // The reason is as follows: + // + // Imagine a bounding box (BB) is centered at (0,0,0). + // Camera has vertical FOV (camera.fov) and horizontal FOV + // (camera.fov scaled by aspect, see fovh below) + // + // Therefore if you want to put the entire object into the field of view, + // you have to compute the distance as: z/2 (half of Z size of the BB + // protruding towards us) plus for both X and Y size of BB you have to + // figure out the distance created by the appropriate FOV. + // + // The FOV is always a triangle: + // + // (size/2) + // +--------+ + // | / + // | / + // | / + // | F° / + // | / + // | / + // | / + // |/ + // + // F° is half of respective FOV, so to compute the distance (the length + // of the straight line) one has to: `size/2 / Math.tan(F)`. + // + // FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera + // the camera.fov is the vertical FOV. + + const fov = camera.fov * ( Math.PI / 180 ); + const fovh = 2*Math.atan(Math.tan(fov/2) * camera.aspect); + let dx = size.z / 2 + Math.abs( size.x / 2 / Math.tan( fovh / 2 ) ); + let dy = size.z / 2 + Math.abs( size.y / 2 / Math.tan( fov / 2 ) ); + let cameraZ = Math.max(dx, dy); + + // offset the camera, if desired (to avoid filling the whole canvas) + if( offset !== undefined && offset !== 0 ) cameraZ *= offset; + + camera.position.set( 0, 0, cameraZ ); + + // set the far plane of the camera so that it easily encompasses the whole object + const minZ = boundingBox.min.z; + const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ; + + camera.far = cameraToFarEdge * 3; + camera.updateProjectionMatrix(); + + if ( orbitControls !== undefined ) { + // set camera to rotate around the center + orbitControls.target = new THREE.Vector3(0, 0, 0); + + // prevent camera from zooming out far enough to create far plane cutoff + orbitControls.maxDistance = cameraToFarEdge * 2; + } +}; + +const setupDebug = () => { + // Set up GUI controls + const GUI = dat.GUI; + const gui = new GUI({ width: 300, autoPlace: true}); + // container.append(gui.domElement); + + const uniforms = customOutline.fsQuad.material.uniforms; + const params = { + mode: { Mode: 0 }, + depthBias: uniforms.multiplierParameters.value.x, + depthMult: uniforms.multiplierParameters.value.y, + normalBias: uniforms.multiplierParameters.value.z, + normalMult: uniforms.multiplierParameters.value.w, + }; + + gui + .add(params.mode, "Mode", { + "Outlines": 0, + "Original scene": 2, + "Depth buffer": 3, + "SurfaceID debug buffer": 4, + "Outlines only": 5, + }) + .onChange(function (value) { + uniforms.debugVisualize.value = value; + }); + + gui.add(params, "depthBias", 0.0, 5).onChange(function (value) { + uniforms.multiplierParameters.value.x = value; + }); + gui.add(params, "depthMult", 0.0, 20).onChange(function (value) { + uniforms.multiplierParameters.value.y = value; + }); + gui.add(params, "normalBias", 0.0, 20).onChange(function (value) { + uniforms.multiplierParameters.value.z = value; + }); + gui.add(params, "normalMult", 0.0, 10).onChange(function (value) { + uniforms.multiplierParameters.value.w = value; + }); +} + class OutlineModelViewer extends HTMLElement { constructor() { super(); @@ -20,16 +138,19 @@ class OutlineModelViewer extends HTMLElement { this.render(); const model_path = this.getAttribute("model") || "/assets/projects/bike_lights/models/bigger.glb"; + const container = this.shadow.querySelector("div#container"); + console.log(container); const canvas = this.shadow.querySelector("canvas"); let canvas_rect = canvas.getBoundingClientRect(); - console.log(canvas.clientWidth, canvas.clientHeight, canvas_rect, window.devicePixelRatio); const body = document.getElementsByTagName("body")[0]; const style = window.getComputedStyle(body); + const background_color = style.getPropertyValue("background-color"); - const outline_color = 0x000000; - const model_color = 0xffffff; + const isDark = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) + const outline_color = isDark ? 0xffffff : 0x000000; + const model_color = background_color; // Init scene const camera = new THREE.PerspectiveCamera( @@ -121,12 +242,20 @@ class OutlineModelViewer extends HTMLElement { // Set up orbital camera controls. let controls = new OrbitControls(camera, renderer.domElement); controls.autoRotate = true; + + let bbox = new THREE.Box3().setFromObject(scene); + let middle = new THREE.Vector3(); + bbox.getCenter(middle); + + // // Center it + scene.applyMatrix4(new THREE.Matrix4().makeTranslation( -middle.x, -middle.y, -middle.z ) ); + // fitCameraToCenteredObject(camera, scene, 1, controls ); controls.update(); // Render loop function update() { requestAnimationFrame(update); - controls.update(); + // controls.update(); composer.render(); } update(); @@ -147,18 +276,24 @@ class OutlineModelViewer extends HTMLElement { } window.addEventListener("resize", onWindowResize, false); + if(this.getAttribute("debug")) setupDebug(); } render() { this.shadow.innerHTML = ` - - +
+ + + Debug +
Details
+
+