马黑黑 发表于 2025-5-28 08:12

ThreeJS入门(八)光源和阴影

本帖最后由 马黑黑 于 2025-5-28 20:58 编辑 <br /><br /><style>
        .artBox { font-size: 18px; }
        .artBox > p { margin: 10px 0; line-height: 30px; }
        .artBox mark { background: lightblue; padding: 4px 8px; }
        #prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: beige; display: none; padding: 0; overflow: hidden; z-index: 1000; margin: 0; }
        #prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
        iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>

<div id="prevBox"></div>
<div class="artBox">
        <p>阴影是现实世界的自然现象,要在 ThreeJS 中呈现这一现象,需要很大的性能开销,因此,ThreeJS 默认不开启阴影功能,需要手动设置。</p>
        <p>ThreeJS 模拟阴影,首先需要支持阴影的光源,平行光 <mark>DirectionalLight</mark>、点光源 <mark>PointLight</mark>、聚光灯<mark>SpotLight</mark> 等具有来源、方向的光源,均具备投射阴影的能力,可以借助它们来实现阴影模拟功能;其次需要材质支持,只有支持阴影投射功能的材质才可以在光的作用下投射阴影、只有具备接收阴影能力的材质才能在光的作用下显示其它物体投射的阴影,一般而言,不受光影响的材质不会产生阴影也不能显示其它物体投射的阴影。</p>
        <p>在 ThreeJS 中实现阴影的模拟是一件相对繁琐且复杂的事情。具体步骤如下:</p>
        <p>一,启用渲染器的阴影贴图功能。可以再渲染器创建之后开启阴影投射功能并设置阴影类型:</p>
        <div class="hEdiv"><pre class="hEpre">
var renderer = new THREE.WebGLRenderer({ antialias: true }); // 创建渲染器
renderer.shadowMap.enabled = true; // 启用阴影贴图
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 设置阴影类型为软阴影
        </pre></div>
        <p>二,创建光源并启用阴影投射。再次强调,只有特定类型的光源(如平行光、点光源、聚光灯等)可以投射阴影。创建光源之时做必要的配套设置:</p>
        <div class="hEdiv"><pre class="hEpre">
// 创建光源 :环境光 + 平行光
// ① 环境光(红光,强度1)
var ambientLight = new THREE.AmbientLight(0xff0000, 1);
// ② 平行光(白色+强度1)
var directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 这里使用的其实是默认参数
directionalLight.position.set(20, 20, 20); // 设置光源位置
directionalLight.castShadow = true; // 启用光源的阴影投射
scene.add(ambientLight, directionalLight); // 将两种光源都加入到场景中
        </pre></div>
        <p>三,设置物体的阴影属性。首先必须注意,物体的材质要选用支持阴影的类型;其次,但凡模拟阴影,都需要创建至少两个物体,其一是要投射阴影的物体(比如球),其二是接受并呈现阴影的物体(比如地板)。投射阴影的物体设置 castShadow 属性为 true,接收阴影的物体需要设置 receiveShadow 属性为 true,这些属性设置均在创建物体时同时完成:</p>
        <div class="hEdiv"><pre class="hEpre">
// 创建一个圆球
var ball = new THREE.Mesh(
        new THREE.SphereGeometry(),
        new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
ball.castShadow = true; //开启投射阴影功能

// 创建地板 : 标准材质、紫色
var floor = new THREE.Mesh(
        new THREE.PlaneGeometry(3, 8),
        new THREE.MeshStandardMaterial({ color: 0xff800080 })
);
floor.rotateZ(-Math.PI / 4); // 旋转-45度
floor.receiveShadow = true; // 开启接收阴影功能

scene.add(ball, floor); // 球和地板均加入到场景中
        </pre></div>
        <p>至此,一个简单的阴影效果就营造出来了,当然,代码需要整合一下:</p>
        <div class="hEdiv"><pre id="pre1" class="hEpre">
&lt;script type="module"&gt;
        import * as THREE from 'https://unpkg.ihwx.cn/three@0.176.0/build/three.module.js';

        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 0, 10);
        var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.shadowMap.enabled = true; // 渲染器启用阴影
        renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 设置阴影类型为软阴影
        document.body.appendChild(renderer.domElement);

        // 创建一个圆球(投射阴影的物体)
        var ball = new THREE.Mesh(
                new THREE.SphereGeometry(1, 64, 64), // 几何体(半径+横、纵分段数)
                new THREE.MeshStandardMaterial({ color: 0xff0000 }) // 材质(红色)
        );
        ball.castShadow = true; //开启投射阴影功能

        // 创建地板(显示阴影的物体)
        var floor = new THREE.Mesh(
                new THREE.PlaneGeometry(3, 8), // 平面几何体(宽高 3*8)
               new THREE.MeshStandardMaterial({ color: 0xff800080 }) // 标准材质(紫色)
        );
        floor.rotateZ(-Math.PI / 4); // 旋转-45度
        floor.receiveShadow = true; // 开启接收阴影功能
        scene.add(ball, floor); // 球和地板加入到场景中

        // 创建光源 :环境光 + 平行光(环境光用于改善光照环境,平行光用来令物体投射阴影)
        var ambientLight = new THREE.AmbientLight(0xff0000, 1); // ① 环境光(红光,强度1)
        // ② 平行光(白色+强度1是默认值可以缺省)
        var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(20, 20, 20); // 设置光源位置
        directionalLight.castShadow = true; // 启用光源的阴影投射
        scene.add(ambientLight, directionalLight); // 两种光源加入到场景中

        // 窗口自适应
        window.onresize = () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.render(scene, camera); // 渲染效果
        }

        window.onresize(); // 手动刷新
&lt;/script&gt;
        </pre></div>
        <blockquote><button id="btnPrev1">运行代码</button></blockquote>
        <p>本例实现了静物阴影模拟,没有加入动画,不过阴影一旦成功创建,动画中无需做其它额外工作,阴影会随着物体的运动产生相应变化。最后小结:在 ThreeJS 中模拟阴影,需要渲染器开启阴影投射,需要支持阴影投射的光源,需要物体的材质具备投射阴影属性,另外,还需要一个接收阴影的物体。</p>
        <p>ThreeJS文档关于阴影的描述章节可查看 <a href="http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/lights/shadows/LightShadow" target="_blank">灯光 / 阴影</a> 页面,其左侧栏有更多的和阴影有关的相关链接。官方文档关于阴影的描述可能不够友好,更理想的第三方探讨资料请自行网查。</p>
</div>

<script type="module">
        import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
        const pres = document.querySelectorAll('.hEpre');
        const divs = document.querySelectorAll('.hEdiv');
        divs.forEach( (div, key) => hlight.hl(div, pres));
       
        const preView = (htmlCode, targetBox) => {
                if (targetBox.innerHTML) return;
                const iframe = document.createElement('iframe');
                htmlCode = htmlCode + '<style>body {margin: 0; }</style>';
                iframe.srcdoc = htmlCode;
                targetBox.appendChild(iframe);
                targetBox.style.display = 'block';
                targetBox.onclick = () => {
                        targetBox.innerHTML = '';
                        targetBox.style.display = 'none';
                }
        };

        const value1 = pre1.textContent;
        btnPrev1.onclick = () => preView(value1, prevBox);
</script>

红影 发表于 2025-5-28 10:46

这个阴影设置还挺麻烦,一路看下来,最后的总结都归纳了:“最后小结:在 ThreeJS 中模拟阴影,需要渲染器开启阴影投射,需要支持阴影投射的光源,需要物体的材质具备投射阴影属性,另外,还需要一个接收阴影的物体。”

红影 发表于 2025-5-28 10:53

渲染器开设阴影,这个不算难,主要是需要知道支持阴影投射的光源,看上面说的,有平行光 DirectionalLight、点光源 PointLight、聚光灯SpotLight 等,嗯,先记这三个,材质也需要具备投射阴影属性,这个比较难,不知道有哪些,看例子,标准材质有这个属性。还需要一个接受阴影的物体。
四项内容缺一不可,这个太复杂了点{:4_204:}

红影 发表于 2025-5-28 10:54

不过有阴影真漂亮啊,而且创建后,在动画中无需做其它额外工作,阴影会随着物体的运动产生相应变化。这个是一大好处呢。{:4_199:}

红影 发表于 2025-5-28 10:54

这个说得特别明白,黑黑辛苦了{:4_187:}

花飞飞 发表于 2025-5-28 20:20

阴影出场还是比较隆重的,主体就不用说了。。特别是需要启用阴影贴图,显示时还需要设置一个地板来做为依托。{:4_199:}

花飞飞 发表于 2025-5-28 20:26

试着改了各种颜色,除了在平行光照射下投射到地板上的阴影之外,球体本身上的亮部和暗部也是层次分明,产生的立体感让人赞叹。。太漂亮了。{:4_199:}
阴影随物体而动,这个之前巫姐姐的幼儿园积木和魅惑两贴都有些感受。。各美其美。。

花飞飞 发表于 2025-5-28 20:29

进链接里瞅了几眼,灯光阴影的代码示例都跟瀑布一样,晕乎乎的出来了。{:4_173:}

杨帆 发表于 2025-5-28 20:46

马老师讲的真好,老师您辛苦了!{:4_191:}

马黑黑 发表于 2025-5-28 20:58

杨帆 发表于 2025-5-28 20:46
马老师讲的真好,老师您辛苦了!

{:4_190:}

马黑黑 发表于 2025-5-28 20:58

红影 发表于 2025-5-28 10:46
这个阴影设置还挺麻烦,一路看下来,最后的总结都归纳了:“最后小结:在 ThreeJS 中模拟阴影,需要渲染器 ...

一步一步来似乎也没啥吧

马黑黑 发表于 2025-5-28 20:59

红影 发表于 2025-5-28 10:53
渲染器开设阴影,这个不算难,主要是需要知道支持阴影投射的光源,看上面说的,有平行光 DirectionalLight ...

一旦了解了,也不是很难

马黑黑 发表于 2025-5-28 21:00

红影 发表于 2025-5-28 10:54
不过有阴影真漂亮啊,而且创建后,在动画中无需做其它额外工作,阴影会随着物体的运动产生相应变化。这个是 ...

嗯,就是呢,阴影对性能的开销偏大

马黑黑 发表于 2025-5-28 21:00

红影 发表于 2025-5-28 10:54
这个说得特别明白,黑黑辛苦了

阔气了

马黑黑 发表于 2025-5-28 21:00

花飞飞 发表于 2025-5-28 20:20
阴影出场还是比较隆重的,主体就不用说了。。特别是需要启用阴影贴图,显示时还需要设置一个地板来做为依托 ...

地板是必须的额,可以把地板设为整个场景

马黑黑 发表于 2025-5-28 21:01

花飞飞 发表于 2025-5-28 20:26
试着改了各种颜色,除了在平行光照射下投射到地板上的阴影之外,球体本身上的亮部和暗部也是层次分明,产生 ...

各美其美,美美大同

马黑黑 发表于 2025-5-28 21:01

花飞飞 发表于 2025-5-28 20:29
进链接里瞅了几眼,灯光阴影的代码示例都跟瀑布一样,晕乎乎的出来了。

很厉害的

花飞飞 发表于 2025-5-28 21:18

马黑黑 发表于 2025-5-28 21:01
很厉害的

{:4_173:}晕得厉害,跟晕船差不多

花飞飞 发表于 2025-5-28 21:19

马黑黑 发表于 2025-5-28 21:01
各美其美,美美大同

阴影么,自然是灵动而美丽的{:4_173:}

花飞飞 发表于 2025-5-28 21:20

马黑黑 发表于 2025-5-28 21:00
地板是必须的额,可以把地板设为整个场景

嗯哪,试了8*8,结果还是有旋转45度,看着也好看。{:4_173:}
页: [1] 2 3 4 5 6 7 8
查看完整版本: ThreeJS入门(八)光源和阴影