Skip to content

Commit

Permalink
Baked shadows (google#3295)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elalish authored Mar 22, 2022
1 parent d12b6ca commit 360fd14
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 16 deletions.
5 changes: 5 additions & 0 deletions packages/model-viewer/src/features/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export declare interface EnvironmentInterface {
shadowIntensity: number;
shadowSoftness: number;
exposure: number;
hasBakedShadow(): boolean;
}

export const EnvironmentMixin = <T extends Constructor<ModelViewerElementBase>>(
Expand Down Expand Up @@ -105,6 +106,10 @@ export const EnvironmentMixin = <T extends Constructor<ModelViewerElementBase>>(
}
}

hasBakedShadow(): boolean {
return this[$scene].bakedShadows.length > 0;
}

[$onModelLoad]() {
super[$onModelLoad]();

Expand Down
58 changes: 50 additions & 8 deletions packages/model-viewer/src/three-components/ModelScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -90,6 +90,7 @@ export class ModelScene extends Scene {
public shadow: Shadow|null = null;
public shadowIntensity = 0;
public shadowSoftness = 1;
public bakedShadows = Array<Mesh>();

public exposure = 1;
public canScale = true;
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -629,6 +674,7 @@ export class ModelScene extends Scene {
if (this._currentGLTF == null) {
return;
}
this.setBakedShadowVisibility();
if (shadowIntensity <= 0 && this.shadow == null) {
return;
}
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/model-viewer/src/three-components/ModelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const reduceVertices = <T>(
T => {
let value = initialValue;
const vertex = new Vector3();
model.traverse((object: any) => {
model.traverseVisible((object: any) => {
let i, l;

object.updateWorldMatrix(false, false);
Expand Down
1 change: 1 addition & 0 deletions packages/model-viewer/src/three-components/Shadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion packages/model-viewer/src/three-components/SmoothControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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) <
Expand Down
7 changes: 7 additions & 0 deletions packages/modelviewer.dev/data/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions packages/modelviewer.dev/examples/scenegraph/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -427,14 +427,14 @@ <h2 class="demo-title">Swap textures</h2>
</div>
</model-viewer>
<script type="module">
const modelViewerTexture = document.querySelector("model-viewer#helmet");
const modelViewerTexture1 = document.querySelector("model-viewer#helmet");

modelViewerTexture.addEventListener("load", () => {
modelViewerTexture1.addEventListener("load", () => {

const material = modelViewerTexture.model.materials[0];
const material = modelViewerTexture1.model.materials[0];

const createAndApplyTexture = async (channel, event) => {
const texture = await modelViewerTexture.createTexture(event.target.value);
const texture = await modelViewerTexture1.createTexture(event.target.value);
if (channel.includes('base') || channel.includes('metallic')) {
material.pbrMetallicRoughness[channel].setTexture(texture);
} else {
Expand Down Expand Up @@ -481,7 +481,7 @@ <h2 class="demo-title">Pick a Material</h2>
</div>
<example-snippet stamp-to="pickMaterialExample" highlight-as="html">
<template>
<model-viewer id="pickMaterial" camera-controls src="../../shared-assets/models/glTF-Sample-Models/2.0/Buggy/glTF-Binary/Buggy.glb" ar ar-modes="webxr scene-viewer" scale="0.001 0.001 0.001" alt="A Material Picking Example">
<model-viewer id="pickMaterial" shadow-intensity="1" camera-controls src="../../shared-assets/models/glTF-Sample-Models/2.0/Buggy/glTF-Binary/Buggy.glb" ar ar-modes="webxr scene-viewer" scale="0.001 0.001 0.001" alt="A Material Picking Example">
</model-viewer>
<script type="module">
const modelViewer = document.querySelector("model-viewer#pickMaterial");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ <h2 class="demo-title">Pan and focus</h2>
</div>
<example-snippet stamp-to="panning" highlight-as="html">
<template>
<model-viewer id="pan-demo" enable-pan auto-rotate camera-controls src="../../shared-assets/models/NeilArmstrong.glb" alt="Neil Armstrong's Spacesuit from the Smithsonian Digitization Programs Office and National Air and Space Museum"></model-viewer>
<model-viewer id="pan-demo" enable-pan auto-rotate shadow-intensity="1" camera-controls src="../../shared-assets/models/NeilArmstrong.glb" alt="Neil Armstrong's Spacesuit from the Smithsonian Digitization Programs Office and National Air and Space Museum"></model-viewer>
</template>
</example-snippet>
</div>
Expand Down

0 comments on commit 360fd14

Please sign in to comment.