From f00415354118e74e6777cb3daffd1e2dd88ce4d2 Mon Sep 17 00:00:00 2001 From: Bernard Kwok Date: Wed, 10 Jan 2024 13:21:54 -0500 Subject: [PATCH] Renderable logic improvements to web viewer (#1644) - Adds in proper parsing of renderable elements vs always just picking the first surface shader found. - The test suite files which have (multiple) nodegraph outputs and top level outputs will now load properly. - Adds in UI to mark folders as renderable (using a shaderball icon) - Adds in "soloing" capability to allow picking a renderable and have it show up on all geometry. When nothing is solo'ed the default material assignment is used. - Includes fixes for: - Dag path assignment matching . - Missing exposure of NodeGraph::getDownStreamPorts() in JS. - Addressing the big performance hit when binding materials to geometry in ThreeJS. The code by default is some quite slow code for reflection / debugging purposes which is now turned off. Chess set load is seconds vs minutes. This affects the 'solo'ing workflow significantly as each switch is a geometry re-bind. If the material is not already cached then slow code will be hit which can cause seconds to pass when selecting a new material -- which appears like a "hang" from a user perspective. --- javascript/MaterialXView/index.ejs | 7 + .../MaterialXView/public/shader_ball.svg | 5 + .../MaterialXView/public/shader_ball2.svg | 8 + javascript/MaterialXView/source/index.js | 7 + javascript/MaterialXView/source/viewer.js | 298 ++++++++++++++++-- source/JsMaterialX/JsMaterialXCore/JsNode.cpp | 1 + 6 files changed, 296 insertions(+), 30 deletions(-) create mode 100644 javascript/MaterialXView/public/shader_ball.svg create mode 100644 javascript/MaterialXView/public/shader_ball2.svg diff --git a/javascript/MaterialXView/index.ejs b/javascript/MaterialXView/index.ejs index 3d898899c2..8f278051af 100644 --- a/javascript/MaterialXView/index.ejs +++ b/javascript/MaterialXView/index.ejs @@ -19,6 +19,13 @@ .peditorfolder { background-color: #333333; } + + .peditor_material_assigned { + background-color: #006cb8; + } + .peditor_material_assigned:hover { + background-color: #32adff; + } diff --git a/javascript/MaterialXView/public/shader_ball.svg b/javascript/MaterialXView/public/shader_ball.svg new file mode 100644 index 0000000000..2b51705a42 --- /dev/null +++ b/javascript/MaterialXView/public/shader_ball.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/javascript/MaterialXView/public/shader_ball2.svg b/javascript/MaterialXView/public/shader_ball2.svg new file mode 100644 index 0000000000..62907b5df8 --- /dev/null +++ b/javascript/MaterialXView/public/shader_ball2.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/javascript/MaterialXView/source/index.js b/javascript/MaterialXView/source/index.js index ddd1ec8ffc..d11ba9691e 100644 --- a/javascript/MaterialXView/source/index.js +++ b/javascript/MaterialXView/source/index.js @@ -62,6 +62,13 @@ function init() // Set up renderer renderer = new THREE.WebGLRenderer({ canvas, context }); renderer.setSize(window.innerWidth, window.innerHeight); + // Disable introspection for shader debugging for deployment. + // - The code associated with getting program information can be very slow when + // dealing with shaders with lots of input uniforms (such as standard surface, openpbr shading models) + // as each call is blocking. + // - Adding this avoids the chess set scene from "hanging" the Chrome browser on Windows to a few second load. + // - Documentation for this flag: https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderer.debug + renderer.debug.checkShaderErrors = false; composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene.getScene(), scene.getCamera()); diff --git a/javascript/MaterialXView/source/viewer.js b/javascript/MaterialXView/source/viewer.js index 50e51b67c0..9992dd6985 100644 --- a/javascript/MaterialXView/source/viewer.js +++ b/javascript/MaterialXView/source/viewer.js @@ -111,7 +111,8 @@ export class Scene console.log("Total geometry load time: ", performance.now() - startTime, " ms."); - viewer.getMaterial().updateMaterialAssignments(viewer); + viewer.getMaterial().clearSoloMaterialUI(); + viewer.getMaterial().updateMaterialAssignments(viewer, ""); this.setUpdateTransforms(); } @@ -303,13 +304,16 @@ export class Scene } else { - const paths = geometry.split(','); - for (let path of paths) + if (geometry != NO_GEOMETRY_SPECIFIER) { - if (dagPath.match(path)) + const paths = geometry.split(','); + for (let path of paths) { - matches = true; - break; + if (dagPath.match(path)) + { + matches = true; + break; + } } } } @@ -460,6 +464,17 @@ class MaterialAssign this._geometry = geometry; this._collection = collection; this._shader = null; + this._materialUI = null; + } + + setMaterialUI(value) + { + this._materialUI = value; + } + + getMaterialUI() + { + return this._materialUI; } setShader(shader) @@ -482,6 +497,11 @@ class MaterialAssign return this._geometry; } + setGeometry(value) + { + this._geometry = value; + } + getCollection() { return this._collection; @@ -506,19 +526,44 @@ export class Material { this._materials = []; this._defaultMaterial = null; + this._soloMaterial = ""; + } + + clearMaterials() + { + this._materials.length = 0; + this._defaultMaterial = null; + this._soloMaterial = ""; + } + + setSoloMaterial(value) + { + this._soloMaterial = value; + } + + getSoloMaterial() + { + return this._soloMaterial; } // If no material file is selected, we programmatically create a default material as a fallback static createFallbackMaterial(doc) { - const ssName = 'SR_default'; - const ssNode = doc.addChildOfCategory('standard_surface', ssName); + let ssNode = doc.getChild('Generated_Default_Shader'); + if (ssNode) + { + return ssNode; + } + const ssName = 'Generated_Default_Shader'; + ssNode = doc.addChildOfCategory('standard_surface', ssName); ssNode.setType('surfaceshader'); const smNode = doc.addChildOfCategory('surfacematerial', 'Default'); smNode.setType('material'); const shaderElement = smNode.addInput('surfaceshader'); shaderElement.setType('surfaceshader'); shaderElement.setNodeName(ssName); + + return ssNode; } async loadMaterialFile(loader, materialFilename) @@ -565,11 +610,10 @@ export class Material // Check if there are any looks defined in the document // If so then traverse the looks for all material assignments. // Generate code and compile for any associated surface shader - // and assign to the associatged geometry. If there are no looks + // and assign to the associated geometry. If there are no looks // then the first material is found and assignment to all the - // geometry. - this._materials.length = 0; - this._defaultMaterial = null; + // geometry. + this.clearMaterials(); var looks = doc.getLooks(); if (looks.length) { @@ -610,7 +654,7 @@ export class Material } else { - newAssignment = new MaterialAssign(shader, NO_GEOMETRY_SPECIFIER); + newAssignment = new MaterialAssign(shader, NO_GEOMETRY_SPECIFIER, null); } if (newAssignment) @@ -623,14 +667,109 @@ export class Material } else { - // Search for any surface shader. It + // Search for any surface shaders. The first found // is assumed to be assigned to the entire scene // The identifier used is "*" to mean the entire scene. - let elem = mx.findRenderableElement(doc); - if (elem) + const materialNodes = doc.getMaterialNodes(); + let shaderList = []; + let foundRenderable = false; + for (let i=0; i 0) + { + let shaderNodePath = shaderNodes[0].getNamePath() + if (!shaderList.includes(shaderNodePath)) + { + let assignment = NO_GEOMETRY_SPECIFIER; + if (foundRenderable == false) + { + assignment = ALL_GEOMETRY_SPECIFIER; + foundRenderable = true; + } + console.log('-- add shader: ', shaderNodePath); + shaderList.push(shaderNodePath); + this._materials.push(new MaterialAssign(shaderNodes[0], assignment)); + } + } + } } + const nodeGraphs = doc.getNodeGraphs(); + for (let i=0; i 0) + { + continue + } + let outputs = nodeGraph.getOutputs(); + for (let j=0; j" + elem.getNamePath(); + let img = matTitle.getElementsByTagName('img')[0]; + if (img) + { + // Add event listener to icon to call updateSoloMaterial function + img.addEventListener('click', function(event) + { + Material.updateSoloMaterial(viewer, elemPath, materials,event); + }); + } + if (closeUI) { matUI.close(); - } + } const uniformBlocks = Object.values(shader.getStage('pixel').getUniformBlocks()); var uniformToUpdate; const ignoreList = ['u_envRadianceMips', 'u_envRadianceSamples', 'u_alphaThreshold']; @@ -1116,9 +1350,13 @@ export class Material case 'filename': break; case 'string': - uniformToUpdate = material.uniforms[name]; - if (uniformToUpdate && value != null) { - item = currentFolder.add(material.uniforms[name], 'value'); + console.log('String: ', name); + if (value != null) { + var dummy = + { + thevalue: value + } + let item = currentFolder.add(dummy, 'thevalue'); item.name(path); item.disable(true); item.domElement.classList.add('peditoritem'); diff --git a/source/JsMaterialX/JsMaterialXCore/JsNode.cpp b/source/JsMaterialX/JsMaterialXCore/JsNode.cpp index 8878a219f5..fae1bcba5d 100644 --- a/source/JsMaterialX/JsMaterialXCore/JsNode.cpp +++ b/source/JsMaterialX/JsMaterialXCore/JsNode.cpp @@ -57,6 +57,7 @@ EMSCRIPTEN_BINDINGS(node) .function("setNodeDef", &mx::NodeGraph::setNodeDef) .function("getNodeDef", &mx::NodeGraph::getNodeDef) .function("getImplementation", &mx::NodeGraph::getImplementation) + .function("getDownstreamPorts", &mx::NodeGraph::getDownstreamPorts) .function("addInterfaceName", &mx::NodeGraph::addInterfaceName) .function("removeInterfaceName", &mx::NodeGraph::removeInterfaceName) .function("modifyInterfaceName", &mx::NodeGraph::modifyInterfaceName)