Three.js场景3D模型environment环境贴图设置后不生效问题
问题背景
我在基于WebGL的ThreeJS场景中导入glTF模型时,发现在线预览平台上环境参数对渲染效果影响极大:
- 开启环境贴图的渲染效果:

- 未配置环境贴图的渲染效果:

最初的实现逻辑是加载JPG格式纹理作为环境贴图,再加载glTF模型,把纹理赋值给模型的scene.environment属性,核心代码如下:
import * as THREE from "three"; import { GLTFLoader } from "GLTFLoader"; // import { EXRLoader } from "EXRLoader"; export class AppController { constructor(app) { this.app = app; } animate = () => { requestAnimationFrame( this.animate ); this.renderer.render( this.scene, this.camera ); } init = () => { console.log('AppController.init()'); this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.scene.background = new THREE.Color( 0x00ffff ); const light = new THREE.AmbientLight( 0x404040 ); // soft white light this.scene.add( light ); this.renderer = new THREE.WebGLRenderer(); this.renderer.setSize( window.innerWidth, window.innerHeight ); this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.outputEncoding = THREE.sRGBEncoding; document.body.appendChild( this.renderer.domElement ); this.camera.position.z = 100; const fthis = this; new THREE.TextureLoader().load("./assets/images/env.jpg", function ( texture ) { fthis.scene.environment = texture; fthis.loadModel(texture); }, undefined, function ( err ) { console.error( err ); }); this.animate(); } loadModel = (texture) => { const loader = new GLTFLoader(); const modelPath = 'assets/models/Pebble_Logo_Extrude.gltf'; const fthis = this; loader.load( modelPath, function ( gltf ) { console.log('gltf', gltf); fthis.pebbleLogo = gltf; fthis.pebbleLogo.scene.environment = texture; fthis.pebbleLogo.scene.traverse(function (child) { if (child instanceof THREE.Mesh) { if(child.material !== null){ console.log("setting envmap"); child.material.envMap = texture; child.material.metalness = 0.0; child.material.envMapIntensity = 2.0; child.material.needsUpdate = true; } } }); fthis.scene.add( fthis.pebbleLogo.scene ); }, undefined, function ( error ) { console.error( error ); } ); } }
代码运行后实际渲染效果如下,环境贴图完全没有生效:
之前看到旧文章提到需要用EXRLoader处理环境贴图,但文章发布时间较早,当前官方文档没有相关说明,不确定该方案是否适用。
解决方案
环境贴图不生效和EXRLoader没有直接关系,EXR只是高动态范围(HDR)纹理的一种格式,用JPG格式的低动态范围(LDR)全景图也能实现环境反射,问题核心是直接把普通2D等距柱状全景纹理赋值给环境属性的操作不符合Three.js的PBR渲染逻辑——glTF模型默认使用PBR材质,这类材质无法直接采样普通2D全景纹理做环境反射,必须使用预滤波处理后的环境立方体贴图。
修复只需要做3处调整:
- 初始化渲染器之后,创建
PMREMGenerator实例,专门用来把普通全景纹理转换成PBR材质可用的预滤波环境贴图 - 纹理加载完成后,先通过PMREMGenerator处理成可用的环境贴图,再赋值给
scene.environment,不需要手动遍历每个网格材质单独设置envMap,场景全局设置后所有PBR材质会自动应用环境贴图 - 给加载的JPG环境纹理设置正确的颜色空间,避免颜色显示异常
修正后的核心代码如下:
init = () => { console.log('AppController.init()'); this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.scene.background = new THREE.Color( 0x00ffff ); const light = new THREE.AmbientLight( 0x404040 ); this.scene.add( light ); this.renderer = new THREE.WebGLRenderer(); this.renderer.setSize( window.innerWidth, window.innerHeight ); this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.outputEncoding = THREE.sRGBEncoding; // 创建PMREM生成器 this.pmremGenerator = new THREE.PMREMGenerator(this.renderer); document.body.appendChild( this.renderer.domElement ); this.camera.position.z = 100; const fthis = this; new THREE.TextureLoader().load("./assets/images/env.jpg", function ( texture ) { // 设置纹理正确的颜色空间 texture.colorSpace = THREE.SRGBColorSpace; // 将普通全景纹理转换为PBR可用的环境贴图 const envMap = fthis.pmremGenerator.fromEquirectangular(texture).texture; // 原普通纹理使用完后释放内存 texture.dispose(); fthis.scene.environment = envMap; fthis.loadModel(); }, undefined, function ( err ) { console.error( err ); }); this.animate(); } loadModel = () => { const loader = new GLTFLoader(); const modelPath = 'assets/models/Pebble_Logo_Extrude.gltf'; const fthis = this; loader.load( modelPath, function ( gltf ) { console.log('gltf', gltf); fthis.pebbleLogo = gltf; // 不需要重复给模型子场景设置environment,也不需要遍历材质手动赋值envMap // 模型加入场景后会自动继承scene的全局environment配置 // 如需调整反射强度,可遍历材质单独设置envMapIntensity fthis.pebbleLogo.scene.traverse(function (child) { if (child instanceof THREE.Mesh && child.material) { child.material.envMapIntensity = 2.0; // 不要强制修改metalness参数,保留glTF模型自带的材质属性即可 } }); fthis.scene.add( fthis.pebbleLogo.scene ); }, undefined, function ( error ) { console.error( error ); } ); }
补充:如果后续要使用HDR/EXR格式的环境贴图,只需要把
TextureLoader换成对应的EXRLoader/RGBELoader即可,后续的PMREM处理逻辑完全一致,不需要额外调整。
内容的提问来源于stack exchange,提问作者Lee Probert
相关产品推荐
相关产品推荐

