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

THREE.js编辑器导出场景加载后显示异常求助

THREE.js加载GLTF导出场景材质异常问题排查

我正在开发一个THREE.js项目,将打包后的GLTF文件导入在线编辑器时显示完全正常,但在自身项目中加载导出的场景时出现两处材质问题:一种材质完全异常(丢失颜色等),另一种材质出现奇怪的黄色色调。

编辑器显示效果:
编辑器正常显示效果

项目中显示效果:
项目中材质异常效果

我已尝试设置sRGB为true,并关闭ToneMapping,但显示效果仍不正常。

以下是应用代码:

import * as THREE from './build/three.module.js';

import Stats from './node_modules/three/examples/jsm/libs/stats.module.js';

import { GLTFLoader } from './node_modules/three/examples/jsm/loaders/GLTFLoader.js';

import { Octree } from './node_modules/three/examples/jsm/math/Octree.js';
import { OctreeHelper } from './node_modules/three/examples/jsm/helpers/OctreeHelper.js';

import { Capsule } from './node_modules/three/examples/jsm/math/Capsule.js';

import { GUI } from './node_modules/three/examples/jsm/libs/lil-gui.module.min.js';

const clock = new THREE.Clock();

const scene = new THREE.Scene();
scene.background = new THREE.Color( 0x88ccee );

const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.rotation.order = 'YXZ';

const container = document.getElementById( 'container' );

const renderer = new THREE.WebGLRenderer( { antialias: true, } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.textureEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.NoToneMapping;
container.appendChild( renderer.domElement );



const stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );


const GRAVITY = 30;


const STEPS_PER_FRAME = 5;

const ray = new THREE.Raycaster();

const worldOctree = new Octree();

const playerCollider = new Capsule( new THREE.Vector3( 0, 0.15, 0 ), new THREE.Vector3( 0, 1.5, 0 ), 0.35 );

const playerVelocity = new THREE.Vector3();
const playerDirection = new THREE.Vector3();

let playerOnFloor = false;
let mouseTime = 0;

const keyStates = {};

const vector1 = new THREE.Vector3();
const vector2 = new THREE.Vector3();
const vector3 = new THREE.Vector3();

document.addEventListener( 'keydown', ( event ) => {

    keyStates[ event.code ] = true;

} );

document.addEventListener( 'keyup', ( event ) => {

    keyStates[ event.code ] = false;

} );

container.addEventListener( 'mousedown', () => {

    document.body.requestPointerLock();

    mouseTime = performance.now();

} );

document.addEventListener( 'mouseup', () => {

//  if ( document.pointerLockElement !== null ) throwBall();

} );

document.body.addEventListener( 'mousemove', ( event ) => {

    if ( document.pointerLockElement === document.body ) {

        camera.rotation.y -= event.movementX / 500;
        camera.rotation.x -= event.movementY / 500;

    }

} );

window.addEventListener( 'resize', onWindowResize );

function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

}


function playerCollisions() {

    const result = worldOctree.capsuleIntersect( playerCollider );

    playerOnFloor = false;

    if ( result ) {

        playerOnFloor = result.normal.y > 0;

        if ( ! playerOnFloor ) {

            playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) );

        }

        playerCollider.translate( result.normal.multiplyScalar( result.depth ) );

    }

}

function updatePlayer( deltaTime ) {

    let damping = Math.exp( - 4 * deltaTime ) - 1;

    if ( ! playerOnFloor ) {

        playerVelocity.y -= GRAVITY * deltaTime;

        // small air resistance
        damping *= 0.1;

    }

    playerVelocity.addScaledVector( playerVelocity, damping );

    const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime );
    playerCollider.translate( deltaPosition );

    playerCollisions();

    camera.position.copy( playerCollider.end );

}


function getForwardVector() {

    camera.getWorldDirection( playerDirection );
    playerDirection.y = 0;
    playerDirection.normalize();

    return playerDirection;

}

function getSideVector() {

    camera.getWorldDirection( playerDirection );
    playerDirection.y = 0;
    playerDirection.normalize();
    playerDirection.cross( camera.up );

    return playerDirection;

}

function controls( deltaTime ) {

    // gives a bit of air control
    const speedDelta = deltaTime * ( playerOnFloor ? 15 : 8 );

    if ( keyStates[ 'KeyW' ] ) {

        playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) );

    }

    if ( keyStates[ 'KeyS' ] ) {

        playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) );

    }

    if ( keyStates[ 'KeyA' ] ) {

        playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) );

    }

    if ( keyStates[ 'KeyD' ] ) {

        playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) );

    }



}

const loader = new THREE.ObjectLoader();

loader.load( './scene-4.json', ( gallery ) => {

    scene.add( gallery );
    gallery.position.set(0,0,3);

    worldOctree.fromGraphNode( gallery );

    gallery.traverse( child => {

        if ( child.isMesh ) {

            child.castShadow = true;
            child.receiveShadow = true;

            if ( child.material.map ) {

                child.material.map.anisotropy = 4;
            }

        }

    } );


    const helper = new OctreeHelper( worldOctree );
    helper.visible = false;
    scene.add( helper );

    const gui = new GUI( { width: 200 } );
    gui.add( { debug: false }, 'debug' )
        .onChange( function ( value ) {

            helper.visible = value;

        } );

    animate();

} );

function teleportPlayerIfOob() {

    if ( camera.position.y <= - 25 ) {

        playerCollider.start.set( 0, 0.35, 0 );
        playerCollider.end.set( 0, 1, 0 );
        playerCollider.radius = 0.35;
        camera.position.copy( playerCollider.end );
        camera.rotation.set( 0, 0, 0 );

    }

}


function animate() {

    const deltaTime = Math.min( 0.05, clock.getDelta() ) / STEPS_PER_FRAME;

    // we look for collisions in substeps to mitigate the risk of
    // an object traversing another too quickly for detection.

    for ( let i = 0; i < STEPS_PER_FRAME; i ++ ) {

        controls( deltaTime );

        updatePlayer( deltaTime );


        teleportPlayerIfOob();

    }

    renderer.render( scene, camera );

    stats.update();

    requestAnimationFrame( animate );

}

可能的原因及解决方案

  • 加载器类型不匹配:你导入了GLTFLoader但实际用ObjectLoader加载.json文件。ObjectLoader仅支持THREE.js原生JSON场景格式,GLTF模型必须用GLTFLoader加载,这是材质解析异常的核心原因。
    修复代码:

    // 替换ObjectLoader为GLTFLoader
    const loader = new GLTFLoader();
    loader.load( './你的模型文件.glb', (gltf) => {
        const gallery = gltf.scene;
        scene.add(gallery);
        // 后续场景处理逻辑不变
    });
    
  • 材质编码未同步:即使设置了全局渲染器编码,模型材质的纹理编码可能未正确继承。遍历模型时强制设置纹理编码:

    gallery.traverse( child => {
        if ( child.isMesh ) {
            // ...原有代码
            if (child.material.map) {
                child.material.map.encoding = THREE.sRGBEncoding;
                child.material.needsUpdate = true;
            }
        }
    });
    
  • 缺少匹配的光照:在线编辑器通常默认配置了环境光和方向光,而你的项目中仅设置了阴影但无有效光源,导致材质颜色显示偏差。添加基础光照:

    // 添加环境光
    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
    // 添加方向光
    const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
    dirLight.position.set(5, 10, 7.5);
    dirLight.castShadow = true;
    scene.add(dirLight);
    
  • THREE.js版本差异:如果在线编辑器使用的THREE.js版本与你的项目不一致,可能存在材质解析的兼容性问题。尝试升级或降级项目的THREE.js版本,与编辑器版本对齐。

内容的提问来源于stack exchange,提问作者KhaosEmerald

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:00:46