Appearance
基础入门
本地环境配置
1. html 文件引入
html
<script src="./build/three.js"></script>
2. type="importmap"配置路径
通过配置<script type="importmap">
,让学习环境.html 文件,也能和 vue 或 react 开发环境中一样方式引入 threejs 扩展库
html
<script type="importmap">
{
"imports": {
"three": "./three.js/build/three.module.js",
"three/addons/": "./three.js/examples/jsm/"
}
}
</script>
3. ES6 import 方式引入
html
<!-- 配置type="importmap",.html文件也能和项目开发环境一样方式引入threejs -->
<script type="module">
import * as THREE from "three";
// 扩展库OrbitControls.js
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
// 扩展库GLTFLoader.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
</script>
创建 3d 案例
1. 场景Scene
创建 3d 场景
js// 创建3D场景对象Scene const scene = new THREE.Scene();
创建一个几何体
几何体
Geometry
: 表示三维物体的几何形状 , 包括长方体、圆柱体、球体等js//创建一个长方体几何对象Geometry const geometry = new THREE.BoxGeometry(100, 100, 100);
材质
Material
: 定义几何体的外观效果js//创建一个材质对象Material const material = new THREE.MeshBasicMaterial({ color: 0xff0000, //0xff0000设置材质颜色为红色 });
网格模型
Mesh
: 定义几何体js// 两个参数分别为几何体geometry、材质material const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
模型位置
.position
(定义模型在三维场景中所处的位置)jsconst mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh //设置网格模型在三维空间中的位置坐标,默认是坐标原点 mesh.position.set(0, 10, 0);
方法
.add()
(将模型添加到三维场景中)jsscene.add(mesh);
2. 相机Camera
创建透视投影相机
js// 实例化一个透视投影相机对象, 本质模拟人眼观察 const camera = new THREE.PerspectiveCamera();
相机位置
js//相机在Three.js三维坐标系中的位置 // 根据需要设置相机位置具体值 camera.position.set(200, 200, 200);
相机观察目标
js//相机观察目标指向Threejs 3D空间中某个位置 camera.lookAt(0, 0, 0); //坐标原点 camera.lookAt(mesh.position); //指向mesh对应的位置
定义相机渲染输出的画布尺寸
Canvas 画布: 虚拟相机渲染三维场景在浏览器网页上呈现的结果称为 Canvas 画布
视椎体: 视锥体之内的物体,才会渲染出来,视锥体范围之外的物体不会显示在 Canvas 画布上
js// width和height用来设置Three.js输出的Canvas画布尺寸(像素px) const width = 800; //宽度 const height = 500; //高度 // 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面 const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
3. 渲染器Renderer
创建
WebGL
渲染器对象jsconst renderer = new THREE.WebGLRenderer();
设置 Canvas 画布尺寸
js// 定义threejs输出画布的尺寸(单位:像素px) const width = 800; //宽度 const height = 500; //高度 renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
生成渲染
jsrenderer.render(scene, camera); //执行渲染操作
获得画布并插入 html 页面
.domElement
html<div id="webgl" style="margin-top: 200px;margin-left: 100px;"></div>
jsdocument.getElementById("webgl").appendChild(renderer.domElement);
三维坐标系
辅助观察坐标系
js
// AxesHelper:辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
材质透明度
设置材质半透明,这样可以看到坐标系的坐标原点。
js
const material = new THREE.MeshBasicMaterial({
color: 0x0000ff, //设置材质颜色
transparent: true, //开启透明
opacity: 0.5, //设置透明度
});
AxesHelper
的 xyz 轴
three.js 坐标轴颜色红 R、绿 G、蓝 B 分别对应坐标系的 x、y、z 轴,对于 three.js 的 3D 坐标系默认 y 轴朝上
相机控件 OrbitControls
平时开发调试代码,或者展示模型的时候,可以通过相机控件 OrbitControls 实现旋转缩放预览效果。
1.引入扩展库 OrbitControls.js
js
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
2. 使用 OrbitControls
js
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener("change", function () {
renderer.render(scene, camera); //执行渲染操作
}); //监听鼠标、键盘事件
本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的 360 度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围
光源对物体表面影响
模拟光照 Light
对网格模型 Mesh
表面的影响
提示
threejs 提供的网格材质,有的受光照影响,有的不受光照影响:
- 基础网格材质
MeshBasicMaterial
不会受到光照影响。 - 漫反射网格材质
MeshLambertMaterial
等其它材质会受到光照影响
光源
创建一个点光源:
js
// 参数1:0xffffff是纯白光,表示光源颜色
// 参数2:1.0,表示光照强度,可以根据需要调整
const pointLight = new THREE.PointLight(0xffffff, 1.0);
//光源位置
pointLight.position.set(400, 0, 0); //点光源放在x轴上
// 添加
scene.add(pointLight); //点光源添加到场景中
光源辅助观察
生成一个光源来源的辅助观察对象, 可以通过相机控件旋转缩放三维场景预览
js
// 光源辅助观察: PointLightHelper(点光源), DirectionalLightHelper(平行光)...
const pointLightHelper = new THREE.PointLightHelper(pointLight, 10);
scene.add(pointLightHelper);
环境光
环境光没有特定方向,整体改变场景的光照明暗
js
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);
平行光
沿着特定方向发射的光源
js
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(80, 100, 50);
// 方向光指向对象网格模型mesh,可以不设置,默认的位置是0,0,0
directionalLight.target = mesh;
scene.add(directionalLight);
动画循环渲染
借助 HTML5 的 API 请求动画帧window.requestAnimationFrame
实现动画渲染
js
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
// renderer.render(scene, camera); //执行渲染操作
document.body.appendChild(renderer.domElement);
// 渲染函数
function render() {
renderer.render(scene, camera); //执行渲染操作
mesh.rotateY(0.01); //每次绕y轴旋转0.01弧度
requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
}
render();
TIP
设置了渲染循环, 相机控件OrbitControls
就不用再通过事件change
执行renderer.render(scene, camera)
Canvas 画布布局和渲染帧率
全屏渲染
js
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
const width = window.innerWidth; //窗口文档显示区的宽度作为画布宽度
const height = window.innerHeight; //窗口文档显示区的高度作为画布高度
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
html
<style>
body {
overflow: hidden;
margin: 0px;
}
</style>
canvas 画布宽高度动态变化
当缩放浏览器窗口时, 实时更新渲染保证显示正常
js
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
// 重置渲染器输出画布canvas尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
// 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
camera.updateProjectionMatrix();
};
查看渲染帧率 Stats
在浏览器上显示渲染的 fps, 场景越复杂渲染性能越低
引入性能监视器 Stats
jsimport Stats from "three/addons/libs/stats.module.js";
使用 Stats
js//创建stats对象 const stats = new Stats(); //stats.domElement:web页面上输出计算结果,一个div元素, document.body.appendChild(stats.domElement); // 渲染函数 function render() { //requestAnimationFrame循环调用的函数中调用方法update(),来刷新时间 stats.update(); renderer.render(scene, camera); //执行渲染操作 requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧 } render();
stats 方法
setMode(mode)
设置首次打开页面,测试结果的显示模式,鼠标单击可以更换不同的显示模式。js// stats.domElement显示:渲染帧率 刷新频率,一秒渲染次数 stats.setMode(0); //默认模式 //stats.domElement显示:渲染周期 渲染一帧多长时间(单位:毫秒ms) stats.setMode(1);
常见几何体
js
//BoxGeometry:长方体
const geometry = new THREE.BoxGeometry(100, 100, 100);
// SphereGeometry:球体
const geometry = new THREE.SphereGeometry(50);
// CylinderGeometry:圆柱
const geometry = new THREE.CylinderGeometry(50, 50, 100);
// PlaneGeometry:矩形平面
const geometry = new THREE.PlaneGeometry(100, 50);
// CircleGeometry:圆形平面
const geometry = new THREE.CircleGeometry(50);
双面可见
Three.js 的材质默认正面可见,反面不可见; 平面几何体只能看到一面
js
new THREE.MeshBasicMaterial({
//两面可见, 默认FrontSide
side: THREE.DoubleSide,
});
高光网络材质 Phong
MeshPhongMaterial
提供一个镜面反射效果, 可以实现 MeshLambertMaterial
不能实现的高光反射效果
高光亮度属性.shininess
js
// 模拟镜面反射,产生一个高光效果
const material = new THREE.MeshPhongMaterial({
color: 0xff0000,
shininess: 20, //高光部分的亮度,默认30
});
高光颜色属性.specular
js
const material = new THREE.MeshPhongMaterial({
color: 0xff0000,
shininess: 20, //高光部分的亮度,默认30
specular: 0x444444, //高光部分的颜色
});
WebGL 渲染器设置(锯齿模糊)
渲染器锯齿属性.antialias
js
const renderer = new THREE.WebGLRenderer({
antialias: true,
});
或者
js
renderer.antialias = true;
获取设备像素比window.devicePixelRatio
js
// 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同
console.log("查看当前屏幕设备像素比", window.devicePixelRatio);
设置设备像素比.setPixelRatio()
js
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);
设置背景颜色.setClearColor()
js
renderer.setClearColor(0x444444, 1); //设置背景颜色
gui.js 库(可视化改变三维场景)
借助 dat.gui.js 可以快速创建控制三维场景的 UI 交互界面
1. 创建一个 GUI 对象
js
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
// 实例化一个gui对象
const gui = new GUI();
2. 改变 GUI 界面默认的 style 属性
js
//改变交互界面style属性
gui.domElement.style.right = "0px";
gui.domElement.style.width = "300px";
3. 改变属性值
.add()
方法: 可以快速创建一个 UI 交互界面,比如一个拖动条; 用来改变一个 JavaScript 对象属性的属性值。
格式:.add(控制对象,对象具体属性,其他参数)
js
// 添加模型位置控制, 参数3和参数4定义一个区间范围
gui.add(mesh.position, "x", 0, 180);
gui.add(mesh.position, "y", 0, 180);
gui.add(mesh.position, "z", 0, 180);
gui 调试-颜色命名等
命名.name
.add()
创建的交互界面,会默认显示所改变属性的名字。通过.name
可以自定义显示名称
js
const gui = new GUI(); //创建GUI对象
gui.add(ambient, "intensity", 0, 2.0).name("环境光强度");
gui.add(directionalLight, "intensity", 0, 2.0).name("平行光强度");
步长.step()
js
gui.add(ambient, "intensity", 0, 2.0).name("环境光强度").step(0.1);
方法.onChange()
js
const obj = { x: 30 };
// 当obj的x属性变化的时候,就把此时obj.x的值value赋值给mesh的x坐标
gui.add(obj, "x", 0, 180).onChange(function (value) {
mesh.position.x = value;
// 可以写任何想跟着obj.x同步变化的事件
});
颜色值.addColor()
生成颜色值改变的交互界面
js
const obj = {
color: 0x00ffff,
};
gui.addColor(obj, "color").onChange(function (value) {
mesh.material.color.set(value);
});
gui 调试-下拉菜单、单选框
下拉菜单
传入数组
jsconst obj = { scale: 0 }; // 参数3数据类型:数组(下拉菜单) gui .add(obj, "scale", [-100, 0, 100]) .name("y坐标") .onChange(function (value) { mesh.position.y = value; });
传入对象
jsconst obj = { scale: 0 }; // 参数3数据类型:对象(下拉菜单) gui .add(obj, "scale", { 左: -100, 中: 0, 右: 100, }) .name("位置选择") .onChange(function (value) { mesh.position.x = value; });
单选框
js
const obj = {
bool: false,
};
// 改变的obj属性数据类型是布尔值,交互界面是单选框
gui.add(obj, "bool").name("是否旋转");
控制一个对象是否旋转:
js
gui.add(obj, "bool").name("旋转动画");
// 渲染循环
function render() {
// 当gui界面设置obj.bool为true,mesh执行旋转动画
if (obj.bool) mesh.rotateY(0.01);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
gui.js 库(分组)
当 GUI 交互界面需要控制的属性比较多的时候,为了避免混合,适当分组管理可以清晰
创建子菜单分组.addFolder()
创建材质子菜单
jsconst gui = new GUI(); //创建GUI对象 const obj = { color: 0x00ffff }; const matFolder = gui.addFolder("材质"); // 材质颜色color matFolder.addColor(obj, "color").onChange((value) => { material.color.set(value); }); // 材质高光颜色specular matFolder.addColor(obj, "specular").onChange((value) => { material.specular.set(value); });
创建平行光子菜单
jsconst dirFolder = gui.addFolder("平行光"); dirFolder.add(directionalLight, "intensity", 0, 2); // 平行光位置 dirFolder.add(directionalLight.position, "x", -400, 400); dirFolder.add(directionalLight.position, "y", -400, 400); dirFolder.add(directionalLight.position, "z", -400, 400);
关闭.close()和展开.open()交互界面
js
const gui = new GUI(); //创建GUI对象
gui.close(); //关闭菜单
// 创建材质子菜单
const matFolder = gui.addFolder("材质");
matFolder.close(); //关闭菜单
子菜单嵌套子菜单
js
// 平行光子菜单
const dirFolder = gui.addFolder("平行光");
// 平行光强度
dirFolder.add(directionalLight, "intensity", 0, 2);
const dirFolder2 = dirFolder.addFolder("位置"); //子菜单的子菜单
// 平行光位置
dirFolder2.add(directionalLight.position, "x", -400, 400);
dirFolder2.add(directionalLight.position, "y", -400, 400);
dirFolder2.add(directionalLight.position, "z", -400, 400);