mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
initial pbr shading support.
various improvements.
This commit is contained in:
69
models/suzanne-pbr.usda
Executable file
69
models/suzanne-pbr.usda
Executable file
File diff suppressed because one or more lines are too long
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
402
web/demo/main.js
402
web/demo/main.js
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
276
web/demo/previewsurface-experiment.js
Normal file
276
web/demo/previewsurface-experiment.js
Normal 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);
|
||||
Reference in New Issue
Block a user