如何从外部文件加载Three.js着色器?排查加载未定义问题
解决Three.js中FileLoader加载着色器后未定义的问题
你遇到的问题其实是异步加载的经典坑——THREE.FileLoader.load()是异步执行的:当你调用loader.load()后,代码不会等着着色器文件加载完成,而是直接往下执行new THREE.ShaderMaterial()这一行,这时候vShader和fShader还没被回调函数赋值,自然会报未定义错误。
而你后来在控制台能拿到正确值,是因为页面刷新后过了一小会儿,着色器文件已经加载完成,回调函数把内容赋值给了这两个变量,这时候控制台访问当然是有值的。
下面给你两种可行的解决方案:
方案1:嵌套回调,确保双着色器加载完成后再执行后续逻辑
把创建材质、添加Mesh的逻辑放到第二个着色器的加载回调里,这样就能保证两个着色器都已经加载完毕:
var renderer,uniforms,vShader,fShader,camera,scene,mesh; var loader = new THREE.FileLoader(); init(); animate(); function init(){ // 注意这里你漏了括号! renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.getElementById('WebGL-output').appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000); camera.position.z = 5000; scene = new THREE.Scene(); uniforms = { "color" : { type : "c", value : new THREE.Color(0x00ff00) }, }; // 先加载顶点着色器,完成后再加载片元着色器 loader.load('plaid.vert',function ( data ) { vShader = data; // 顶点着色器加载完成后,加载片元着色器 loader.load('plaid.frag',function ( data ) { fShader = data; // 两个着色器都就绪,创建材质和Mesh createMaterialAndMesh(); }); }); } // 抽离创建逻辑,让代码更清晰 function createMaterialAndMesh(){ var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vShader, fragmentShader: fShader, }); var geometry = new THREE.CircleGeometry(100, 50); mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // 原代码用了0x000000全黑环境光,Mesh会看不到,这里改成白色 var light = new THREE.AmbientLight(0xffffff); scene.add(light); } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); }
方案2:用Promise.all处理多异步加载(更优雅)
把每个FileLoader请求包装成Promise,用Promise.all()等待所有着色器加载完成,这种方式在需要加载多个资源时结构更清晰:
var renderer,uniforms,camera,scene,mesh; var loader = new THREE.FileLoader(); init(); animate(); function init(){ renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.getElementById('WebGL-output').appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000); camera.position.z = 5000; scene = new THREE.Scene(); uniforms = { "color" : { type : "c", value : new THREE.Color(0x00ff00) }, }; // 把加载操作包装成Promise const loadShader = (url) => { return new Promise((resolve) => { loader.load(url, (data) => { resolve(data); }); }); }; // 并行加载两个着色器,全部完成后执行逻辑 Promise.all([ loadShader('plaid.vert'), loadShader('plaid.frag') ]).then(([vertexContent, fragmentContent]) => { createMaterialAndMesh(vertexContent, fragmentContent); }); } function createMaterialAndMesh(vShader, fShader){ var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vShader, fragmentShader: fShader, }); var geometry = new THREE.CircleGeometry(100, 50); mesh = new THREE.Mesh(geometry, material); scene.add(mesh); var light = new THREE.AmbientLight(0xffffff); scene.add(light); } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); }
另外提醒两个小细节:
- 原代码中
renderer = new THREE.WebGLRenderer漏了括号,必须写成new THREE.WebGLRenderer()才能正确实例化渲染器 - 原代码用了
AmbientLight(0x000000)全黑环境光,会导致Mesh完全不可见,建议改成0xffffff或其他明亮颜色
内容的提问来源于stack exchange,提问作者zlon
相关产品推荐
相关产品推荐

