three.js基础之简单动画、GUI、Controls、相机、灯光、简单材质
本系列中案例参考自 https://github.com/josdirksen/learning-threejs-third
动画
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere } from "./init.js";
const gui = new GUI();
function init() {
window.addEventListener("resize", onResize, false);
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
cube.position.set(-10, 5, 0);
scene.add(cube);
const sphere = addSphere({ color: 0x7777ff });
sphere.position.set(20, 5, 0);
scene.add(sphere);
let step = 0;
const controls = new (function () {
this.rotationSpeed = 0.02;
this.bouncingSpeed = 0.03;
})();
gui.add(controls, "rotationSpeed", 0, 0.5);
gui.add(controls, "bouncingSpeed", 0, 0.5);
const trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
render();
function render() {
trackballControls.update(clock.getDelta());
cube.rotation.x += controls.rotationSpeed;
cube.rotation.y += controls.rotationSpeed;
cube.rotation.z += controls.rotationSpeed;
step += controls.bouncingSpeed;
sphere.position.x = 20 + 10 * Math.cos(step);
sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
}
init();
</script>
GUI
gui用于调节控制各种物体的参数,结合动画使用非常有用
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox } from "./init.js";
function init() {
const gui = new GUI();
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const gui_ambientLight = gui.addFolder("AmbientLight");
gui_ambientLight.add(ambientLight, "intensity", 0, 10);
const ambientLight_controls = new (function () {
this.ambientColor = ambientLight.color.getStyle();
this.disableSpotlight = false;
})();
gui_ambientLight.addColor(ambientLight_controls, "ambientColor").onChange(function (e) {
ambientLight.color = new THREE.Color(ambientLight_controls.ambientColor);
});
gui_ambientLight.add(ambientLight_controls, "disableSpotlight").onChange(function (e) {
spotLight.visible = !e;
});
const spotLight = initSpotLight();
spotLight.position.set(-40, 60, -10);
scene.add(spotLight);
const gui_spotLight = gui.addFolder("SpotLight");
gui_spotLight.add(spotLight, "intensity", 0, 200);
const plane = addPlane();
scene.add(plane);
const box = addBox({ color: 0xff0000 });
box.position.set(-10, 5, 0);
scene.add(box);
const gui_box = gui.addFolder("box");
gui_box.add(box.position, "x", -20, 20);
gui_box.add(box.position, "y", 10, 50);
gui_box.add(box.position, "z", -20, 20);
const obj = {
color: box.material.color.getStyle(),
};
gui_box.addColor(obj, "color").onChange(function (value) {
box.material.color.set(value);
});
const step = 0;
const controls = new (function () {
this.rotationSpeed = 0.02;
this.numberOfObjects = scene.children.length;
this.removeCube = function () {
const allChildren = scene.children;
const lastObject = allChildren[allChildren.length - 1];
if (lastObject instanceof THREE.Mesh) {
scene.remove(lastObject);
this.numberOfObjects = scene.children.length;
}
};
this.addCube = function () {
const cubeSize = Math.ceil(Math.random() * 3);
const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const cubeMaterial = new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff,
});
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.position.x = -30 + Math.round(Math.random() * planeGeometry.parameters.width);
cube.position.y = Math.round(Math.random() * 5);
cube.position.z = -20 + Math.round(Math.random() * planeGeometry.parameters.height);
scene.add(cube);
this.numberOfObjects = scene.children.length;
};
this.outputObjects = function () {
console.log(scene.children);
};
})();
const gui_opts = gui.addFolder("opts");
gui_opts.add(controls, "rotationSpeed", 0, 0.5);
gui_opts.add(controls, "addCube");
gui_opts.add(controls, "removeCube");
gui_opts.add(controls, "outputObjects");
gui_opts.add(controls, "numberOfObjects").listen();
const trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
render();
function render() {
const spt = clock.getDelta() * 1000; //毫秒
trackballControls.update(clock.getDelta());
scene.traverse(function (e) {
if (e instanceof THREE.Mesh && e != plane) {
e.rotation.x += controls.rotationSpeed;
e.rotation.y += controls.rotationSpeed;
e.rotation.z += controls.rotationSpeed;
}
});
requestAnimationFrame(render);
renderer.render(scene, camera);
}
}
init();
</script>
Controls
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { FlyControls } from "three/addons/controls/FlyControls.js";
import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere, addCylinder } from "./init.js";
function init() {
const gui = new GUI();
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000, width: 4, height: 4, depth: 4 });
cube.position.set(-10, 5, 0);
scene.add(cube);
const sphere = addSphere({ color: 0x7777ff, radius: 4 });
sphere.position.set(20, 5, 0);
scene.add(sphere);
const cylinder = addCylinder({ color: 0x77ff77, tr: 2, br: 2, height: 20 });
scene.add(cylinder);
const clock = new THREE.Clock();
// 轨道控制器(OrbitControls)
// Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动。
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotate = true;
// 轨迹球控制器(TrackballControls)
// TrackballControls 与 OrbitControls 相类似。然而,它不能恒定保持摄像机的up向量。
// 这意味着,如果摄像机绕过“北极”和“南极”,则不会翻转以保持“右侧朝上”。
const trackballControls = new TrackballControls(camera, renderer.domElement);
// 飞行控制器(FlyControls)飞行模式的导航方式。
// 你可以在3D空间中任意变换摄像机,并且无任何限制
const flyControls = new FlyControls(camera, renderer.domElement);
flyControls.movementSpeed = 25;
flyControls.rollSpeed = Math.PI / 24;
flyControls.autoForward = true;
flyControls.dragToLook = false;
// FirstPersonControls
const fpControls = new FirstPersonControls(camera, renderer.domElement);
fpControls.lookSpeed = 0.4;
fpControls.movementSpeed = 20;
fpControls.lookVertical = true;
fpControls.constrainVertical = true;
fpControls.verticalMin = 1.0;
fpControls.verticalMax = 2.0;
fpControls.lon = -150;
fpControls.lat = 120;
const controls = {
selectedControl: "orbit",
control: orbitControls,
};
gui.add(controls, "selectedControl", ["orbit", "trackball", "fly", "first-person"]).onChange(function (e) {
switch (e) {
case "orbit":
controls.control = orbitControls;
break;
case "trackball":
controls.control = trackballControls;
break;
case "fly":
controls.control = flyControls;
break;
case "first-person":
controls.control = fpControls;
break;
}
});
render();
function render() {
controls.control.update(clock.getDelta());
requestAnimationFrame(render);
renderer.render(scene, camera);
}
}
init();
</script>
相机
<canvas id="OrthographicCamera"></canvas>
<canvas id="PerspectiveCamera"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initAmbientLight, initDirectionalLight, addPlane, addBox } from "./init.js";
let resize_Orthographic;
let resize_Perspective;
const width = 500;
const height = 400;
const gui = new GUI();
{
const renderer = initRenderer("OrthographicCamera", width, height);
const scene = new THREE.Scene();
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const directionalLight = initDirectionalLight();
scene.add(directionalLight);
// 正交相机,无论物体距离相机距离远或者近,在最终渲染的图片中物体的大小都保持不变。 这对于渲染2D场景或者UI元素是非常有用的。
// OrthographicCamera( left : Number, right : Number, top : Number, bottom : Number, near : Number, far : Number )
// left — 摄像机视锥体左侧面
// right — 摄像机视锥体右侧面
// top — 摄像机视锥体上侧面
// bottom — 摄像机视锥体下侧面
// near — 摄像机视锥体近端面
// far — 摄像机视锥体远端面
const obj = {
N: 4,
};
const camera = new THREE.OrthographicCamera(width / -obj.N, width / obj.N, height / obj.N, height / -obj.N, 1, 1000);
camera.position.set(-30, 30, 30);
camera.lookAt(0, 0, 0);
scene.add(camera);
const gui_orthographic = gui.addFolder("Orthographic");
gui_orthographic.add(camera.position, "x", -30, 30);
gui_orthographic.add(camera.position, "x", -30, 30);
gui_orthographic.add(camera.position, "y", -30, 30);
gui_orthographic.add(obj, "N", 2, 50).onChange(function (value) {
camera.left = width / -value;
camera.right = width / value;
camera.top = height / value;
camera.bottom = height / -value;
camera.updateProjectionMatrix();
console.log(obj, value);
});
gui_orthographic.add(camera, "left", width / -2, 0);
gui_orthographic.add(camera, "right", 0, width / 2);
gui_orthographic.add(camera, "top", 0, height / 2);
gui_orthographic.add(camera, "bottom", height / -2, 0);
gui_orthographic.add(camera, "near", 0.1, 10);
gui_orthographic.add(camera, "far", 500, 5000);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0x44ff44 });
scene.add(cube);
render();
function render() {
camera.updateProjectionMatrix(); //相机参数更新
requestAnimationFrame(render);
renderer.render(scene, camera);
}
resize_Orthographic = function () {
const width = 400;
const height = 400;
renderer.setSize(width, height); //更新Canvas画布尺寸
camera.left = width / -obj.N;
camera.right = width / obj.N;
camera.top = height / -obj.N;
camera.bottom = height / obj.N;
camera.updateProjectionMatrix(); //相机参数更新
};
}
{
const renderer = initRenderer("PerspectiveCamera", width, height);
const scene = new THREE.Scene();
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const directionalLight = initDirectionalLight();
scene.add(directionalLight);
// 透视相机被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度
// aspect — 摄像机视锥体长宽比
// near — 摄像机视锥体近端面
// far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); //透视投影相机参数设置
camera.position.set(-30, 30, 30);
camera.lookAt(0, 0, 0);
scene.add(camera);
const gui_perspective = gui.addFolder("Perspective");
gui_perspective.add(camera.position, "x", -30, 30);
gui_perspective.add(camera.position, "y", -30, 30);
gui_perspective.add(camera.position, "z", -30, 30);
gui_perspective.add(camera, "fov", 0, 90);
gui_perspective.add(camera, "aspect", 0.1, 5);
gui_perspective.add(camera, "near", 0.1, 10);
gui_perspective.add(camera, "far", 500, 5000);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0x44ff44 });
scene.add(cube);
render();
function render() {
camera.updateProjectionMatrix(); //相机参数更新
requestAnimationFrame(render);
renderer.render(scene, camera);
}
resize_Perspective = function () {
const width = 400;
const height = 400;
renderer.setSize(width, height); //更新Canvas画布尺寸
camera.aspect = width / height;
camera.updateProjectionMatrix(); //相机参数更新
};
}
window.addEventListener("resize", resize_Orthographic);
window.addEventListener("resize", resize_Perspective);
</script>
例子
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane } from "./init.js";
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
let camera = initPerspectiveCamera();
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const width = 30;
const height = 30;
const plane = addPlane({ width: width, height: height });
scene.add(plane);
const cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
for (let j = 0; j < height / 5; j++) {
for (let i = 0; i < width / 5; i++) {
const rnd = Math.random() * 0.75 + 0.25;
const cubeMaterial = new THREE.MeshLambertMaterial();
cubeMaterial.color = new THREE.Color(rnd, 0, 0);
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.z = -(height / 2) + 2 + j * 5;
cube.position.x = -(width / 2) + 2 + i * 5;
cube.position.y = 2;
scene.add(cube);
}
}
const controls = new (function () {
this.perspective = "Perspective";
this.switchCamera = function () {
if (camera instanceof THREE.PerspectiveCamera) {
camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);
camera.position.set(-30, 30, 30);
camera.lookAt(scene.position);
trackballControls = new TrackballControls(camera, renderer.domElement);
this.perspective = "Orthographic";
} else {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(-30, 30, 30);
camera.lookAt(scene.position);
trackballControls = new TrackballControls(camera, renderer.domElement);
this.perspective = "Perspective";
}
};
})();
const gui = new GUI();
gui.add(controls, "switchCamera");
gui.add(controls, "perspective").listen();
let trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
let step = 0;
render();
function render() {
trackballControls.update(clock.getDelta());
// step += 0.02;
// if (camera instanceof THREE.Camera) {
// const x = 10 + 100 * Math.sin(step);
// camera.lookAt(new THREE.Vector3(x, 10, 0));
// }
requestAnimationFrame(render);
renderer.render(scene, camera);
}
</script>
ArrayCamera
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import Stats from "three/addons/libs/stats.module.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { initRenderer, initPerspectiveCamera, initDirectionalLight, addPlane, addCylinder } from "./init.js";
function init() {
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const dirLight = initDirectionalLight();
scene.add(dirLight);
dirLight.position.set(0.5, 0.5, 1);
dirLight.shadow.camera.zoom = 4;
const AMOUNT = 4;
const ASPECT_RATIO = window.innerWidth / window.innerHeight;
const WIDTH = (window.innerWidth / AMOUNT) * window.devicePixelRatio;
const HEIGHT = (window.innerHeight / AMOUNT) * window.devicePixelRatio;
const cameras = [];
for (let y = 0; y < AMOUNT; y++) {
for (let x = 0; x < AMOUNT; x++) {
const subcamera = new THREE.PerspectiveCamera(40, ASPECT_RATIO, 0.1, 10);
subcamera.viewport = new THREE.Vector4(Math.floor(x * WIDTH), Math.floor(y * HEIGHT), Math.ceil(WIDTH), Math.ceil(HEIGHT));
subcamera.position.x = x / AMOUNT - 0.5;
subcamera.position.y = 0.5 - y / AMOUNT;
subcamera.position.z = 1.5;
subcamera.position.multiplyScalar(2);
subcamera.lookAt(0, 0, 0);
subcamera.updateMatrixWorld();
cameras.push(subcamera);
}
}
const camera = new THREE.ArrayCamera(cameras);
camera.position.z = 3;
scene.add(camera);
const plane = addPlane({ width: 100, height: 100, color: 0x999999 });
plane.rotation.x = 0;
plane.position.set(0, 0, -1);
scene.add(plane);
const cylinder = addCylinder({ tr: 0.5, br: 0.5, height: 1 });
cylinder.receiveShadow = true;
scene.add(cylinder);
const trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const stats = new Stats();
document.body.appendChild(stats.dom);
render();
function render() {
trackballControls.update(clock.getDelta());
requestAnimationFrame(render);
stats.begin();
renderer.render(scene, camera);
stats.end();
}
}
init();
</script>
灯光
AmbientLight
<canvas id="canvas-ambient-light"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";
const gui = new GUI();
const renderer = initRenderer("canvas-ambient-light");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
//环境光:环境光会均匀的照亮场景中的所有物体。 不能用来投射阴影,因为它没有方向。
// AmbientLight( color : Color, intensity : Float )
//参数:光源颜色、光照强度
const ambient = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambient);
gui.add(ambient, "intensity", 0, 20).name("环境光强度");
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
scene.add(cube);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
</script>
DirectionalLight
<canvas id="canvas-directional-light"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";
const gui = new GUI();
const renderer = initRenderer("canvas-directional-light");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
// 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。平行光可以投射阴影。
// DirectionalLight( color : Color, intensity : Float )
//参数:光源颜色、光照强度
const directionalLight = new THREE.DirectionalLight(0xffffff, 5);
directionalLight.position.set(-30, 30, -30);
directionalLight.castShadow = true; // 开启光源阴影
// 设置三维场景计算阴影的范围
directionalLight.shadow.camera.left = -200;
directionalLight.shadow.camera.right = 200;
directionalLight.shadow.camera.top = 200;
directionalLight.shadow.camera.bottom = -200;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 300;
console.log("阴影默认像素", directionalLight.shadow.mapSize); // mapSize属性默认512x512
console.log("shadow.radius", directionalLight.shadow.radius); // 模糊弱化阴影边缘
scene.add(directionalLight);
// DirectionalLightHelper:可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight);
scene.add(dirLightHelper);
// 可视化平行光阴影对应的正投影相机对象
const shadowHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
scene.add(cube);
let step = 0;
const controls = initControls();
render();
function render() {
if (!controls.stopMovingLight) {
step += 0.03;
const pos = {};
pos.z = -8;
pos.y = +(27 * Math.sin(step / 3));
pos.x = 10 + 26 * Math.cos(step / 3);
directionalLight.position.copy(pos);
dirLightHelper.update();
}
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function initControls() {
gui.add(directionalLight, "intensity", 0, 20).name("平行光强度");
gui.add(directionalLight.position, "x", -30, 30).name("平行光位置x");
gui.add(directionalLight.position, "y", -30, 30).name("平行光位置y");
gui.add(directionalLight.position, "z", -30, 30).name("平行光位置z");
const controls = {
directionalLightColor: directionalLight.color.getStyle(),
shadowDebug: false,
castShadow: true,
onlyShadow: false,
target: "Plane",
stopMovingLight: false,
};
gui.addColor(controls, "directionalLightColor").onChange(function (e) {
directionalLight.color = new THREE.Color(e);
});
gui.add(controls, "shadowDebug").onChange(function (e) {
e ? scene.add(shadowHelper) : scene.remove(shadowHelper);
});
gui.add(controls, "castShadow").onChange(function (e) {
directionalLight.castShadow = e;
});
gui.add(controls, "onlyShadow").onChange(function (e) {
directionalLight.onlyShadow = e;
});
gui.add(controls, "target", ["Plane", "Mesh"]).onChange(function (e) {
switch (e) {
case "Plane":
directionalLight.target = plane;
break;
case "Mesh":
directionalLight.target = mesh;
break;
}
});
gui.add(controls, "stopMovingLight");
return controls;
}
</script>
PointLight
<canvas id="canvas-point-light"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";
const gui = new GUI();
const renderer = initRenderer("canvas-point-light");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
//点光源:从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。
// PointLight( color : Color, intensity : Float, distance : Number, decay : Float )
// 参数:光源颜色、光照强度、光源照射的最大距离、沿着光照距离的衰退量
const pointLight = new THREE.PointLight(0xffffff, 5, 0, 0.5);
pointLight.castShadow = true; // 开启光源阴影
pointLight.position.set(-30, 30, -30);
scene.add(pointLight);
// 光源辅助观察
const pointLightHelper = new THREE.PointLightHelper(pointLight);
scene.add(pointLightHelper);
const shadowHelper = new THREE.CameraHelper(pointLight.shadow.camera);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
scene.add(cube);
let invert = 1;
let phase = 0;
const controls = setupControls();
render();
function render() {
if (!controls.stopMovingLight) {
if (phase > 2 * Math.PI) {
invert = invert * -1;
phase -= 2 * Math.PI;
} else {
phase += 0.03;
}
const pos = {};
pos.z = +(25 * Math.sin(phase));
pos.x = +(14 * Math.cos(phase));
pos.y = 5;
if (invert < 0) {
let pivot = 14;
pos.x = invert * (pos.x - pivot) + pivot;
}
pointLight.position.copy(pos);
pointLightHelper.update();
}
shadowHelper.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function setupControls() {
const controls = {
pointLightColor: pointLight.color.getStyle(),
shadowDebug: false,
castShadow: true,
stopMovingLight: false,
};
gui.add(pointLight, "intensity", 0, 500).name("点光源强度");
gui.add(pointLight, "distance", 0, 5000);
gui.add(pointLight, "decay", 0, 5);
gui.add(pointLight.position, "x", -30, 30).name("点光源位置x");
gui.add(pointLight.position, "y", -30, 30).name("点光源位置y");
gui.add(pointLight.position, "z", -30, 30).name("点光源位置z");
gui.addColor(controls, "pointLightColor").onChange(function (e) {
pointLight.color = new THREE.Color(e);
});
gui.add(controls, "shadowDebug").onChange(function (e) {
e ? scene.add(shadowHelper) : scene.remove(shadowHelper);
});
gui.add(controls, "castShadow").onChange(function (e) {
pointLight.castShadow = e;
});
gui.add(controls, "stopMovingLight");
return controls;
}
</script>
SpotLight
<canvas id="canvas-spot-light"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";
const gui = new GUI();
const renderer = initRenderer("canvas-spot-light");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
// 聚光源:光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大。
// SpotLight( color : Color, intensity : Float, distance : Float, angle : Radians, penumbra : Float, decay : Float )
// 参数:光源颜色、光照强度、光源照射的最大距离、光线照射范围的角度、聚光锥的半影衰减百分比、沿着光照距离的衰退量
const spotLight = new THREE.SpotLight(0xffffff, 20, 0, Math.PI / 3, 0, 0.5);
spotLight.position.set(-30, 30, -30);
spotLight.castShadow = true; // 开启光源阴影
spotLight.shadow.mapSize.width = 512;
spotLight.shadow.mapSize.height = 512;
spotLight.shadow.camera.near = 0.5;
spotLight.shadow.camera.far = 500;
spotLight.shadow.camera.fov = 1;
scene.add(spotLight);
const shadowHelper = new THREE.CameraHelper(spotLight.shadow.camera);
// 聚光源辅助对象,可视化聚光源
const spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
scene.add(cube);
const controls = setupControls();
let step = 0;
let invert = 1;
let phase = 0;
render();
function render() {
if (!controls.stopMovingLight) {
if (phase > 2 * Math.PI) {
invert = invert * -1;
phase -= 2 * Math.PI;
} else {
phase += 0.03;
}
const pos = {};
pos.z = +(7 * Math.sin(phase));
pos.x = +(14 * Math.cos(phase));
pos.y = 15;
if (invert < 0) {
const pivot = 14;
pos.x = invert * (pos.x - pivot) + pivot;
}
spotLight.position.copy(pos);
}
spotLightHelper.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function setupControls() {
gui.add(spotLight, "intensity", 0, 200).name("聚光源强度");
gui.add(spotLight, "angle", 0, 2 * Math.PI).name("聚光源照射范围的角度");
gui.add(spotLight, "penumbra", 0, 100).name("聚光源聚光锥的半影衰减百分比");
gui.add(spotLight, "distance", 0, 500).name("聚光源照射的最大距离");
gui.add(spotLight, "decay", 0, 5).name("聚光源沿着光照距离的衰退量");
gui.add(spotLight.position, "x", -30, 30).name("聚光源位置x");
gui.add(spotLight.position, "y", -30, 30).name("聚光源位置y");
gui.add(spotLight.position, "z", -30, 30).name("聚光源位置z");
const controls = {
spotLightColor: spotLight.color.getStyle(),
shadowDebug: false,
castShadow: true,
target: "Plane",
stopMovingLight: false,
penumbra: 0,
};
gui.addColor(controls, "spotLightColor").onChange(function (e) {
spotLight.color = new THREE.Color(e);
});
gui.add(controls, "shadowDebug").onChange(function (e) {
e ? scene.add(shadowHelper) : scene.remove(shadowHelper);
});
gui.add(controls, "castShadow").onChange(function (e) {
spotLight.castShadow = e;
});
gui.add(controls, "target", ["Plane", "Mesh"]).onChange(function (e) {
switch (e) {
case "Plane":
spotLight.target = plane;
break;
case "Mesh":
spotLight.target = mesh;
break;
}
});
gui.add(controls, "stopMovingLight");
return controls;
}
</script>
HemisphereLight
<canvas id="canvas-hemisphere-light"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";
const gui = new GUI();
const renderer = initRenderer("canvas-hemisphere-light");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
// 半球光:光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。半球光不能投射阴影。
// HemisphereLight( color : Integer, groundColor : Integer, intensity : Float )
// 参数:天空光线颜色、地面光线颜色、光源照射的最大距离、光照强度
const hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
hemisphereLight.position.set(-30, 30, -30);
scene.add(hemisphereLight);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
scene.add(cube);
let step = 0;
const controls = setupControls();
render();
function render() {
if (!controls.stopMovingLight) {
step += 0.03;
const pos = {};
pos.z = -8;
pos.y = +(27 * Math.sin(step / 3));
pos.x = 10 + 26 * Math.cos(step / 3);
hemisphereLight.position.copy(pos);
}
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function setupControls() {
const controls = {
color: 0xffffbb,
groundColor: 0x080820,
stopMovingLight: false,
};
gui.add(hemisphereLight, "intensity", 0, 500).name("半球光强度");
gui.addColor(controls, "color").onChange(function (e) {
hemisphereLight.color = new THREE.Color(e);
});
gui.addColor(controls, "groundColor").onChange(function (e) {
hemisphereLight.groundColor = new THREE.Color(e);
});
gui.add(controls, "stopMovingLight");
return controls;
}
</script>
RectAreaLight
<canvas id="canvas-rectarea-light"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { RectAreaLightUniformsLib } from "three/addons/lights/RectAreaLightUniformsLib.js";
import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";
RectAreaLightUniformsLib.init();
const gui = new GUI();
const renderer = initRenderer("canvas-rectarea-light");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
// 平面光:平面光光源从一个矩形平面上均匀地发射光线。这种光源可以用来模拟像明亮的窗户或者条状灯光光源。不支持阴影。
// 只支持 MeshStandardMaterial 和 MeshPhysicalMaterial 两种材质。
// RectAreaLight( color : Color, intensity : Float, width : Float, height : Float )
// 参数:光源颜色、光照强度、光源宽度、光源高度
const areaLight1 = new THREE.RectAreaLight(0xff0000, 50, 4, 10);
areaLight1.position.set(-30, 10, 10);
scene.add(areaLight1);
const areaLight2 = new THREE.RectAreaLight(0x00ff00, 50, 4, 10);
areaLight2.position.set(0, 10, 10);
scene.add(areaLight2);
const areaLight3 = new THREE.RectAreaLight(0x0000ff, 50, 4, 10);
areaLight3.position.set(30, 10, 10);
scene.add(areaLight3);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
const planeGeometry = new THREE.PlaneGeometry(100, 50, 1, 1);
const planeMaterial = new THREE.MeshStandardMaterial({
roughness: 0.044676705160855,
metalness: 0.0,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(0, 0, 0);
scene.add(plane);
const cube = addBox({ color: 0xff0000 });
scene.add(cube);
let step = 0;
const controls = setupControls();
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function setupControls() {
const controls = {
color1: 0xff0000,
color2: 0x00ff00,
color3: 0x0000ff,
};
gui.addColor(controls, "color1").onChange(function (e) {
areaLight1.color = new THREE.Color(e);
});
gui.add(areaLight1, "intensity", 0, 200);
gui.add(areaLight1, "width", 0, 10);
gui.add(areaLight1, "height", 0, 20);
gui.addColor(controls, "color2").onChange(function (e) {
areaLight2.color = new THREE.Color(e);
});
gui.add(areaLight2, "intensity", 0, 200);
gui.add(areaLight2, "width", 0, 10);
gui.add(areaLight2, "height", 0, 20);
gui.addColor(controls, "color3").onChange(function (e) {
areaLight3.color = new THREE.Color(e);
});
gui.add(areaLight3, "intensity", 0, 200);
gui.add(areaLight3, "width", 0, 10);
gui.add(areaLight3, "height", 0, 20);
return controls;
}
</script>
材质
<canvas id="MeshBasicMaterial"></canvas>
<canvas id="MeshLambertMaterial"></canvas>
<canvas id="MeshPhongMaterial"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight } from "./init.js";
const gui = new GUI();
function init(domId, callback) {
const width = 300;
const height = 200;
const renderer = initRenderer(domId, width, height);
const scene = new THREE.Scene();
const camera = initPerspectiveCamera({ aspect: width / height });
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
callback(scene);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
}
init("MeshBasicMaterial", (scene) => {
const geometry = new THREE.BoxGeometry(10, 20, 10);
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
init("MeshLambertMaterial", (scene) => {
const geometry = new THREE.BoxGeometry(10, 20, 10);
// 漫反射网格材质
const material = new THREE.MeshLambertMaterial({
color: 0xff0000,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
init("MeshPhongMaterial", (scene) => {
const geometry = new THREE.BoxGeometry(10, 20, 10);
// 高光网格材质
const material = new THREE.MeshPhongMaterial({
color: 0xff0000,
shininess: 10, //高光部分的亮度,默认30
specular: 0xffffff, //高光部分的颜色
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
gui.add(material, "shininess", 0, 100).name("高光网格材质高光部分的亮度");
const obj = {
color: 0xffffff,
};
gui.addColor(obj, "color").onChange(function (value) {
mesh.material.specular.set(value);
});
});
</script>
合集:
three.js基础
分类:
javascript
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!