Threejs 陰影秘籍,打造震撼 3D 效果

定義

陰影是光線照射到物體上,被物體遮擋后形成的效果。在 Three.js 中,可以通過設(shè)置光源的 castShadow 屬性來開啟陰影效果。

步驟

想要實(shí)現(xiàn)陰影效果涉及到以下幾個(gè)步驟:

  • 光源: 開啟光源的 castShadow 屬性。
  • 渲染器: 開啟渲染器的允許在場景中使用陰影貼圖(shadowMapEnabled)屬性。
  • 物體: 設(shè)置物體的材質(zhì)是否接收陰影(receiveShadow)屬性為 true,表示該物體可以接收陰影。
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 允許渲染器生成陰影
document.body.appendChild(renderer.domElement);

//創(chuàng)建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(-5, 5, 0);
directionalLight.castShadow = true; // 開啟投影
scene.add(directionalLight);

//創(chuàng)建平面
const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial());
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true; // 接收陰影
scene.add(plane);

//創(chuàng)建球體
const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 32),
  new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
sphere.position.set(0, 1, 0);
sphere.castShadow = true; // 開啟投影
scene.add(sphere);
shadow.png
  • mapSize: 陰影貼圖的大小,默認(rèn)值為 512。值越大,陰影越清晰,但計(jì)算量也越大。較高的值會(huì)以計(jì)算時(shí)間為代價(jià)提供更好的陰影質(zhì)量。
  • radius: 將此值設(shè)置為大于1的值將模糊陰影的邊緣。較高的值會(huì)在陰影中產(chǎn)生不必要的條帶效果 - 更大的mapSize將允許在這些效果變得可見之前使用更高的值。

透視相機(jī)

陰影相機(jī)(ShadowCamera)是用于生成陰影貼圖的相機(jī)。在 Three.js 中,光源的 shadowCamera 屬性可以用來設(shè)置陰影相機(jī)的參數(shù)。

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7.5);
light.castShadow = true;

light.shadow.mapSize.width = 1024; // 陰影貼圖的寬度
light.shadow.mapSize.height = 1024; // 陰影貼圖的高度
light.shadow.camera.near = 0.5; // 陰影相機(jī)最近距離
light.shadow.camera.far = 500; // 陰影相機(jī)最遠(yuǎn)距離
light.shadow.camera.left = -50; // 陰影相機(jī)左邊界
light.shadow.camera.right = 50; // 陰影相機(jī)右邊界
light.shadow.camera.top = 50; // 陰影相機(jī)上邊界
light.shadow.camera.bottom = -50; // 陰影相機(jī)下邊界

scene.add(light);
shadowCamera.png

示例

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 創(chuàng)建場景
const scene = new THREE.Scene();

// 創(chuàng)建相機(jī)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(3, 3, -20);
camera.lookAt(0, 0, 0);

// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 允許渲染器生成陰影
document.body.appendChild(renderer.domElement);

//創(chuàng)建環(huán)境光
const ambientLight = new THREE.AmbientLight(0x101010);
scene.add(ambientLight);

//創(chuàng)建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(-5, 5, 0);
directionalLight.castShadow = true; // 開啟投影
scene.add(directionalLight);
//創(chuàng)建平行光輔助
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 1);
scene.add(directionalLightHelper);

//創(chuàng)建點(diǎn)光源
const pointLight = new THREE.PointLight(0xffffff, 10, 1000);
pointLight.position.set(-5, 3, 0);
pointLight.castShadow = true; // 開啟投影
scene.add(pointLight);
//創(chuàng)建點(diǎn)光源輔助
const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
scene.add(pointLightHelper);

//創(chuàng)建平面
const plane = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial());
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true; // 接收陰影
scene.add(plane);

//創(chuàng)建球體
const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 32),
  new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
sphere.position.set(0, 1, 0);
sphere.castShadow = true; // 開啟投影
scene.add(sphere);

//創(chuàng)建控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 啟用阻尼效果
controls.enableDamping = true;
let angle = 0
function animate() {
  angle += 0.01
  pointLight.position.x = Math.sin(angle) * 3
  pointLight.position.z = Math.cos(angle) * 3
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

//監(jiān)聽窗口大小變化
window.addEventListener("resize", () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
shadow1.gif

射線

射線(Raycaster)是三維空間中的一條線,它從一點(diǎn)出發(fā),沿著一個(gè)方向無限延伸。射線通常用于檢測物體之間的碰撞、光線投射等。

構(gòu)造器

const raycaster = new THREE.Raycaster(origin, direction, near, far);
  • origin:射線的起點(diǎn),通常是一個(gè)三維向量。
  • direction:射線的方向,通常是一個(gè)歸一化的三維向量。
  • near:可選參數(shù),表示射線開始的位置,near不能為負(fù)值,默認(rèn)為0。
  • far:可選參數(shù),表示射線結(jié)束的位置,far不能小于near,默認(rèn)為無窮大。

方法

  • set(origin, direction):設(shè)置射線的起點(diǎn)和方向。
  • setFromCamera(coords, camera):從屏幕坐標(biāo)設(shè)置射線的起點(diǎn)和方向。
  • intersectObject(object, recursive):檢測射線與物體之間的碰撞,返回一個(gè)包含碰撞結(jié)果的數(shù)組。
  • intersectObjects(objects, recursive):檢測射線與多個(gè)物體之間的碰撞,返回一個(gè)包含碰撞結(jié)果的數(shù)組。
//創(chuàng)建射線
const raycaster = new THREE.Raycaster();
// 設(shè)置射線的起點(diǎn)和方向
const origin = new THREE.Vector3(-6, 0, 0);
const direction = new THREE.Vector3(1, 0, 0).normalize(); // 將方向向量標(biāo)準(zhǔn)化
raycaster.set(origin, direction);
const meshs = [sphere1, sphere2, sphere3];

function animate() {
  // 使用raycaster對象與meshs數(shù)組中的對象進(jìn)行相交檢測
  const intersects = raycaster.intersectObjects(meshs);
  sphere1.position.y = 4 * Math.sin(Date.now() * 0.001);
  sphere2.position.y = 4 * Math.sin(Date.now() * 0.001 + (2 * Math.PI) / 3);
  sphere3.position.y = 4 * Math.sin(Date.now() * 0.001 + (4 * Math.PI) / 3);
  // 遍歷meshs數(shù)組中的每個(gè)對象
  //由于射線是一瞬間完成的,所以需要將每個(gè)對象的材質(zhì)顏色重置
  for (const mesh of meshs) {
    // 將每個(gè)對象的材質(zhì)顏色設(shè)置為白色
    mesh.material.color.set(0xffffff);
  }
  // 遍歷相交檢測結(jié)果數(shù)組
  for (const intersectObject of intersects) {
    // 將相交的對象的材質(zhì)顏色設(shè)置為紅色
    intersectObject.object.material.color.set(0xff0000);
  }
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
Raycaster.gif

書洞筆記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容