You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从外部文件加载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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:52:12