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

Three.js BoxGeometry角落阴影锯齿状漏光问题求助

Three.js DirectionalLight角落锯齿状漏光问题修复

问题描述

使用BoxGeometry构建结构并添加环绕旋转的DirectionalLight后,角落出现锯齿状漏光。已尝试调整shadow.mapSize、模糊半径、renderer.shadowMap.type等参数,问题未解决;替换为导入的开放盒模型后,问题依然存在。

阴影锯齿漏光

相关代码

JavaScript

const gui = new lil.GUI();

// SCENE
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true });


const settings = {
    shadowMapType: "basic",
    physicallyCorrectLights: false,
    shadowMapSize: 2056,
    shadowRadius: 5
}

// CAMERA
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

// RENDERER
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.physicallyCorrectLights = settings.physicallyCorrectLights;
renderer.shadowMap.type = settings.shadowMapType;
renderer.shadowMap.enabled = true;

// CONTROLS
const controls = new THREE.OrbitControls(camera, renderer.domElement);

// RESIZE HAMDLER
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', onWindowResize);

// INIT CAMERA
camera.position.z = 35;
camera.position.x = 7.5;
camera.position.y = 13;
camera.lookAt(7.5, 0, -20)

// INIT HEMISPHERE LIGHT
scene.add(new THREE.AmbientLight( 0xffffff, 0.5 ));

// SCENE
scene.background = new THREE.Color(0xffffff);

// FLOOR
const plane = new THREE.Mesh(new THREE.PlaneGeometry(500, 500, 32), new THREE.MeshPhongMaterial({ color: 0xfab74b}));
plane.rotation.x = - Math.PI / 2
plane.receiveShadow = true
scene.add(plane);


const geometry = new THREE.BoxGeometry( 1, 15, 7 );
const material = new THREE.MeshPhongMaterial( {color: 0xFFFFFF} );
const cube = new THREE.Mesh( geometry, material );
cube.position.y = 7.5; 
cube.position.z = -4.5; 
cube.receiveShadow = true
cube.castShadow = true
scene.add( cube );

const geometry2 = new THREE.BoxGeometry( 1, 15, 7 );
const material2 = new THREE.MeshPhongMaterial( {color: 0xFFFFFF} );
const cube2 = new THREE.Mesh( geometry2, material2);
cube2.position.y = 7.5;
cube2.position.z = -4.5; 
cube2.position.x = 16; 
cube2.receiveShadow = true
cube2.castShadow = true
scene.add( cube2 );


//back wall
const geometry3 = new THREE.BoxGeometry( 1, 15, 15 );
const material3 = new THREE.MeshPhongMaterial( {color: 0xFFFFFF} );
const cube3 = new THREE.Mesh( geometry3, material3);
cube3.position.y = 7.5;
cube3.position.x = 8;
cube3.position.z = -7.5 
cube3.rotateY(Math.PI / 2)
cube3.receiveShadow = true
cube3.castShadow = true
scene.add( cube3 );


//top
const geometry4 = new THREE.BoxGeometry( 1, 7, 17 );
const material4 = new THREE.MeshPhongMaterial( {color: 0xFFFFFF} );
const cube4 = new THREE.Mesh( geometry4, material4);
cube4.position.y = 15.5;
cube4.position.x = 8;
cube4.position.z = -4.5 
cube4.rotateY(Math.PI / 2)
cube4.rotateZ(Math.PI / 2)
cube4.receiveShadow = true
cube4.castShadow = true
scene.add( cube4 );




// DIRECTIONAL LIGHT
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.x += 20
directionalLight.position.y += 20
directionalLight.position.z += 20
directionalLight.castShadow = true
directionalLight.shadow.mapSize.width = settings.shadowMapSize;
directionalLight.shadow.mapSize.height = settings.shadowMapSize;
directionalLight.shadow.radius = settings.shadowRadius
// directionalLight.shadow.blurSamples = 500
const d = 25;
directionalLight.shadow.camera.near = 0.5
directionalLight.shadow.camera.far = 60
directionalLight.shadow.camera.left = - d;
directionalLight.shadow.camera.right = d;
directionalLight.shadow.camera.top = d;
directionalLight.shadow.camera.bottom = - d;
scene.add(directionalLight);

scene.add( new THREE.CameraHelper( directionalLight.shadow.camera ) );



// ANIMATE
function animate() {
    // TARGET
    const time = Date.now() * 0.0005;
    directionalLight.position.x = Math.sin(time * 0.7) * 20;
    directionalLight.position.z = Math.cos(time * 0.7) * 20;

    renderer.render(scene, camera);
    requestAnimationFrame(animate);
}
document.body.appendChild(renderer.domElement);
animate();

gui.add( settings, 'shadowMapType', ["basic", "pcf","pcfsoft", "vsm"])
    .onChange( value => {
        switch (value) {
          case 'basic':
            renderer.shadowMap.type = THREE.BasicShadowMap;
            break;
          case 'pcf':
            renderer.shadowMap.type = THREE.PCFShadowMap;
            break;
          case 'pcfsoft':
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            break;
          case 'vsm':
            renderer.shadowMap.type = THREE.VSMShadowMap;
            break;
          default:
            console.log('Invalid shadow map type');
        }
        console.log(renderer.shadowMap.type)
    } );
gui.add(settings, "physicallyCorrectLights")
    .onChange( value => {
        renderer.physicallyCorrectLights = settings.physicallyCorrectLights
    })
gui.add( settings, 'shadowMapSize', 0, 12000, 200 )
    .onChange( value => {
        directionalLight.shadow.map.dispose(); // important
        directionalLight.shadow.mapSize = new THREE.Vector2(value,value)
    })

gui.add( settings, 'shadowRadius', 0, 100, 1 )
.onChange( value => {
    directionalLight.shadow.radius = value;
    directionalLight.shadow.updateMatrices(directionalLight);
})

CSS

*
{
    margin: 0;
    padding: 0;
}

html,
body
{
    overflow: hidden;
}

.webgl
{
    position: fixed;
    top: 0;
    left: 0;
    outline: none;
}

HTML

<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/examples/js/controls/OrbitControls.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three-buffer-geometry-utils@1.0.0/index.min.js"></script>
<script src="https://unpkg.com/lil-gui@0.16.1/dist/lil-gui.umd.js"></script>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Open Box</title>
</head>
<body>
    <canvas class="webgl"></canvas>
</body>
</html>

解决方案

1. 缩小阴影相机视锥体范围

当前阴影相机范围过大,导致目标区域的阴影纹理精度不足。修改阴影相机参数,仅包裹需要计算阴影的区域:

// 替换原阴影相机配置
directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 40;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 26;
directionalLight.shadow.camera.top = 20;
directionalLight.shadow.camera.bottom = -5;
// 更新相机投影矩阵
directionalLight.shadow.camera.updateProjectionMatrix();

2. 添加阴影偏移参数

通过阴影偏移解决表面精度误差导致的漏光:

directionalLight.shadow.bias = -0.0001;
directionalLight.shadow.normalBias = 0.01;
  • bias:调整深度偏移,负值可减少角落漏光
  • normalBias:基于表面法线的偏移,针对性修复边缘/角落的精度问题

3. 优化阴影采样类型

使用PCFSoftShadowMap提升阴影边缘精度与柔和度:

renderer.shadowMap.type = THREE.PCFSoftShadowMap;

4. 合并相邻几何体(可选)

多个BoxGeometry拼接处易出现精度误差,合并为单个几何体可消除接缝处的阴影问题:

const mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries([
  geometry, geometry2, geometry3, geometry4
]);
const mergedMesh = new THREE.Mesh(mergedGeometry, material);
mergedMesh.position.y = 7.5;
mergedMesh.receiveShadow = true;
mergedMesh.castShadow = true;
// 移除原多个cube,添加合并后的网格
scene.remove(cube, cube2, cube3, cube4);
scene.add(mergedMesh);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 20:57:29