Skip to content

Commit a8a769e

Browse files
authored
Merge pull request #661 from gkjohnson/orb-update
Update Material database page to use USD material orb
2 parents 7e7be06 + 2ccf57e commit a8a769e

File tree

2 files changed

+128
-77
lines changed

2 files changed

+128
-77
lines changed

example/materialDatabase.js

Lines changed: 41 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,21 @@
11
import {
22
AgXToneMapping,
3-
PerspectiveCamera,
43
Scene,
5-
Box3,
6-
Mesh,
7-
CylinderGeometry,
8-
MeshPhysicalMaterial,
4+
Vector3,
95
WebGLRenderer,
10-
EquirectangularReflectionMapping,
11-
Color,
126
} from 'three';
13-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
147
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
158
import { WebGLPathTracer } from '../src/index.js';
16-
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
17-
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
189
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
1910
import { LoaderElement } from './utils/LoaderElement.js';
2011
import { getScaledSettings } from './utils/getScaledSettings.js';
12+
import { MaterialOrbSceneLoader } from './utils/MaterialOrbLoader.js';
13+
import { RectAreaLightUniformsLib } from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
2114

22-
const MODEL_URL = 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/models/material-balls/material_ball_v2.glb';
23-
const ENV_URL = 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/master/hdri/autoshop_01_1k.hdr';
2415
const DB_URL = 'https://api.physicallybased.info/materials';
25-
const CREDITS = 'Materials courtesy of "physicallybased.info"';
16+
const CREDITS = 'Materials courtesy of "physicallybased.info"</br>Material sphere courtesy of USD Working Group';
2617

27-
let pathTracer, renderer, controls, shellMaterial;
18+
let pathTracer, renderer, controls, material;
2819
let camera, database, scene;
2920
let loader, imgEl;
3021

@@ -41,6 +32,8 @@ init();
4132

4233
async function init() {
4334

35+
RectAreaLightUniformsLib.init();
36+
4437
loader = new LoaderElement();
4538
loader.attach( document.body );
4639

@@ -49,84 +42,42 @@ async function init() {
4942
// renderer
5043
renderer = new WebGLRenderer( { antialias: true } );
5144
renderer.toneMapping = AgXToneMapping;
45+
renderer.toneMappingExposure = 0.02;
5246
document.body.appendChild( renderer.domElement );
5347

5448
// path tracer
5549
pathTracer = new WebGLPathTracer( renderer );
5650
pathTracer.multipleImportanceSampling = params.multipleImportanceSampling;
5751
pathTracer.tiles.set( params.tiles, params.tiles );
52+
pathTracer.textureSize.set( 2048, 2048 );
5853
pathTracer.filterGlossyFactor = 0.5;
5954

60-
// camera
61-
const aspect = window.innerWidth / window.innerHeight;
62-
camera = new PerspectiveCamera( 75, aspect, 0.025, 500 );
63-
camera.position.set( - 4, 2, 3 );
64-
65-
// controls
66-
controls = new OrbitControls( camera, renderer.domElement );
67-
controls.addEventListener( 'change', () => pathTracer.updateCamera() );
68-
6955
// scene
7056
scene = new Scene();
7157

7258
// load assets
73-
const [ envTexture, gltf, dbJson ] = await Promise.all( [
74-
new RGBELoader().loadAsync( ENV_URL ),
75-
new GLTFLoader().setMeshoptDecoder( MeshoptDecoder ).loadAsync( MODEL_URL ),
59+
const [ orb, dbJson ] = await Promise.all( [
60+
new MaterialOrbSceneLoader().loadAsync(),
7661
fetch( DB_URL ).then( res => res.json() ),
7762
] );
7863

79-
// background
80-
envTexture.mapping = EquirectangularReflectionMapping;
81-
scene.background = envTexture;
82-
scene.environment = envTexture;
83-
8464
// scene initialization
85-
gltf.scene.scale.setScalar( 0.01 );
86-
gltf.scene.updateMatrixWorld();
87-
scene.add( gltf.scene );
88-
89-
const box = new Box3();
90-
box.setFromObject( gltf.scene );
91-
92-
const floor = new Mesh(
93-
new CylinderGeometry( 3, 3, 0.05, 200 ),
94-
new MeshPhysicalMaterial( { color: 0xffffff, roughness: 0, metalness: 0.25 } ),
95-
);
96-
floor.geometry = floor.geometry.toNonIndexed();
97-
floor.geometry.clearGroups();
98-
floor.position.y = box.min.y - 0.03;
99-
scene.add( floor );
100-
101-
shellMaterial = new MeshPhysicalMaterial();
102-
const coreMaterial = new MeshPhysicalMaterial( { color: new Color( 0.5, 0.5, 0.5 ) } );
103-
gltf.scene.traverse( c => {
104-
105-
// the vertex normals on the material ball are off...
106-
// TODO: precompute the vertex normals so they are correct on load
107-
if ( c.geometry ) {
108-
109-
c.geometry.computeVertexNormals();
110-
111-
}
112-
113-
if ( c.name === 'Sphere_1' ) {
114-
115-
c.material = coreMaterial;
116-
117-
} else {
65+
scene.add( orb.scene );
66+
camera = orb.camera;
67+
material = orb.material;
11868

119-
c.material = shellMaterial;
69+
// move camera to the scene
70+
scene.attach( camera );
71+
camera.removeFromParent();
12072

121-
}
122-
123-
if ( c.name === 'subsphere_1' ) {
124-
125-
c.material = coreMaterial;
126-
127-
}
73+
// controls
74+
controls = new OrbitControls( camera, renderer.domElement );
75+
controls.addEventListener( 'change', () => pathTracer.updateCamera() );
12876

129-
} );
77+
// shift target
78+
const fwd = new Vector3( 0, 0, - 1 ).transformDirection( camera.matrixWorld ).normalize();
79+
controls.target.copy( camera.position ).addScaledVector( fwd, 25 );
80+
controls.update();
13081

13182
// database set up
13283
database = {};
@@ -172,6 +123,7 @@ function onResize() {
172123

173124
function applyMaterialInfo( info, material ) {
174125

126+
// defaults
175127
material.color.set( 0xffffff );
176128
material.transmission = 0.0;
177129
material.attenuationDistance = Infinity;
@@ -185,6 +137,7 @@ function applyMaterialInfo( info, material ) {
185137
material.iridescenceIOR = 1.0;
186138
material.iridescenceThicknessRange = [ 0, 0 ];
187139

140+
// apply database values
188141
if ( info.specularColor ) material.specularColor.setRGB( ...info.specularColor );
189142
if ( 'metalness' in info ) material.metalness = info.metalness;
190143
if ( 'roughness' in info ) material.roughness = info.roughness;
@@ -200,12 +153,23 @@ function applyMaterialInfo( info, material ) {
200153

201154
if ( material.transmission ) {
202155

203-
if ( info.color ) material.attenuationColor.setRGB( ...info.color );
204-
material.attenuationDistance = 200 / info.density;
156+
if ( info.color ) {
157+
158+
material.attenuationColor.setRGB( ...info.color );
159+
160+
}
161+
162+
// Blender uses 1 / density when exporting volume transmission which doesn't look
163+
// exactly right. But because the scene is 1000x in size we multiply by 1000 here.
164+
material.attenuationDistance = 1000 / info.density;
205165

206166
} else {
207167

208-
if ( info.color ) material.color.setRGB( ...info.color );
168+
if ( info.color ) {
169+
170+
material.color.setRGB( ...info.color );
171+
172+
}
209173

210174
}
211175

@@ -215,7 +179,7 @@ function applyMaterialInfo( info, material ) {
215179

216180
function onParamsChange() {
217181

218-
applyMaterialInfo( database[ params.material ], shellMaterial );
182+
applyMaterialInfo( database[ params.material ], material );
219183

220184
pathTracer.multipleImportanceSampling = params.multipleImportanceSampling;
221185
pathTracer.renderScale = params.renderScale;

example/utils/MaterialOrbLoader.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { MeshPhysicalMaterial, RectAreaLight, SRGBColorSpace } from 'three';
2+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
3+
4+
// TODO: this scene should technically be rendered at a 1000x smaller scale
5+
// TODO: it would be nice to round the orb 3d model a bit more
6+
7+
const ORB_SCENE_URL = 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/models/usd-shader-ball/usd-shaderball-scene.glb';
8+
function assignWatts( light, watts ) {
9+
10+
// https://github.com/will-ca/glTF-Blender-IO/blob/af9e7f06508a95425b05e485fa83681b268bbdfc/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py#L92-L97
11+
const PBR_WATTS_TO_LUMENS = 683;
12+
const area = light.width * light.height;
13+
const lumens = PBR_WATTS_TO_LUMENS * watts;
14+
light.intensity = lumens / ( area * 4 * Math.PI );
15+
16+
}
17+
18+
export class MaterialOrbSceneLoader {
19+
20+
constructor( manager ) {
21+
22+
this.manager = manager;
23+
24+
}
25+
26+
loadAsync( url = ORB_SCENE_URL, ...rest ) {
27+
28+
return new GLTFLoader( this.manager )
29+
.loadAsync( url, ...rest )
30+
.then( gltf => {
31+
32+
const {
33+
scene,
34+
cameras,
35+
} = gltf;
36+
37+
const leftLight = new RectAreaLight( 0xffffff, 1, 15, 15 );
38+
assignWatts( leftLight, 6327.84 );
39+
scene.getObjectByName( 'light' ).add( leftLight );
40+
41+
for ( let i = 0; i < 4; i ++ ) {
42+
43+
const light = new RectAreaLight( 0xffffff, 1, 24.36, 24.36 );
44+
assignWatts( light, 11185.5 );
45+
scene.getObjectByName( 'light' + i ).add( light );
46+
47+
}
48+
49+
// TODO: why is this necessary?
50+
const camera = cameras[ 0 ];
51+
camera.fov *= 2.0;
52+
camera.updateProjectionMatrix();
53+
54+
// some objects in the scene use 16 bit float vertex colors so we disable them here
55+
scene.traverse( c => {
56+
57+
if ( c.material ) {
58+
59+
c.material.vertexColors = false;
60+
61+
}
62+
63+
// TODO: this should be colored by a texture that says "Sub-Surface Bar"
64+
if ( c.name === 'sss_bars' ) {
65+
66+
c.material.color.setRGB( 0.01, 0.01, 0.01, SRGBColorSpace );
67+
68+
}
69+
70+
} );
71+
72+
const material = new MeshPhysicalMaterial();
73+
scene.getObjectByName( 'material_surface' ).material = material;
74+
75+
return {
76+
77+
material,
78+
camera,
79+
scene,
80+
81+
};
82+
83+
} );
84+
85+
}
86+
87+
}

0 commit comments

Comments
 (0)