From 360fd14aa995d04a1db1f8fdf988707eefe7cb36 Mon Sep 17 00:00:00 2001 From: Emmett Lalish Date: Mon, 21 Mar 2022 17:19:20 -0700 Subject: [PATCH] Baked shadows (#3295) * started logic to find baked shadows * remove baked shadows from BBox and hide if MV shadows are enabled * fix bugs * added hasBakedShadow function * made bake test scale independent --- .../model-viewer/src/features/environment.ts | 5 ++ .../src/three-components/ModelScene.ts | 58 ++++++++++++++++--- .../src/three-components/ModelUtils.ts | 2 +- .../src/three-components/Shadow.ts | 1 + .../src/three-components/SmoothControls.ts | 5 +- packages/modelviewer.dev/data/docs.json | 7 +++ .../examples/scenegraph/index.html | 10 ++-- .../examples/stagingandcameras/index.html | 2 +- 8 files changed, 74 insertions(+), 16 deletions(-) diff --git a/packages/model-viewer/src/features/environment.ts b/packages/model-viewer/src/features/environment.ts index d7f59469d3..a51209e27a 100644 --- a/packages/model-viewer/src/features/environment.ts +++ b/packages/model-viewer/src/features/environment.ts @@ -38,6 +38,7 @@ export declare interface EnvironmentInterface { shadowIntensity: number; shadowSoftness: number; exposure: number; + hasBakedShadow(): boolean; } export const EnvironmentMixin = >( @@ -105,6 +106,10 @@ export const EnvironmentMixin = >( } } + hasBakedShadow(): boolean { + return this[$scene].bakedShadows.length > 0; + } + [$onModelLoad]() { super[$onModelLoad](); diff --git a/packages/model-viewer/src/three-components/ModelScene.ts b/packages/model-viewer/src/three-components/ModelScene.ts index a10bc749a4..94ffeb494b 100644 --- a/packages/model-viewer/src/three-components/ModelScene.ts +++ b/packages/model-viewer/src/three-components/ModelScene.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import {AnimationAction, AnimationClip, AnimationMixer, Box3, Camera, Event as ThreeEvent, LoopPingPong, LoopRepeat, Matrix3, Object3D, PerspectiveCamera, Raycaster, Scene, Sphere, Vector2, Vector3, WebGLRenderer} from 'three'; +import {AnimationAction, AnimationClip, AnimationMixer, Box3, Camera, Event as ThreeEvent, LoopPingPong, LoopRepeat, Material, Matrix3, Mesh, Object3D, PerspectiveCamera, Raycaster, Scene, Sphere, Vector2, Vector3, WebGLRenderer} from 'three'; import {CSS2DRenderer} from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import ModelViewerElementBase, {$renderer, RendererInterface} from '../model-viewer-base.js'; @@ -26,7 +26,7 @@ import {Hotspot} from './Hotspot.js'; import {reduceVertices} from './ModelUtils.js'; import {Shadow} from './Shadow.js'; - +const MIN_SHADOW_RATIO = 100; export interface ModelLoadEvent extends ThreeEvent { url: string; @@ -90,6 +90,7 @@ export class ModelScene extends Scene { public shadow: Shadow|null = null; public shadowIntensity = 0; public shadowSoftness = 1; + public bakedShadows = Array(); public exposure = 1; public canScale = true; @@ -316,17 +317,53 @@ export class ModelScene extends Scene { this.queueRender(); } + findBakedShadows(group: Object3D) { + const boundingBox = new Box3(); + + group.traverse((object: Object3D) => { + const mesh = object as Mesh; + if (!mesh.isMesh) { + return; + } + const material = mesh.material as Material; + if (!(material as any).isMeshBasicMaterial || !material.transparent) { + return; + } + boundingBox.setFromObject(mesh); + const size = boundingBox.getSize(vector3); + const minDim = Math.min(size.x, size.y, size.z); + const maxDim = Math.max(size.x, size.y, size.z); + if (maxDim < MIN_SHADOW_RATIO * minDim) { + return; + } + this.bakedShadows.push(mesh); + mesh.userData.shadow = true; + }); + } + updateBoundingBox() { this.target.remove(this.modelContainer); + this.findBakedShadows(this.modelContainer); + if (this.tightBounds === true) { const bound = (box: Box3, vertex: Vector3): Box3 => { return box.expandByPoint(vertex); }; + this.setBakedShadowVisibility(false); this.boundingBox = reduceVertices(this.modelContainer, bound, new Box3()); + // If there's nothing but the baked shadow, then it's not a baked shadow. + if (this.boundingBox.isEmpty()) { + this.setBakedShadowVisibility(true); + this.bakedShadows = []; + this.boundingBox = + reduceVertices(this.modelContainer, bound, new Box3()); + } + this.setBakedShadowVisibility(); } else { this.boundingBox.setFromObject(this.modelContainer); } + this.boundingBox.getSize(this.size); this.target.add(this.modelContainer); @@ -342,6 +379,7 @@ export class ModelScene extends Scene { */ async updateFraming() { this.target.remove(this.modelContainer); + this.setBakedShadowVisibility(false); const {center} = this.boundingSphere; if (this.tightBounds === true) { @@ -367,9 +405,16 @@ export class ModelScene extends Scene { reduceVertices(this.modelContainer, horizontalTanFov, 0) / Math.tan((this.framedFoVDeg / 2) * Math.PI / 180); + this.setBakedShadowVisibility(); this.target.add(this.modelContainer); } + setBakedShadowVisibility(visible: boolean = this.shadowIntensity <= 0) { + for (const shadow of this.bakedShadows) { + shadow.visible = visible; + } + } + idealCameraDistance(): number { const halfFovRad = (this.framedFoVDeg / 2) * Math.PI / 180; return this.boundingSphere.radius / Math.sin(halfFovRad); @@ -629,6 +674,7 @@ export class ModelScene extends Scene { if (this._currentGLTF == null) { return; } + this.setBakedShadowVisibility(); if (shadowIntensity <= 0 && this.shadow == null) { return; } @@ -680,12 +726,8 @@ export class ModelScene extends Scene { this.raycaster.setFromCamera(ndcPosition, this.getCamera()); const hits = this.raycaster.intersectObject(object, true); - if (hits.length === 0) { - return null; - } - - const hit = hits[0]; - if (hit.face == null) { + const hit = hits.find((hit) => !hit.object.userData.shadow); + if (hit == null || hit.face == null) { return null; } diff --git a/packages/model-viewer/src/three-components/ModelUtils.ts b/packages/model-viewer/src/three-components/ModelUtils.ts index 9d0e1e2961..7e628386cd 100644 --- a/packages/model-viewer/src/three-components/ModelUtils.ts +++ b/packages/model-viewer/src/three-components/ModelUtils.ts @@ -63,7 +63,7 @@ export const reduceVertices = ( T => { let value = initialValue; const vertex = new Vector3(); - model.traverse((object: any) => { + model.traverseVisible((object: any) => { let i, l; object.updateWorldMatrix(false, false); diff --git a/packages/model-viewer/src/three-components/Shadow.ts b/packages/model-viewer/src/three-components/Shadow.ts index 52ca44a22c..4ff3df2906 100644 --- a/packages/model-viewer/src/three-components/Shadow.ts +++ b/packages/model-viewer/src/three-components/Shadow.ts @@ -91,6 +91,7 @@ export class Shadow extends Object3D { side: BackSide, }); this.floor = new Mesh(plane, shadowMaterial); + this.floor.userData.shadow = true; camera.add(this.floor); // the plane onto which to blur the texture diff --git a/packages/model-viewer/src/three-components/SmoothControls.ts b/packages/model-viewer/src/three-components/SmoothControls.ts index 825cb09706..fa75b00d02 100644 --- a/packages/model-viewer/src/three-components/SmoothControls.ts +++ b/packages/model-viewer/src/three-components/SmoothControls.ts @@ -560,7 +560,7 @@ export class SmoothControls extends EventDispatcher { this.userAdjustOrbit(0, 0, deltaZoom); } - if (this.enablePan && this.panMetersPerPixel > 0) { + if (this.panMetersPerPixel > 0) { const thisX = 0.5 * (targetTouches[0].clientX + targetTouches[1].clientX); const thisY = @@ -646,6 +646,9 @@ export class SmoothControls extends EventDispatcher { } private recenter(pointer: Pointer) { + if (!this.enablePan) { + return; + } const {scene} = this; (scene.element as any)[$panElement].style.opacity = 0; if (Math.abs(pointer.clientX - this.startPointerPosition.clientX) < diff --git a/packages/modelviewer.dev/data/docs.json b/packages/modelviewer.dev/data/docs.json index 0523627a87..5ee11829d3 100644 --- a/packages/modelviewer.dev/data/docs.json +++ b/packages/modelviewer.dev/data/docs.json @@ -823,6 +823,13 @@ } } ], + "Methods": [ + { + "name": "hasBakedShadow()", + "htmlName": "hasBakedShadow", + "description": "Returns true if a baked-in shadow plane appears to be part of this model. These planes are removed from framing calculations and are only rendered if shadow-intensity is zero, to avoid multiple shadows interacting." + } + ], "Events": [ { "name": "environment-change", diff --git a/packages/modelviewer.dev/examples/scenegraph/index.html b/packages/modelviewer.dev/examples/scenegraph/index.html index e183bfce11..5c8c346499 100644 --- a/packages/modelviewer.dev/examples/scenegraph/index.html +++ b/packages/modelviewer.dev/examples/scenegraph/index.html @@ -427,14 +427,14 @@

Swap textures