initial pbr shading support.

various improvements.
This commit is contained in:
Syoyo Fujita
2025-06-02 13:47:04 +09:00
parent 20ad03caf8
commit 13a29e7ebd
7 changed files with 629 additions and 315 deletions

69
models/suzanne-pbr.usda Executable file

File diff suppressed because one or more lines are too long

View File

@@ -301,19 +301,20 @@ class TinyUSDZLoaderNative {
}
#endif
bool loadFromBinary(const std::string &binary) {
bool loadFromBinary(const std::string &binary, const std::string &filename) {
bool is_usdz = tinyusdz::IsUSDZ(
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size());
tinyusdz::Stage stage;
loaded_ = tinyusdz::LoadUSDFromMemory(
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(),
"dummy.usda", &stage, &warn_, &error_);
filename, &stage, &warn_, &error_);
if (!loaded_) {
return false;
}
tinyusdz::tydra::RenderSceneConverterEnv env(stage);
//
@@ -371,6 +372,8 @@ class TinyUSDZLoaderNative {
return false;
}
filename_ = filename;
return true;
}
~TinyUSDZLoaderNative() {}
@@ -645,6 +648,11 @@ class TinyUSDZLoaderNative {
void setEnableComposition(bool enabled) { enableComposition_ = enabled; }
// Return filename passed to loadFromBinary.
std::string getURI() const {
return filename_;
}
// TODO: Deprecate
bool ok() const { return loaded_; }
@@ -687,6 +695,7 @@ class TinyUSDZLoaderNative {
bool loaded_{false};
bool enableComposition_{false};
std::string filename_;
std::string warn_;
std::string error_;
@@ -892,6 +901,7 @@ EMSCRIPTEN_BINDINGS(tinyusdz_module) {
.function("loadAsync", &TinyUSDZLoaderNative::loadAsync)
#endif
.function("loadFromBinary", &TinyUSDZLoaderNative::loadFromBinary)
.function("getURI", &TinyUSDZLoaderNative::getURI)
.function("getMesh", &TinyUSDZLoaderNative::getMesh)
.function("numMeshes", &TinyUSDZLoaderNative::numMeshes)
.function("getMaterial", &TinyUSDZLoaderNative::getMaterial)

View File

@@ -29,6 +29,7 @@ class TinyUSDZLoader extends Loader {
this.imageCache = {};
this.textureCache = {};
this.enableComposition_ = false;
}
// Initialize the native WASM module
@@ -47,11 +48,15 @@ class TinyUSDZLoader extends Loader {
return this;
}
//
setEnableComposition(enabled) {
this.enableComposition_ = enabled;
}
// Set AssetResolver callback.
// This is used to resolve asset paths(e.g. textures, usd files) in the USD.
// For web app, usually we'll convert asset path to URI
setAssetResolver( callback ) {
setAssetResolver(callback) {
this.assetResolver_ = callback;
}
@@ -81,9 +86,9 @@ class TinyUSDZLoader extends Loader {
console.log('Loaded USD binary data:', usd_binary.length, 'bytes');
//return this.parse(usd_binary);
scope.parse(usd_binary, function(usd) {
scope.parse(usd_binary, url, function (usd) {
onLoad(usd);
} , onError);
}, onError);
})
.catch((error) => {
@@ -97,24 +102,24 @@ class TinyUSDZLoader extends Loader {
//
// Parse a USDZ/USDA/USDC binary data
//
parse( binary /* ArrayBuffer */, onLoad, onError ) {
parse(binary /* ArrayBuffer */, filePath /* optional */, onLoad, onError) {
const _onError = function ( e ) {
const _onError = function (e) {
if ( onError ) {
if (onError) {
onError( e );
onError(e);
} else {
} else {
console.error( e );
console.error(e);
}
}
//scope.manager.itemError( url );
//scope.manager.itemEnd( url );
//scope.manager.itemError( url );
//scope.manager.itemEnd( url );
};
};
if (!this.native_) {
console.error('TinyUSDZLoader: Native module is not initialized.');
@@ -123,7 +128,7 @@ class TinyUSDZLoader extends Loader {
const usd = new this.native_.TinyUSDZLoaderNative();
const ok = usd.loadFromBinary(binary);
const ok = usd.loadFromBinary(binary, filePath);
if (!ok) {
_onError(new Error('TinyUSDZLoader: Failed to load USD from binary data.'));
} else {

View File

@@ -51,15 +51,15 @@ class TinyUSDZLoaderUtils extends LoaderUtils {
// - [x] occlusion -> aoMap
// - [x] normal -> normalMap
// - [x] displacement -> displacementMap
static convertUsdMaterialToMeshPhysicalMaterial(usdMaterial, usd) {
static convertUsdMaterialToMeshPhysicalMaterial(usdMaterial, usdScene) {
const material = new THREE.MeshPhysicalMaterial();
// Helper function to create texture from USD texture ID
function createTextureFromUSD(textureId) {
if (textureId === undefined) return null;
const tex = usd.getTexture(textureId);
const img = usd.getImage(tex.textureImageId);
const tex = usdScene.getTexture(textureId);
const img = usdScene.getImage(tex.textureImageId);
const image8Array = new Uint8ClampedArray(img.data);
const texture = new THREE.DataTexture(image8Array, img.width, img.height);
@@ -79,6 +79,8 @@ class TinyUSDZLoaderUtils extends LoaderUtils {
if (usdMaterial.hasOwnProperty('diffuseColorTextureId')) {
material.map = createTextureFromUSD(usdMaterial.diffuseColorTextureId);
console.log("has diffuse tex");
}
// IOR
@@ -105,13 +107,13 @@ class TinyUSDZLoaderUtils extends LoaderUtils {
}
if (material.useSpecularWorkflow) {
material.specular = new THREE.Color(0.0, 0.0, 0.0);
material.specularColor = new THREE.Color(0.0, 0.0, 0.0);
if (usdMaterial.hasOwnProperty('specularColor')) {
const color = usdMaterial.specularColor;
material.specular = new THREE.Color(color[0], color[1], color[2]);
material.specularColor = new THREE.Color(color[0], color[1], color[2]);
}
if (usdMaterial.hasOwnProperty('specularColorTextureId')) {
material.specularMap = createTextureFromUSD(usdMaterial.specularColorTextureId);
material.specularColorMap = createTextureFromUSD(usdMaterial.specularColorTextureId);
}
} else {
material.metalness = 0.0;
@@ -172,6 +174,133 @@ class TinyUSDZLoaderUtils extends LoaderUtils {
return material;
}
static convertUsdMeshToThreeMesh(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.points, 3));
// Assume mesh is triangulated.
// itemsize = 1 since Index expects IntArray for VertexIndices in Three.js?
geometry.setIndex(new THREE.BufferAttribute(mesh.faceVertexIndices, 1));
if (mesh.hasOwnProperty('texcoords')) {
geometry.setAttribute('uv', new THREE.BufferAttribute(mesh.texcoords, 2));
}
// TODO: uv1
// faceVarying normals
if (mesh.hasOwnProperty('normals')) {
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.normals, 3));
} else {
geometry.computeVertexNormals();
}
if (mesh.hasOwnProperty('vertexColors')) {
geometry.setAttribute('color', new THREE.BufferAttribute(mesh.vertexColors, 3));
}
// Only compute tangents if we have both UV coordinates and normals
if (mesh.hasOwnProperty('tangents')) {
geometry.setAttribute('tangent', new THREE.BufferAttribute(mesh.tangents, 3));
} else if (mesh.hasOwnProperty('texcoords') && (mesh.hasOwnProperty('normals') || geometry.attributes.normal)) {
// TODO: try MikTSpace tangent algorithm: https://threejs.org/docs/#examples/en/utils/BufferGeometryUtils.computeMikkTSpaceTangents
geometry.computeTangents();
}
// TODO: vertex opacities(per-vertex alpha)
return geometry;
}
static setupMesh(mesh /* TinyUSDZLoaderNative::RenderMesh */, defaultMtl, usdScene, options) {
const geometry = this.convertUsdMeshToThreeMesh(mesh);
const normalMtl = new THREE.MeshNormalMaterial();
let mtl = null;
//console.log("overrideMaterial:", options.overrideMaterial);
if (options.overrideMaterial) {
mtl = defaultMtl || normalMtl
} else {
const usdMaterial = usdScene.getMaterial(mesh.materialId);
const pbrMaterial = this.convertUsdMaterialToMeshPhysicalMaterial(usdMaterial, usdScene);
//console.log("pbrMaterial:", pbrMaterial);
// Setting envmap is required for PBR materials to work correctly(e.g. clearcoat)
pbrMaterial.envMap = options.envMap || null;
pbrMaterial.envMapIntensity = options.envMapIntensity || 1.0;
console.log("envmap:", options.envMap);
mtl = pbrMaterial || defaultMtl || normalMtl;
}
const threeMesh = new THREE.Mesh(geometry, mtl );
return threeMesh;
}
// arr = float array with 16 elements(row major order)
static toMatrix4(a) {
const m = new THREE.Matrix4();
m.set(a[0], a[1], a[2], a[3],
a[4], a[5], a[6], a[7],
a[8], a[9], a[10], a[11],
a[12], a[13], a[14], a[15]);
return m;
}
// Supported options
// 'overrideMaterial' : Override usd material with defaultMtl.
static buildThreeNode(usdNode /* TinyUSDZLoader.Node */, defaultMtl = null, usdScene /* TinyUSDZLoader.Scene */ = null, options = {})
/* => THREE.Object3D */ {
var node = new THREE.Group();
if (usdNode.nodeType == 'xform') {
// intermediate xform node
// TODO: create THREE.Group and apply transform.
node.matrix = this.toMatrix4(usdNode.localMatrix);
} else if (usdNode.nodeType == 'mesh') {
// contentId is the mesh ID in the USD scene.
const mesh = usdScene.getMesh(usdNode.contentId);
const threeMesh = this.setupMesh(mesh, defaultMtl, usdScene, options);
node = threeMesh;
} else {
// ???
}
node.name = usdNode.primName;
node.userData['primMeta.displayName'] = usdNode.displayName;
node.userData['primMeta.absPath'] = usdNode.absPath;
// traverse children
for (const child of usdNode.children) {
const childNode = this.buildThreeNode(child, defaultMtl, usdScene, options);
node.add(childNode);
}
return node;
}
}
export { TinyUSDZLoaderUtils };
export { TinyUSDZLoaderUtils };

View File

@@ -11,7 +11,8 @@
<script>
</script>
<!-- <script type="module" src="./main.js"></script> -->
<script type="module" src="./example-composition.js"></script>
<script type="module" src="./main.js"></script>
<!-- <script type="module" src="./example-composition.js"></script> -->
<!-- <script type="module" src="./previewsurface-experiment.js"></script> -->
</body>
</html>

View File

@@ -1,221 +1,15 @@
import * as THREE from 'three';
//import * as THREE from 'https://cdn.jsdelivr.net/npm/three/build/three.module.js';
import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
import { GUI } from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.module.js';
//import initTinyUSDZ from 'https://lighttransport.github.io/tinyusdz/tinyusdz.js';
// For developer
//import initTinyUSDZ from './tinyusdz.js';
import { TinyUSDZLoader } from './TinyUSDZLoader.js'
import { TinyUSDZLoaderUtils } from './TinyUSDZLoaderUtils.js'
//const USDZ_FILEPATH = './UsdCookie.usdz';
//const usd_res = await fetch(USDZ_FILEPATH);
//const usd_data = await usd_res.arrayBuffer();
//const usd_binary = new Uint8Array(usd_data);
/*
initTinyUSDZ().then(async function(TinyUSDZLoaderNative) {
// Setup the async loader helper before attempting to use it
//console.log("Setting up async loader...");
//TinyUSDZLoaderNative.setupAsyncLoader();
//console.log("Async loader setup complete.");
const gui = new GUI();
// FIXME
let y_rot_value = 0.02;
let exposure = 3.0;
let ambient = 0.4
let ambientLight = new THREE.AmbientLight(0x404040, ambient);
// Create a parameters object
const params = {
rotationSpeed: y_rot_value,
wireframe: false,
ambient: ambient,
exposure: exposure,
};
// Add controls
gui.add(params, 'rotationSpeed', 0, 0.1).name('Rotation Speed').onChange((value) => {
y_rot_value = value;
});
gui.add(params, 'ambient', 0, 10).name('Ambient').onChange((value) => {
ambient = value;
ambientLight.intensity = ambient;
});
gui.add(params, 'exposure', 0, 10).name('Intensity').onChange((value) => {
exposure = value;
renderer.toneMappingExposure = exposure;
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
// Set up better rendering for PBR materials
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = exposure;
renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild( renderer.domElement );
// Add basic lighting for PBR materials
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
// Add loading indicator
const loadingDiv = document.createElement('div');
loadingDiv.innerHTML = 'Loading USD file...';
loadingDiv.style.position = 'absolute';
loadingDiv.style.top = '50%';
loadingDiv.style.left = '50%';
loadingDiv.style.transform = 'translate(-50%, -50%)';
loadingDiv.style.color = 'white';
loadingDiv.style.fontSize = '20px';
loadingDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
loadingDiv.style.padding = '20px';
loadingDiv.style.borderRadius = '10px';
document.body.appendChild(loadingDiv);
try {
// Create loader instance without loading
const usd = new TinyUSDZLoaderNative.TinyUSDZLoaderNative();
// Load asynchronously with detailed error handling
console.log('Starting async USD loading...');
console.log('Module functions:', Object.keys(TinyUSDZLoaderNative).join(', '));
console.log('usd_binary length:', usd_binary.length);
try {
await usd.loadAsync(usd_binary);
console.log('USD loading completed!');
} catch (error) {
console.error('Async loading error:', error);
throw new Error(`Failed to load USD file asynchronously: ${error.message}`);
}
console.log('USD loading completed!');
// Remove loading indicator
document.body.removeChild(loadingDiv);
// First mesh only
const mesh = usd.getMesh(0);
console.log("mesh loaded:", mesh);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', new THREE.BufferAttribute( mesh.points, 3 ) );
if (mesh.hasOwnProperty('texcoords')) {
geometry.setAttribute( 'uv', new THREE.BufferAttribute( mesh.texcoords, 2 ) );
}
const usdMaterial = usd.getMaterial(mesh.materialId);
console.log("usdMaterial:", usdMaterial);
var material;
// Use the proper material conversion function
material = ConvertUsdPreviewSurfaceToMeshPhysicalMaterial(usdMaterial, usd);
// Assume triangulated indices.
geometry.setIndex( new THREE.Uint32BufferAttribute(mesh.faceVertexIndices, 1) );
geometry.computeVertexNormals();
const mesh0 = new THREE.Mesh( geometry, material );
scene.add( mesh0 );
camera.position.z = 1.0;
function animate() {
mesh0.rotation.y += y_rot_value;
renderer.render( scene, camera );
}
renderer.setAnimationLoop( animate );
} catch (error) {
console.error('Failed to load USD file:', error);
// Remove loading indicator
if (document.body.contains(loadingDiv)) {
document.body.removeChild(loadingDiv);
}
// Show error message
const errorDiv = document.createElement('div');
errorDiv.innerHTML = `Error loading USD file: ${error.message}`;
errorDiv.style.position = 'absolute';
errorDiv.style.top = '50%';
errorDiv.style.left = '50%';
errorDiv.style.transform = 'translate(-50%, -50%)';
errorDiv.style.color = 'red';
errorDiv.style.fontSize = '16px';
errorDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
errorDiv.style.padding = '20px';
errorDiv.style.borderRadius = '10px';
document.body.appendChild(errorDiv);
}
});
*/
import { TinyUSDZComposer } from './TinyUSDZComposer.js'
import { createTypeReferenceDirectiveResolutionCache } from 'typescript';
const manager = new THREE.LoadingManager();
const gui = new GUI();
// FIXME
let y_rot_value = 0.02;
let exposure = 3.0;
let ambient = 0.4
let ambientLight = new THREE.AmbientLight(0x404040, ambient);
let camera_z = 1.4; // TODO: Compute best fit from scene's bbox.
let shader_normal = false;
let material_changed = false;
// Create a parameters object
const params = {
rotationSpeed: y_rot_value,
wireframe: false,
ambient: ambient,
exposure: exposure,
camera_z: camera_z,
shader_normal: shader_normal,
};
// Add controls
gui.add(params, 'camera_z', 0, 10).name('Camera Z').onChange((value) => {
camera_z = value;
});
gui.add(params, 'rotationSpeed', 0, 0.1).name('Rotation Speed').onChange((value) => {
y_rot_value = value;
});
gui.add(params, 'ambient', 0, 10).name('Ambient').onChange((value) => {
ambient = value;
ambientLight.intensity = ambient;
});
gui.add(params, 'exposure', 0, 10).name('Intensity').onChange((value) => {
exposure = value;
renderer.toneMappingExposure = exposure;
});
gui.add(params, 'shader_normal').name('NormalMaterial').onChange((value) => {
shader_normal = value;
material_changed = true;
//jrenderer.toneMappingExposure = exposure;
});
const suzanne_filename = "./suzanne.usdc";
const cookie_filename = "./UsdCookie.usdc";
// Initialize loading manager with URL callback.
const objectURLs = [];
manager.setURLModifier((url) => {
@@ -228,70 +22,129 @@ manager.setURLModifier((url) => {
});
const gui = new GUI();
let ui_state = {}
ui_state['rot_scale'] = 1.0;
ui_state['defaultMtl'] = TinyUSDZLoaderUtils.createDefaultMaterial();
ui_state['envMapIntensity'] = 3.0; // good for pisaHDR;
ui_state['ambient'] = 0.4;
let ambientLight = new THREE.AmbientLight(0x404040, ui_state['ambient']);
ui_state['camera_z'] = 4; // TODO: Compute best fit from scene's bbox.
// Create a parameters object
const params = {
envMapIntensity: ui_state['envMapIntensity'],
rotationSpeed: ui_state['rot_scale'],
camera_z: ui_state['camera_z'],
};
// Add controls
gui.add(params, 'envMapIntensity', 0, 20, 0.1).name('envMapIntensity').onChange((value) => {
ui_state['envMapIntensity'] = value;
});
gui.add(params, 'camera_z', 0, 20).name('Camera Z').onChange((value) => {
ui_state['camera_z'] = value;
});
gui.add(params, 'rotationSpeed', 0, 10).name('Rotation Speed').onChange((value) => {
ui_state['rot_scale'] = value;
});
async function loadScenes() {
const loader = new TinyUSDZLoader();
const suzanne_filename = "./suzanne-pbr.usda";
const texcat_filename = "./texture-cat-plane.usdz";
const cookie_filename = "./UsdCookie.usdz";
var threeScenes = []
const usd_scenes = await Promise.all([
loader.loadAsync(texcat_filename),
loader.loadAsync(cookie_filename),
loader.loadAsync(suzanne_filename),
]);
console.log("usd_scenes:", usd_scenes);
const defaultMtl = ui_state['defaultMtl'];
const options = {
overrideMaterial: false, // override USD material with defaultMtl(default 'false')
envMap: defaultMtl.envMap, // reuse envmap from defaultMtl
envMapIntensity: ui_state['envmapIntensity'], // default envmap intensity
}
var offset = -(usd_scenes.length-1) * 1.5;
for (const usd_scene of usd_scenes) {
//console.log("usd_scene:", usd_scene);
const usdRootNode = usd_scene.getDefaultRootNode();
//console.log("scene:", usdRootNode);
const threeNode = TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options);
// HACK
if (usd_scene.getURI().includes('UsdCookie')) {
//console.log("UsdCookie");
// Add exra scaling
threeNode.scale.x *= 2.5;
threeNode.scale.y *= 2.5;
threeNode.scale.z *= 2.5;
}
// HACK
threeNode.position.x += offset;
offset += 3.0;
threeScenes.push(threeNode);
}
return threeScenes;
}
const scene = new THREE.Scene();
const envmap = await new HDRCubeTextureLoader()
.setPath( 'textures/cube/pisaHDR/' )
.loadAsync( [ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ] )
scene.background = envmap;
scene.environment = envmap;
// Assign envmap to material
// Otherwise some material parameters like clarcoat will not work properly.
ui_state['defaultMtl'].envMap = envmap;
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
camera.position.z = ui_state['camera_z'];
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//const geometry = new THREE.BoxGeometry(1, 1, 1);
//const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
//const cube = new THREE.Mesh(geometry, material);
//scene.add(cube);
console.log("loading scenes...");
const rootNodes = await loadScenes();
const usdLoader = new TinyUSDZLoader();
//const usd = await usdLoader.loadAsync(cookie_filename);
//const usd = await usdLoader.loadAsync(suzanne_filename);
//
usdLoader.load(suzanne_filename, (usd) => {
console.log('USD file loaded:', usd);
if (usd.numMeshes() < 1) {
console.error("No meshes in USD");
}
// First mesh only
const mesh = usd.getMesh(0);
console.log("mesh loaded:", mesh);
const geometry = usdMeshToThreeMesh(mesh);
const usdMaterial = usd.getMaterial(mesh.materialId);
console.log("usdMaterial:", usdMaterial);
//const pbrMaterial = TinyUSDZLoader.ConvertUsdPreviewSurfaceToMeshPhysicalMaterial(usdMaterial, usd);
const pbrMaterial = new THREE.MeshPhysicalMaterial();
const normalMat = new THREE.MeshNormalMaterial();
const usd_util = new TinyUSDZLoaderUtils();
const baseMat = TinyUSDZLoaderUtils.createDefaultMaterial();
const mesh0 = new THREE.Mesh(geometry, baseMat);
//const mesh0 = new THREE.Mesh(geometry, baseMat);
scene.add(mesh0);
for (const rootNode of rootNodes) {
scene.add(rootNode);
}
function animate() {
//cube.rotation.x += 0.01;
mesh0.rotation.y += y_rot_value;
camera.position.z = camera_z;
if (material_changed) {
material_changed = false;
if (shader_normal) {
mesh0.material = normalMat;
} else {
mesh0.material = pbrMaterial;
}
for (const rootNode of rootNodes) {
rootNode.rotation.y += 0.01 * ui_state['rot_scale'];
rootNode.rotation.x += 0.02 * ui_state['rot_scale'];
}
camera.position.z = ui_state['camera_z'];
renderer.render(scene, camera);
@@ -299,32 +152,3 @@ usdLoader.load(suzanne_filename, (usd) => {
}
renderer.setAnimationLoop(animate);
});
function usdMeshToThreeMesh(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.points, 3));
// Assume mesh is triangulated.
// itemsize = 1 since Index expects IntArray for VertexIndices in Three.js?
geometry.setIndex(new THREE.BufferAttribute(mesh.faceVertexIndices, 1));
if (mesh.hasOwnProperty('texcoords')) {
geometry.setAttribute('uv', new THREE.BufferAttribute(mesh.texcoords, 2));
}
//// faceVarying normals
if (mesh.hasOwnProperty('normals')) {
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.normals, 3));
} else {
geometry.computeVertexNormals();
}
return geometry;
}

View File

@@ -0,0 +1,276 @@
import * as THREE from 'three';
import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
import { GUI } from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.module.js';
import { TinyUSDZLoader } from './TinyUSDZLoader.js'
import { TinyUSDZLoaderUtils } from './TinyUSDZLoaderUtils.js'
import { TinyUSDZComposer } from './TinyUSDZComposer.js'
import { createTypeReferenceDirectiveResolutionCache } from 'typescript';
const manager = new THREE.LoadingManager();
// Initialize loading manager with URL callback.
const objectURLs = [];
manager.setURLModifier((url) => {
console.log(url);
url = URL.createObjectURL(blobs[url]);
objectURLs.push(url);
return url;
});
const gui = new GUI();
let ui_state = {}
ui_state['rot_scale'] = 1.0;
ui_state['defaultMtl'] = TinyUSDZLoaderUtils.createDefaultMaterial();
ui_state['exposure'] = 3.0;
ui_state['ambient'] = 0.4;
let ambientLight = new THREE.AmbientLight(0x404040, ui_state['ambient']);
ui_state['camera_z'] = 4; // TODO: Compute best fit from scene's bbox.
ui_state['shader_normal'] = false;
ui_state['material_changed'] = false;
// Default PBR mateiral params
ui_state['diffuse'] = new THREE.Color(49, 49, 49); // 0.18
ui_state['emissive'] = new THREE.Color(0, 0, 0);
ui_state['roughness'] = 0.5;
ui_state['metalness'] = 0.0;
ui_state['clearcoat'] = 0.0;
ui_state['clearcoatRoughness'] = 0.0;
ui_state['ior'] = 1.5;
ui_state['specularIntensity'] = 1.0;
ui_state['opacity'] = 1.0;
// Create a parameters object
const params = {
rotationSpeed: ui_state['rot_scale'],
camera_z: ui_state['camera_z'],
shader_normal: ui_state['shader_normal'],
diffuse: ui_state['diffuse'],
emissive: ui_state['emissive'],
roughness: ui_state['roughness'],
metalness: ui_state['metalness'],
clearcoat: ui_state['clearcoat'],
clearcoatRoughness: ui_state['clearcoatRoughness'],
specularIntensity: ui_state['specularIntensity'],
opacity: ui_state['opacity'],
ior: ui_state['ior'],
};
// Add controls
gui.add(params, 'camera_z', 0, 20).name('Camera Z').onChange((value) => {
ui_state['camera_z'] = value;
});
gui.add(params, 'rotationSpeed', 0, 10).name('Rotation Speed').onChange((value) => {
ui_state['rot_scale'] = value;
});
gui.addColor(params, 'diffuse').name('color').onChange((value) => {
ui_state['diffuse'] = value;
ui_state['material_changed'] = true;
});
gui.addColor(params, 'emissive').name('emissive').onChange((value) => {
ui_state['emissive'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'roughness', 0.0, 1.0, 0.01).name('roughness').onChange((value) => {
ui_state['roughness'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'metalness', 0.0, 1.0, 0.01).name('metalness').onChange((value) => {
ui_state['metalness'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'ior', 1.0, 2.4, 0.1).name('ior').onChange((value) => {
ui_state['ior'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'clearcoat', 0.0, 1.0, 0.01).name('clearcoat').onChange((value) => {
ui_state['clearcoat'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'clearcoatRoughness', 0.0, 1.0).step(0.01).name('clearcoatRoughness').onChange((value) => {
ui_state['clearcoatRoughness'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'specularIntensity', 0.0, 1.0, 0.01).name('specular').onChange((value) => {
ui_state['specularIntensity'] = value;
ui_state['material_changed'] = true;
});
gui.add(params, 'opacity', 0.0, 1.0, 0.01).name('opacity').onChange((value) => {
ui_state['opacity'] = value;
ui_state['material_changed'] = true;
});
/* TODO
gui.add(params, 'transmission', 0.0, 1.0, 0.01).name('transmission').onChange((value) => {
ui_state['transmission'] = value;
ui_state['material_changed'] = true;
});
*/
gui.add(params, 'shader_normal').name('NormalMaterial').onChange((value) => {
ui_state['shader_normal'] = value;
ui_state['material_changed'] = true;
});
async function loadScenes() {
const loader = new TinyUSDZLoader();
const suzanne_filename = "./suzanne.usdc";
const texcat_filename = "./texture-cat-plane.usdz";
const cookie_filename = "./UsdCookie.usdz";
var threeScenes = []
const usd_scenes = await Promise.all([
loader.loadAsync(texcat_filename),
loader.loadAsync(cookie_filename),
loader.loadAsync(suzanne_filename),
]);
console.log("usd_scenes:", usd_scenes);
const defaultMtl = ui_state['defaultMtl'];
const options = {
overrideMaterial: true // override USD material with defaultMtl
}
var offset = -(usd_scenes.length-1) * 1.5;
for (const usd_scene of usd_scenes) {
//console.log("usd_scene:", usd_scene);
const usdRootNode = usd_scene.getDefaultRootNode();
//console.log("scene:", usdRootNode);
const threeNode = TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options);
// HACK
if (usd_scene.getURI().includes('UsdCookie')) {
console.log("UsdCookie");
// Add exra scaling
threeNode.scale.x *= 2.5;
threeNode.scale.y *= 2.5;
threeNode.scale.z *= 2.5;
}
// HACK
threeNode.position.x += offset;
offset += 3.0;
threeScenes.push(threeNode);
}
return threeScenes;
}
const scene = new THREE.Scene();
const envmap = await new HDRCubeTextureLoader()
.setPath( 'textures/cube/pisaHDR/' )
.loadAsync( [ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ] )
scene.background = envmap;
scene.environment = envmap;
// Assign envmap to material
// Otherwise some material parameters like clarcoat will not work properly.
ui_state['defaultMtl'].envMap = envmap;
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = ui_state['camera_z'];
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
console.log("loading scenes...");
const rootNodes = await loadScenes();
for (const rootNode of rootNodes) {
scene.add(rootNode);
}
function animate() {
for (const rootNode of rootNodes) {
rootNode.rotation.y += 0.01 * ui_state['rot_scale'];
rootNode.rotation.x += 0.02 * ui_state['rot_scale'];
}
camera.position.z = ui_state['camera_z'];
if (ui_state['material_changed']) {
ui_state['material_changed'] = false;
if (ui_state['shader_normal']) {
//mesh0.material = normalMat;
for (const rootNode of rootNodes) {
rootNode.rotation.y += 0.01 * ui_state['rot_scale'];
rootNode.rotation.x += 0.02 * ui_state['rot_scale'];
}
} else {
//mesh0.material = pbrMaterial;
}
// HACK
ui_state['defaultMtl'].color.r = ui_state['diffuse'].r / 255.0;
ui_state['defaultMtl'].color.g = ui_state['diffuse'].g / 255.0;
ui_state['defaultMtl'].color.b = ui_state['diffuse'].b / 255.0;
console.log("diffuse", ui_state['diffuse']);
console.log("mat_diffuse", ui_state['defaultMtl'].color);
ui_state['defaultMtl'].emissive.r = ui_state['emissive'].r / 255.0;
ui_state['defaultMtl'].emissive.g = ui_state['emissive'].g / 255.0;
ui_state['defaultMtl'].emissive.b = ui_state['emissive'].b / 255.0;
ui_state['defaultMtl'].roughness = ui_state['roughness'];
ui_state['defaultMtl'].metalness = ui_state['metalness'];
ui_state['defaultMtl'].ior = ui_state['ior'];
ui_state['defaultMtl'].clearcoat = ui_state['clearcoat'];
ui_state['defaultMtl'].clearcoatRoughness = ui_state['clearcoatRoughness'];
ui_state['defaultMtl'].specularIntensity = ui_state['specularIntensity'];
ui_state['defaultMtl'].opacity = ui_state['opacity'];
ui_state['defaultMtl'].transparent = (ui_state['opacity'] < 1.0);
ui_state['defaultMtl'].needsUpdate = true;
}
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);