現在會遇到一個很明顯的問題,
背對太陽的一面可能太黑。
所以今天我們要再加入另一種光源 THREE.AmbientLight (環境光)
讓整個場景保留一點基本亮度,同時繼續保留太陽光造成的明暗差異。
今天會完成:
index.html
與 Day 18 相同,不需要修改。(完整在 Day 16)
css/style.css
與 Day 18 相同,不需要修改。(完整在 Day 9)
js/main.js
import * as THREE from
"three";
import { OrbitControls } from
"three/addons/controls/OrbitControls.js";
// ========================
// Scene
// ========================
const scene =
new THREE.Scene();
scene.background =
new THREE.Color(
0x000000
);
// ========================
// Camera
// ========================
const camera =
new THREE.PerspectiveCamera(
60,
window.innerWidth /
window.innerHeight,
0.1,
1000
);
camera.position.set(
30,
25,
30
);
camera.lookAt(
0,
0,
0
);
// ========================
// Renderer
// ========================
const renderer =
new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(
window.innerWidth,
window.innerHeight
);
renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);
document
.getElementById("scene")
.appendChild(
renderer.domElement
);
// ========================
// OrbitControls
// ========================
const controls =
new OrbitControls(
camera,
renderer.domElement
);
controls.enableDamping =
true;
controls.dampingFactor =
0.05;
controls.minDistance =
10;
controls.maxDistance =
80;
controls.target.set(
0,
0,
0
);
controls.update();
// ========================
// Clock
// ========================
const clock =
new THREE.Clock();
// ========================
// Texture Loader
// ========================
const textureLoader =
new THREE.TextureLoader();
// ========================
// Sun
// ========================
const sunTexture =
textureLoader.load(
"textures/sun.jpg"
);
sunTexture.colorSpace =
THREE.SRGBColorSpace;
const sunGeometry =
new THREE.SphereGeometry(
2,
64,
64
);
const sunMaterial =
new THREE.MeshBasicMaterial({
map: sunTexture
});
const sun =
new THREE.Mesh(
sunGeometry,
sunMaterial
);
scene.add(
sun
);
// ========================
// Point Light
// ========================
const sunLight =
new THREE.PointLight(
0xffffff,
500,
100
);
sunLight.position.set(
0,
0,
0
);
scene.add(
sunLight
);
// ========================
// Ambient Light
// ========================
const ambientLight =
new THREE.AmbientLight(
0xffffff,
0.08
);
scene.add(
ambientLight
);
// ========================
// Planet Data
// ========================
const planetData = [
{
name: "Mercury",
size: 0.45,
distance: 4,
rotationSpeed: 0.8,
orbitSpeed: 1.2,
texture:
"textures/mercury.jpg"
},
{
name: "Venus",
size: 0.65,
distance: 6,
rotationSpeed: -0.3,
orbitSpeed: 0.95,
texture:
"textures/venus.jpg"
},
{
name: "Earth",
size: 0.7,
distance: 8,
rotationSpeed: 1.0,
orbitSpeed: 0.8,
texture:
"textures/earth.jpg"
},
{
name: "Mars",
size: 0.55,
distance: 10,
rotationSpeed: 0.9,
orbitSpeed: 0.65,
texture:
"textures/mars.jpg"
},
{
name: "Jupiter",
size: 1.3,
distance: 13,
rotationSpeed: 2.0,
orbitSpeed: 0.4,
texture:
"textures/jupiter.jpg"
},
{
name: "Saturn",
size: 1.1,
distance: 16,
rotationSpeed: 1.8,
orbitSpeed: 0.3,
texture:
"textures/saturn.jpg"
},
{
name: "Uranus",
size: 0.9,
distance: 19,
rotationSpeed: 1.2,
orbitSpeed: 0.22,
texture:
"textures/uranus.jpg"
},
{
name: "Neptune",
size: 0.85,
distance: 22,
rotationSpeed: 1.1,
orbitSpeed: 0.16,
texture:
"textures/neptune.jpg"
}
];
// ========================
// Create Orbits
// ========================
planetData.forEach(
(data) => {
const points = [];
const segments = 128;
for (
let i = 0;
i < segments;
i++
) {
const angle =
(i / segments) *
Math.PI * 2;
const x =
Math.cos(angle) *
data.distance;
const z =
Math.sin(angle) *
data.distance;
points.push(
new THREE.Vector3(
x,
0,
z
)
);
}
const orbitGeometry =
new THREE.BufferGeometry()
.setFromPoints(
points
);
const orbitMaterial =
new THREE.LineBasicMaterial({
color: 0x444444
});
const orbit =
new THREE.LineLoop(
orbitGeometry,
orbitMaterial
);
scene.add(
orbit
);
}
);
// ========================
// Create Planets
// ========================
const planets = [];
planetData.forEach(
(data, index) => {
const planetTexture =
textureLoader.load(
data.texture
);
planetTexture.colorSpace =
THREE.SRGBColorSpace;
const geometry =
new THREE.SphereGeometry(
data.size,
64,
64
);
const material =
new THREE.MeshStandardMaterial({
map: planetTexture,
roughness: 1,
metalness: 0
});
const planet =
new THREE.Mesh(
geometry,
material
);
scene.add(
planet
);
const startAngle =
index * 0.8;
planets.push({
mesh: planet,
distance:
data.distance,
rotationSpeed:
data.rotationSpeed,
orbitSpeed:
data.orbitSpeed,
angle:
startAngle
});
}
);
// ========================
// Camera Controls
// ========================
window.addEventListener(
"keydown",
(event) => {
if (event.key === "1") {
camera.position.set(
0,
0,
40
);
}
if (event.key === "2") {
camera.position.set(
30,
25,
30
);
}
if (event.key === "3") {
camera.position.set(
0,
45,
0.1
);
}
if (event.key === "4") {
camera.position.set(
0,
12,
20
);
}
controls.target.set(
0,
0,
0
);
controls.update();
}
);
// ========================
// Resize
// ========================
window.addEventListener(
"resize",
() => {
camera.aspect =
window.innerWidth /
window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
window.innerWidth,
window.innerHeight
);
renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);
}
);
// ========================
// Animation
// ========================
function animate() {
requestAnimationFrame(
animate
);
const delta =
clock.getDelta();
sun.rotation.y +=
0.5 * delta;
planets.forEach(
(planet) => {
// 自轉
planet.mesh.rotation.y +=
planet.rotationSpeed *
delta;
// 公轉
planet.angle +=
planet.orbitSpeed *
delta;
planet.mesh.position.x =
Math.cos(
planet.angle
) *
planet.distance;
planet.mesh.position.z =
Math.sin(
planet.angle
) *
planet.distance;
}
);
controls.update();
renderer.render(
scene,
camera
);
}
animate();
const ambientLight =
new THREE.AmbientLight(
0xffffff,
0.08
);
scene.add(
ambientLight
);
也就是 THREE.AmbientLight 它和昨天的 THREE.PointLight 有很大的不同。
今天加入:
new THREE.AmbientLight(
0xffffff,
0.08
);
AmbientLight 可以理解成整個場景都有一點點基礎亮度。
它沒有特定位置,所以整顆行星都會稍微受到影響。
如果只有太陽的 PointLight 亮面很好看,
但是暗面可能會變成 完全黑掉。
例如地球背光的一側,有可能幾乎看不到 Texture,
所以我們加入非常弱的 AmbientLight,
讓暗面仍然保留一些資訊。
效果會比較接近暗面還是暗,只是沒有完全消失。
今天也順便整理一下前幾天學過的 Material。
太陽現在使用 THREE.MeshBasicMaterial
行星使用 THREE.MeshStandardMaterial
兩者最大的差異就是
MeshBasicMaterial 不受 Light 影響
MeshStandardMaterial 會受到 Light 影響
今天最適合測試的就是 ambientLight。
目前是:
const ambientLight =
new THREE.AmbientLight(
0xffffff,
0.08
);
可以試著改成不同數值比較看看暗面會變什麼樣。
最後再改回 0.08
你就會很清楚知道 AmbientLight 的作用。
也可以暫時把 sunLight 註解掉
// scene.add(
// sunLight
// );
只留下 AmbientLight。
這時候你會發現行星還是看得到,
但是沒有明顯的亮面與暗面方向。
這就是 PointLight 和 AmbientLight 最明顯的差別。
今天 Day 19 加入 THREE.AmbientLight
替整個場景增加非常微弱的補光。
其中
PointLight → 決定主要明暗方向
AmbientLight → 讓暗面不要完全消失
我們也要記住 AmbientLight 不是越亮越好
如果 AmbientLight 太強,反而會失去太陽照射的立體感。
所以今天的重點不是單純 「多加一盞燈」 而是開始學習,
如何搭配不同光源,控制整個 3D 場景的明暗。
「Shadow:讓行星開始產生陰影」
目前我們已經有:
Texture + MeshStandardMaterial + PointLight + AmbientLight
但 Three.js 的 Shadow 並不會自動出現。
Day 20 我們會開始設定
renderer.shadowMap.enabled 以及 castShadow 和 receiveShadow
讓物件真正參與陰影計算。
也會了解 Light 和 Shadow 其實不是同一件事情。