You need to enable JavaScript to run this app. three.js SpotLight 锐角光束设置无效、近距照明异常问题咨询
优惠活动
大模型
产品
解决方案
定价
更多

three.js SpotLight 锐角光束设置无效、近距照明异常问题咨询

问题描述

我是three.js开发新手,此前有Unity平台的3D对象开发与渲染经验。目前开发时遇到SpotLight使用问题:希望在导入的OBJ模型对应位置投射光线,模拟可正常点亮的台灯效果,但无法把灯光的照射角度设置为锐角——当角度低于特定阈值时,灯光就无法正常向外照明。

核心测试代码:

const objLight = new THREE.SpotLight('yellow', 5);
objLight.angle = Math.PI/2;
objLight.penumbra = 0.1;
objLight.decay = 1;
objLight.distance = 40;
objLight.position.set(-0.48, 0.7, 0);
objLight.target.position.set(-0.9, 0, 0);
objLight.castShadow = true;
scene.add(objLight);
scene.add(objLight.target);

测试不同角度值时的现象:

  • angle设为Math.PI/2、Math.PI/3、Math.PI/4时,光照范围随角度减小同步缩小
  • 相机凑近观察可发现,光线仅在靠近光源的极小范围照亮物体,无法向外扩散
  • 已排除模型影响:移除OBJ模型与测试立方体后,问题依旧存在
  • 偶然测试发现:若把灯光位置从x=-0.48移动到x=-4.48,就可以将角度调整至Math.PI/6形成窄光束;若需要更窄的光束,必须把灯光向更远的位置移动
  • 初步推测问题与灯光和目标点的距离相关,曾尝试调整目标点位置但无效果

完整项目代码:

let renderer, camera, scene;

function init() {
    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    document.body.appendChild(renderer.domElement);

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
    camera.position.z = 5;

    new THREE.OrbitControls(camera, renderer.domElement);

    const light = new THREE.AmbientLight('white', 0.2);
    scene.add(light);

    const BoxGeo = new THREE.BoxGeometry(1, 1, 1);
    const BoxMat = new THREE.MeshLambertMaterial({color: 'blue'});
    const Box = new THREE.Mesh(BoxGeo, BoxMat);
    Box.castShadow = true;
    scene.add(Box);

    const TopPlaneGeo = new THREE.PlaneGeometry(10, 10, 1);
    const TopPlaneMat = new THREE.MeshLambertMaterial({color: 'white', side: THREE.DoubleSide});
    const TopPlane = new THREE.Mesh(TopPlaneGeo, TopPlaneMat);
    TopPlane.position.y = 5;
    TopPlane.rotation.x = Math.PI/2;
    TopPlane.receiveShadow = true;
    scene.add(TopPlane);

    const BotPlaneGeo = new THREE.PlaneGeometry(10, 10, 1);
    const BotPlaneMat = new THREE.MeshLambertMaterial({color: 'white', side: THREE.DoubleSide});
    const BotPlane = new THREE.Mesh(BotPlaneGeo, BotPlaneMat);
    BotPlane.position.y = -1;
    BotPlane.rotation.x = Math.PI/2;
    BotPlane.receiveShadow = true;
    scene.add(BotPlane);

    const loader = new THREE.OBJLoader();
    loader.load(
        'objects/Lampe_FerBlanc.obj',
        function(obj) {
            obj.position.set(-0.40, 0.5, 0);
            obj.scale.set(0.01, 0.01, 0.01);
            scene.add(obj);
        }
    )
    
    const objLight = new THREE.SpotLight('yellow', 5);
    objLight.angle = Math.PI/6;
    objLight.penumbra = 0.1;
    objLight.decay = 1;
    objLight.distance = 40;
    objLight.position.set(-4.48, 0.7, 0);
    objLight.target.position.set(-0.9, 0, 0);
    objLight.castShadow = true;
    scene.add(objLight);
    scene.add(objLight.target);

    const helper = new THREE.SpotLightHelper(objLight);
    helper.name = 'helper';
    scene.add(helper);

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

function animate() {
    requestAnimationFrame(animate);
    scene.getObjectByName('helper').update();
    renderer.render(scene, camera);
}

init();
animate();
问题原因与解决方案

核心原因

  1. 对SpotLight的angle参数定义存在认知偏差:three.js中该参数是光轴中心到光束边缘的半角(弧度制),不是光束的全角。
  2. 光束投射范围与灯位、目标点位置强相关:SpotLight的光束是从光源顶点出发、沿光轴(光源指向target的方向)固定半角扩散的圆锥,任意距离处的光束横截面半径为「沿光轴的距离 × tan(angle)」。
  3. 测试中设置的target点位于空中(y=0位置),与水平放置的受光平面(BotPlane在y=-1位置)存在距离差,导致光轴倾斜角度过大。当angle设置较小时,圆锥光束根本没有延伸到受光平面上,只有离光源极近的测试立方体刚好落在光束夹角范围内,因此视觉上会误以为「光线仅在靠近光源的位置发亮,无法向外扩散」。
  4. 移动灯位到x=-4.48时,光轴与受光平面的夹角变小,同样angle值下光束在平面上的覆盖范围大幅提升,因此可以看到正常的窄光束效果。

解决方法

  • 调整target点位置:把target直接设置在受光面的中心位置(比如桌面的坐标点),不要设置在半空中,保证光轴尽可能垂直指向受光面,降低角度计算的复杂度。
  • 合理设置angle参数:根据需要照亮的受光区域尺寸计算所需半角值,通常模拟台灯效果的半角范围为Math.PI/6 ~ Math.PI/4(30°~45°),可根据实际覆盖效果微调。
  • 充分利用已添加的SpotLightHelper:该辅助线会实时渲染光束的圆锥边界,调试时直接观察圆锥覆盖范围是否匹配预期受光区域,无需靠主观判断估算角度。
  • 适配台灯模拟场景的可参考参数配置:
    // 把target直接放在桌面的目标照射位置
    objLight.target.position.set(-0.9, -1, 0); 
    // 半角30度,适配常规台灯桌面照明范围
    objLight.angle = Math.PI/6; 
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.13 18:06:03