Skip to content
Merged
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
18 changes: 9 additions & 9 deletions examples/assets/attributions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,32 @@ Damaged Helmet by <a href="https://sketchfab.com/theblueturtle_">theblueturtle_<
licensed under Creative Commons Attribution-NonCommercial
(<a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet">source</a>)

## equirectangular.png

equirectangular.png by <a href="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/WestLangley">WestLangley</a>
licensed under <a href="https://github.com/mrdoob/three.js/blob/dev/LICENSE">MIT</a>
(<a href="https://github.com/mrdoob/three.js/blob/dev/examples/textures/equirectangular.png">source</a>)

## Shishkebab.*

Shishkebab by <a href="https://poly.google.com/user/4aEd8rQgKu2">Poly</a>,
licensed under <a href="https://creativecommons.org/licenses/by/2.0/">CC-BY</a>
(<a href="https://poly.google.com/view/6uTsH2jqgVn">source</a>)

# whipple_creek_regional_park_04
## whipple_creek_regional_park_04

whipple_creek_regional_park_04_1k.jpg by <a href="https://hdrihaven.com">HDRI Haven</a>
licensed under <a href="https://hdrihaven.com/p/license.php">CC0</a>
(<a href="https://hdrihaven.com/hdri/?h=whipple_creek_regional_park_04">source</a>)
Originally in HDR format

# small_hangar_01
## small_hangar_01

small_hangar_01_1k.jpg by <a href="https://hdrihaven.com">HDRI Haven</a>
licensed under <a href="https://hdrihaven.com/p/license.php">CC0</a>
(<a href="https://hdrihaven.com/hdri/?h=small_hangar_01">source</a>)
Originally in HDR format

## cube.gltf, offcenter-cube.gltf, pbr-spheres.glb, reflective-sphere.gltf
## spruit_sunrise_2k

spruit_sunrise_2k.hdr, spruit_sunrise_2k.jpg by <a href="https://hdrihaven.com">HDRI Haven</a>
licensed under <a href="https://hdrihaven.com/p/license.php">CC0</a>
(<a href="https://hdrihaven.com/hdri/?c=outdoor&h=spruit_sunrise">source</a>)

## cube.gltf, offcenter-cube.gltf, pbr-spheres.glb, radiance.glb, reflective-sphere.gltf

Contributed by <a href="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/jsantell">jsantell</a>
Binary file removed examples/assets/equirectangular.png
Binary file not shown.
Binary file added examples/assets/radiance.glb
Binary file not shown.
Binary file added examples/assets/spruit_sunrise_2k.hdr
Binary file not shown.
Binary file added examples/assets/spruit_sunrise_2k.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 9 additions & 10 deletions examples/background-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,9 @@ <h2>An equirectangular <span class="attribute">background-image</span></h2>
<template>
<model-viewer
controls
background-image="assets/whipple_creek_regional_park_04_1k.jpg"
alt="A 3D model of a damaged helmet with a forest in the background"
src="assets/DamagedHelmet/DamagedHelmet.gltf">
</model-viewer>
background-image="assets/spruit_sunrise_2k.jpg"
alt="A 3D model of metal spheres at varying degrees of roughness"
src="assets/radiance.glb"></model-viewer>
</template>
</example-snippet>
</div>
Expand All @@ -81,14 +80,14 @@ <h2>An equirectangular <span class="attribute">background-image</span></h2>
<div class="content">
<div class="wrapper">
<div class="index">2</div>
<h2>An equirectangular <span class="attribute">background-image</span> with a very reflective model</h2>
<h2>An equirectangular HDR <span class="attribute">background-image</span></h2>
<example-snippet stamp-to="demo-container-2" highlight-as="html">
<template>
<model-viewer
controls
background-image="assets/small_hangar_01_1k.jpg"
alt="A 3D model of a reflective sphere depicted within a hangar"
src="assets/reflective-sphere.gltf"></model-viewer>
controls
background-image="assets/spruit_sunrise_2k.hdr"
alt="A 3D model of metal spheres at varying degrees of roughness"
src="assets/radiance.glb"></model-viewer>
</template>
</example-snippet>
</div>
Expand Down Expand Up @@ -144,7 +143,7 @@ <h2>Cycling between <span class="attribute">background-image</span> and <span cl
<template>
<model-viewer
id="toggle-image"
backgroud-color="#ff0077"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch 👍

background-color="#ff0077"
src="assets/DamagedHelmet/DamagedHelmet.gltf"
alt="A 3D model of a damaged helmet depicted within changing environments"
controls auto-rotate></model-viewer>
Expand Down
59 changes: 25 additions & 34 deletions src/features/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
* limitations under the License.
*/

import {Color} from 'three';
import {BackSide, BoxBufferGeometry, Color, Mesh, ShaderLib, ShaderMaterial, UniformsUtils} from 'three';

import {$needsRender, $onModelLoad, $renderer, $scene, $tick} from '../model-viewer-base.js';

const DEFAULT_BACKGROUND_COLOR = '#ffffff';
const GAMMA_TO_LINEAR = 2.2;

const WHITE = new Color('#ffffff');

const $currentCubemap = Symbol('currentCubemap');
const $currentEnvironmentMap = Symbol('currentEnvironmentMap');
const $setEnvironmentImage = Symbol('setEnvironmentImage');
const $setEnvironmentColor = Symbol('setEnvironmentColor');
const $setShadowLightColor = Symbol('setShadowLightColor');
Expand Down Expand Up @@ -75,17 +75,11 @@ export const EnvironmentMixin = (ModelViewerElement) => {
}
}

[$tick](time, delta) {
super[$tick](time, delta);
const camera = this[$scene].getCamera();
this[$scene].skysphere.position.copy(camera.position);
}

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

if (this[$currentCubemap]) {
this[$scene].model.applyEnvironmentMap(this[$currentCubemap]);
if (this[$currentEnvironmentMap]) {
this[$scene].model.applyEnvironmentMap(this[$currentEnvironmentMap]);
this[$needsRender]();
}
}
Expand All @@ -100,7 +94,7 @@ export const EnvironmentMixin = (ModelViewerElement) => {
return;
}

const textures = await textureUtils.toCubemapAndEquirect(url);
const textures = await textureUtils.generateEnvironmentTextures(url);

// If the background image has changed
// while fetching textures, abort and defer to that
Expand All @@ -118,13 +112,12 @@ export const EnvironmentMixin = (ModelViewerElement) => {
return;
}

const {cubemap, equirect} = textures;
const {skybox, environmentMap} = textures;

this[$scene].skysphere.material.color = new Color(0xffffff);
this[$scene].skysphere.material.map = equirect;
this[$scene].skysphere.material.needsUpdate = true;
this[$currentCubemap] = cubemap;
this[$scene].model.applyEnvironmentMap(cubemap);
this[$scene].background = skybox;

this[$currentEnvironmentMap] = environmentMap;
this[$scene].model.applyEnvironmentMap(environmentMap);

this[$setShadowLightColor](WHITE);

Expand All @@ -143,18 +136,16 @@ export const EnvironmentMixin = (ModelViewerElement) => {

this[$deallocateTextures]();

const skysphereColor = this[$scene].skysphere.material.color =
new Color(color);
skysphereColor.convertGammaToLinear(GAMMA_TO_LINEAR);
this[$setShadowLightColor](skysphereColor);
const parsedColor = new Color(color);

this[$scene].background = parsedColor;

this[$scene].skysphere.material.map = null;
this[$scene].skysphere.material.needsUpdate = true;
this[$setShadowLightColor](parsedColor);

// TODO can cache this per renderer and color
const cubemap = textureUtils.generateDefaultEnvMap();
this[$currentCubemap] = cubemap;
this[$scene].model.applyEnvironmentMap(this[$currentCubemap]);
// TODO(#336): can cache this per renderer and color
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍻 thanks for filing!

const environmentMap = textureUtils.generateDefaultEnvironmentMap();
this[$currentEnvironmentMap] = environmentMap;
this[$scene].model.applyEnvironmentMap(this[$currentEnvironmentMap]);

this[$needsRender]();
}
Expand All @@ -165,13 +156,13 @@ export const EnvironmentMixin = (ModelViewerElement) => {
}

[$deallocateTextures]() {
if (this[$scene].skysphere.material.map) {
this[$scene].skysphere.material.map.dispose();
this[$scene].skysphere.material.map = null;
const background = this[$scene].background;
if (background && background.dispose) {
background.dispose();
}
if (this[$currentCubemap]) {
this[$currentCubemap].dispose();
this[$currentCubemap] = null;
if (this[$currentEnvironmentMap]) {
this[$currentEnvironmentMap].dispose();
this[$currentEnvironmentMap] = null;
}
}
}
Expand Down
91 changes: 48 additions & 43 deletions src/test/features/environment-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,20 @@ import {assetPath, textureMatchesMeta, timePasses, waitForEvent} from '../helper
import {BasicSpecTemplate} from '../templates.js';

const expect = chai.expect;
const BG_IMAGE_URL = assetPath('equirectangular.png');
const BG_IMAGE_URL = assetPath('spruit_sunrise_2k.jpg');
const MODEL_URL = assetPath('reflective-sphere.gltf');

const skysphereUsingMap =
const backgroundHasMap =
(scene, url) => {
const material = scene.skysphere.material;
const color = material.color.getHexString();
return textureMatchesMeta(material.map, {url: url}) && color === 'ffffff';
return textureMatchesMeta(scene.background.texture, {url: url});
}

const skysphereUsingColor =
const backgroundHasColor =
(scene, hex) => {
const {color, map} = scene.skysphere.material;
// Invert gamma correct to match passed in hex
const gammaCorrectedColor = color.clone().convertLinearToGamma(2.2);

return map == null && gammaCorrectedColor.getHexString() === hex;
if (!scene.background || !scene.background.isColor) {
return false;
}
return scene.background.getHexString() === hex;
}

/**
Expand All @@ -47,7 +44,7 @@ const skysphereUsingColor =
* @param {THREE.Scene} scene
* @param {Object} meta
*/
const modelUsingEnvmap = (scene, meta) => {
const modelUsingEnvMap = (scene, meta) => {
let found = false;
scene.model.traverse(object => {
if (!object.material || !object.material.envMap) {
Expand All @@ -71,18 +68,22 @@ const modelUsingEnvmap = (scene, meta) => {
* @param {Model} model
* @param {Object} meta
*/
const waitForEnvmap = (model, meta) => waitForEvent(
model,
'envmap-change',
e => textureMatchesMeta(e.value, {...meta, type: 'EnvironmentMap'}));
const waitForEnvMap = (model, meta) =>
waitForEvent(model, 'envmap-change', event => {
return textureMatchesMeta(event.value, {...meta});
});

/**
* Returns a promise that resolves when a given element is loaded
* and has an environment map set that matches the passed in meta.
* @see textureMatchesMeta
*/
const waitForLoadAndEnvmap = (scene, element, meta) => Promise.all(
[waitForEvent(element, 'load'), waitForEnvmap(scene.model, meta)]);
const waitForLoadAndEnvMap =
(scene, element, meta) => {
const load = waitForEvent(element, 'load');
const envMap = waitForEnvMap(scene.model, meta);
return Promise.all([load, envMap]);
}

suite('ModelViewerElementBase with EnvironmentMixin', () => {
let nextId = 0;
Expand All @@ -109,34 +110,34 @@ suite('ModelViewerElementBase with EnvironmentMixin', () => {
BasicSpecTemplate(() => ModelViewerElement, () => tagName);

test(
'has default skysphere if no background-image or background-color',
'has default background if no background-image or background-color',
() => {
expect(skysphereUsingColor(scene, 'ffffff')).to.be.equal(true);
expect(backgroundHasColor(scene, 'ffffff')).to.be.equal(true);
});

test(
'has default skysphere if no background-image or background-color when in DOM',
'has default background if no background-image or background-color when in DOM',
async () => {
document.body.appendChild(element);
await timePasses();
expect(skysphereUsingColor(scene, 'ffffff')).to.be.equal(true);
expect(backgroundHasColor(scene, 'ffffff')).to.be.equal(true);
});

suite('with a background-image property', () => {
suite('and a src property', () => {
setup(async () => {
let onLoad = waitForLoadAndEnvmap(scene, element, {url: BG_IMAGE_URL});
let onLoad = waitForLoadAndEnvMap(scene, element, {url: BG_IMAGE_URL});
element.src = MODEL_URL;
element.backgroundImage = BG_IMAGE_URL;
await onLoad;
});

test('displays skysphere with the correct map', async function() {
expect(skysphereUsingMap(scene, element.backgroundImage)).to.be.ok;
test('displays background with the correct map', async function() {
expect(backgroundHasMap(scene, element.backgroundImage)).to.be.ok;
});

test('applies the image as an environment map', async function() {
expect(modelUsingEnvmap(scene, {
expect(modelUsingEnvMap(scene, {
url: element.backgroundImage
})).to.be.ok;
});
Expand All @@ -159,26 +160,30 @@ suite('ModelViewerElementBase with EnvironmentMixin', () => {
suite('with a background-color property', () => {
suite('and a src property', () => {
setup(async () => {
let onLoad = waitForLoadAndEnvmap(scene, element, {url: null});
let onLoad = waitForLoadAndEnvMap(scene, element, {
url: null,
});
element.src = MODEL_URL;
element.backgroundColor = '#ff0077';
await onLoad;
});

test('displays skysphere with the correct color', async function() {
expect(skysphereUsingColor(scene, 'ff0077')).to.be.ok;
test('displays background with the correct color', async function() {
expect(backgroundHasColor(scene, 'ff0077')).to.be.ok;
});

test('applies a generated environment map on model', async function() {
expect(modelUsingEnvmap(scene, {url: null})).to.be.ok;
expect(modelUsingEnvMap(scene, {
url: null,
})).to.be.ok;
});

test(
'displays skysphere with correct color after attaching to DOM',
'displays background with correct color after attaching to DOM',
async function() {
document.body.appendChild(element);
await timePasses();
expect(skysphereUsingColor(scene, 'ff0077')).to.be.ok;
expect(backgroundHasColor(scene, 'ff0077')).to.be.ok;
});
test('the directional light is tinted', () => {
const lightColor = scene.shadowLight.color.getHexString().toLowerCase();
Expand All @@ -189,34 +194,34 @@ suite('ModelViewerElementBase with EnvironmentMixin', () => {

suite('with background-color and background-image properties', () => {
setup(async () => {
let onLoad = waitForLoadAndEnvmap(scene, element, {url: BG_IMAGE_URL});
let onLoad = waitForLoadAndEnvMap(scene, element, {url: BG_IMAGE_URL});
element.setAttribute('src', MODEL_URL);
element.setAttribute('background-color', '#ff0077');
element.setAttribute('background-image', BG_IMAGE_URL);
await onLoad;
});

test('displays skysphere with background-image', async function() {
expect(skysphereUsingMap(scene, element.backgroundImage)).to.be.ok;
test('displays background with background-image', async function() {
expect(backgroundHasMap(scene, element.backgroundImage)).to.be.ok;
});

test('applies background-image envmap on model', async function() {
expect(modelUsingEnvmap(scene, {url: element.backgroundImage})).to.be.ok;
test('applies background-image environment map on model', async function() {
expect(modelUsingEnvMap(scene, {url: element.backgroundImage})).to.be.ok;
});

suite('and background-image subsequently removed', () => {
setup(async () => {
let envmapChanged = waitForEnvmap(scene.model, {url: null});
let envMapChanged = waitForEnvMap(scene.model, {url: null});
element.removeAttribute('background-image');
await envmapChanged;
await envMapChanged;
});

test('displays skysphere with background-color', async function() {
expect(skysphereUsingColor(scene, 'ff0077')).to.be.ok;
test('displays background with background-color', async function() {
expect(backgroundHasColor(scene, 'ff0077')).to.be.ok;
});

test('reapplies generated envmap on model', async function() {
expect(modelUsingEnvmap(scene, {url: null})).to.be.ok;
test('reapplies generated environment map on model', async function() {
expect(modelUsingEnvMap(scene, {url: null})).to.be.ok;
});
});
});
Expand Down
Loading