記錄threejs實現夜景光照、投影、陰影、反射、材質顏色編碼的效果
主要是光源設置了shadow、encoding
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Three.js Scene</title>
<style>
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%
}
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "./three172/build/three.module.js",
"three/addons/": "./three172/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { GUI } from 'https://cdn.skypack.dev/dat.gui';
// 場景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// 相機
const camera = new THREE.PerspectiveCamera(
50, window.innerWidth / window.innerHeight, 0.1, 100
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
// 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild(renderer.domElement);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 點光源
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(2, 4, 2);
pointLight.castShadow = true;
pointLight.shadow.mapSize.set(1024, 1024);
pointLight.shadow.bias = -0.0005;
pointLight.shadow.camera.near = 0.1;
pointLight.shadow.camera.far = 100;
scene.add(pointLight);
// 小球可視化光源
const lightSphere = new THREE.Mesh(
new THREE.SphereGeometry(0.05, 16, 16),
new THREE.MeshBasicMaterial({ color: 0xffff00 })
);
lightSphere.position.copy(pointLight.position);
scene.add(lightSphere);
// 地板
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(500, 500),
new THREE.MeshStandardMaterial({ color: 0xffffff })
);
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add(plane);
// 正方體
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x00aaff })
);
cube.position.y = 0.5;
cube.castShadow = true;
scene.add(cube);
// 座標軸輔助線
const axesHelper = new THREE.AxesHelper(3);
scene.add(axesHelper);
// GUI 控制面板
const gui = new GUI();
const lightFolder = gui.addFolder('點光源');
const lightParams = {
x: pointLight.position.x,
y: pointLight.position.y,
z: pointLight.position.z,
intensity: pointLight.intensity,
castShadow: pointLight.castShadow
};
lightFolder.add(lightParams, 'x', -100, 100).onChange(v => {
pointLight.position.x = v;
lightSphere.position.x = v;
});
lightFolder.add(lightParams, 'y', -10, 10).onChange(v => {
pointLight.position.y = v;
lightSphere.position.y = v;
});
lightFolder.add(lightParams, 'z', -100, 100).onChange(v => {
pointLight.position.z = v;
lightSphere.position.z = v;
});
lightFolder.add(lightParams, 'intensity', 0, 100).onChange(v => {
pointLight.intensity = v;
});
lightFolder.add(lightParams, 'castShadow').onChange(v => {
pointLight.castShadow = v;
});
const sceneFolder = gui.addFolder('場景設置');
const sceneParams = {
showAxes: true
};
sceneFolder.add(sceneParams, 'showAxes').onChange(show => {
axesHelper.visible = show;
});
lightFolder.open();
sceneFolder.open();
function addModels(path) {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('./three172/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader);
loader.load(path, (gltf) => {
const model = gltf.scene;
model.position.set(0, 0, 0);
// 遍歷所有子對象,確保陰影和材質設置正確
model.traverse((child) => {
if (child.isMesh) {
// 開啓陰影
child.castShadow = true;
child.receiveShadow = true;
// 修復材質顏色編碼問題
const material = child.material;
// material.normalMap = null;
if (material.map) {
material.map.encoding = THREE.sRGBEncoding;
}
material.needsUpdate = true;
}
});
model.children[0].position.set(0, 0, 0);
scene.add(model);
console.log('模型加載成功:', model);
});
}
addModels('./assets/jifang.glb');
// 渲染循環
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// 自適應窗口
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
推薦一個開源三維可視化開發平台
github跳轉
國內git跳轉