GL_INVALID_OPERATION错误:片段着色器输出缺失致模型变色异常
问题描述
我正在开发Autodesk Viewer自定义扩展,尝试用THREE.MeshPhongMaterial修改模型特定部件颜色,对象识别正常,但材质显示出错,模型变透明,浏览器控制台报错:GL_INVALID_OPERATION: Active draw buffers with missing fragment shader outputs。相关JavaScript代码如下:
class ChangeColorCustom extends Autodesk.Viewing.Extension { constructor(viewer, options) { super(viewer, options); this._colorPickerId = this.guid(); this.addCSS(); } addCSS() { const link = $('<link/>', { rel: 'stylesheet', type: 'text/css', href: '/extensions/ChangeColorCustom/contents/main.css' }); link.appendTo('head'); } load() { this.viewer.addEventListener( Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onItemSelected.bind(this) ); this.createUI(); return true; } unload() { console.log("ChangeColorCustom unloaded"); this.viewer.removeEventListener( Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onItemSelected ); $(`#${this._colorPickerId}-container`).remove(); return true; } createUI() { const container = this.viewer.container; if (container) { const htmlString = ` <div id="${this._colorPickerId}-container" style="position: absolute; right: 25%; top: 5%; z-index: 100;"> <input type="text" id="${this._colorPickerId}-DBIdInput" placeholder="Enter DBID"> <input type="range" id="${this._colorPickerId}-opacitySlider" min="0" max="1" step="0.01" value="1"> <button id="${this._colorPickerId}-applyColorButton">Apply Color</button> <button id="${this._colorPickerId}-resetButton">Reset Color</button> </div> `; container.insertAdjacentHTML('beforeend', htmlString); // Apply color $(`#${this._colorPickerId}-applyColorButton`).click(() => { const dbId = parseInt($(`#${this._colorPickerId}-DBIdInput`).val(), 10); const opacity = parseFloat($(`#${this._colorPickerId}-opacitySlider`).val()); if (!isNaN(dbId)) { // set the color to `#ffffff` const fixedColor = new THREE.Color("#ffffff"); this.changeColor(dbId, fixedColor, opacity); } }); // reset button $(`#${this._colorPickerId}-resetButton`).click(() => { const dbId = parseInt($(`#${this._colorPickerId}-DBIdInput`).val(), 10); if (!isNaN(dbId)) { this.resetColor(dbId); } }); } } changeColor(dbId, color, opacity) { const material = new THREE.MeshPhongMaterial({ color: color, transparent: true, opacity: opacity }); material.colorWrite = true; const instanceTree = this.viewer.model.getData().instanceTree; instanceTree.enumNodeFragments(dbId, (fragId) => { const proxy = this.viewer.impl.getFragmentProxy(this.viewer.model, fragId); proxy.setMaterial(material); proxy.updateAnimTransform(); }, true); this.viewer.impl.invalidate(true, true, true); } resetColor(dbId) { // Reset to the default material or reset logic here. } guid() { const d = new Date().getTime(); return 'xxxx-xxxx-xxxx-xxxx'.replace(/[xy]/g, (c) => { const r = (d + Math.random() * 16) % 16 | 0; return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16); }); } onItemSelected(event) { if (event.dbIdArray.length > 0) { const dbId = event.dbIdArray[0]; this.viewer.getProperties(dbId, (props) => { $(`#${this._colorPickerId}-DBIdInput`).val(props.externalId || props.dbId); }, (error) => { console.error('Error getting properties:', error); }); } } } Autodesk.Viewing.theExtensionManager.registerExtension( 'ChangeColorCustom', ChangeColorCustom );
错误含义
这个WebGL错误的核心原因是:Autodesk Viewer的渲染管线采用**多渲染目标(Multiple Render Targets, MRT)**机制,要求片段着色器输出多个缓冲区数据(如基础颜色、深度信息、自定义渲染通道等)。但直接使用原生THREE.MeshPhongMaterial替换时,其默认着色器仅输出基础颜色,缺少Viewer渲染管线依赖的其他输出通道,导致WebGL上下文抛出操作无效错误,最终出现模型透明的渲染异常。
解决步骤
- 禁止直接使用原生THREE.js材质替换Viewer的片段代理材质,必须基于Viewer内置的材质模板创建兼容材质。
- 使用
this.viewer.impl.phongMaterial()等Viewer提供的材质构造方法,这类材质已预先适配多渲染目标要求。 - 替换材质前,存储原片段的默认材质,用于后续重置操作。
- 根据透明度需求动态设置
transparent属性,避免不必要的透明渲染开销。
修改后的完整代码
class ChangeColorCustom extends Autodesk.Viewing.Extension { constructor(viewer, options) { super(viewer, options); this._colorPickerId = this.guid(); this._originalMaterials = new Map(); // 存储原材质用于重置 this.addCSS(); } addCSS() { const link = $('<link/>', { rel: 'stylesheet', type: 'text/css', href: '/extensions/ChangeColorCustom/contents/main.css' }); link.appendTo('head'); } load() { this.viewer.addEventListener( Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onItemSelected.bind(this) ); this.createUI(); return true; } unload() { console.log("ChangeColorCustom unloaded"); this.viewer.removeEventListener( Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onItemSelected ); $(`#${this._colorPickerId}-container`).remove(); this._originalMaterials.clear(); // 清理存储的原材质 return true; } createUI() { const container = this.viewer.container; if (container) { const htmlString = ` <div id="${this._colorPickerId}-container" style="position: absolute; right: 25%; top: 5%; z-index: 100;"> <input type="text" id="${this._colorPickerId}-DBIdInput" placeholder="Enter DBID"> <input type="range" id="${this._colorPickerId}-opacitySlider" min="0" max="1" step="0.01" value="1"> <button id="${this._colorPickerId}-applyColorButton">Apply Color</button> <button id="${this._colorPickerId}-resetButton">Reset Color</button> </div> `; container.insertAdjacentHTML('beforeend', htmlString); // Apply color $(`#${this._colorPickerId}-applyColorButton`).click(() => { const dbId = parseInt($(`#${this._colorPickerId}-DBIdInput`).val(), 10); const opacity = parseFloat($(`#${this._colorPickerId}-opacitySlider`).val()); if (!isNaN(dbId)) { const fixedColor = new THREE.Color("#ffffff"); this.changeColor(dbId, fixedColor, opacity); } }); // reset button $(`#${this._colorPickerId}-resetButton`).click(() => { const dbId = parseInt($(`#${this._colorPickerId}-DBIdInput`).val(), 10); if (!isNaN(dbId)) { this.resetColor(dbId); } }); } } changeColor(dbId, color, opacity) { const instanceTree = this.viewer.model.getData().instanceTree; instanceTree.enumNodeFragments(dbId, (fragId) => { const proxy = this.viewer.impl.getFragmentProxy(this.viewer.model, fragId); // 保存原材质 if (!this._originalMaterials.has(fragId)) { this._originalMaterials.set(fragId, proxy.getMaterial()); } // 创建Viewer兼容的Phong材质 const material = new this.viewer.impl.phongMaterial(); material.color.set(color); material.opacity = opacity; material.transparent = opacity < 1; material.colorWrite = true; material.depthWrite = true; // 应用材质 proxy.setMaterial(material); proxy.updateAnimTransform(); }, true); this.viewer.impl.invalidate(true, true, true); } resetColor(dbId) { const instanceTree = this.viewer.model.getData().instanceTree; instanceTree.enumNodeFragments(dbId, (fragId) => { const proxy = this.viewer.impl.getFragmentProxy(this.viewer.model, fragId); // 恢复原材质 const originalMat = this._originalMaterials.get(fragId); if (originalMat) { proxy.setMaterial(originalMat); proxy.updateAnimTransform(); this._originalMaterials.delete(fragId); } }, true); this.viewer.impl.invalidate(true, true, true); } guid() { const d = new Date().getTime(); return 'xxxx-xxxx-xxxx-xxxx'.replace(/[xy]/g, (c) => { const r = (d + Math.random() * 16) % 16 | 0; return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16); }); } onItemSelected(event) { if (event.dbIdArray.length > 0) { const dbId = event.dbIdArray[0]; this.viewer.getProperties(dbId, (props) => { $(`#${this._colorPickerId}-DBIdInput`).val(props.externalId || props.dbId); }, (error) => { console.error('Error getting properties:', error); }); } } } Autodesk.Viewing.theExtensionManager.registerExtension( 'ChangeColorCustom', ChangeColorCustom );
关键改动说明
- 新增
_originalMaterialsMap,存储每个片段的原材质,确保重置功能正常。 - 使用
this.viewer.impl.phongMaterial()创建兼容Viewer渲染管线的Phong材质,避免缺失片段着色器输出。 - 动态设置
transparent属性:仅当opacity小于1时启用透明渲染,优化性能。 - 完善重置逻辑,准确恢复原材质。
内容的提问来源于stack exchange,提问作者ryoyu tanaka
相关产品推荐
相关产品推荐

