openGL
是一个跨平台3D/2D的绘图标准,WebGL
是openGL
在浏览器中的一个实现。
编程人员可以直接用WebGL
接口进行编程,但需要一定的学习成本且代码量大。
Three.js
对WebGL
进行了封装,更为轻松地进行Web 3D开发,降低使用门槛的同时,提高了开发效率。
属性名称 | 描述 |
---|---|
场景(Scene) | 是一个三维空间,所有物品的容器,可以把场景想象成一个空房间,接下来会往房间里放要呈现的物体、相机、光源等。可以配合 chrome 插件使用,抛出 window.scene 即可实时调整 obj 的信息和材质信息。 |
相机(Camera) | 必须要往场景中添加一个相机,相机用来确定位置、方向、角度,相机看到的内容就是我们屏幕中看到的内容。 正交相机:无论物体距离相机距离远或者近,最终渲染的图片大小都保持不变,适用于渲染2D场景或者UI元素 透视相机:近大远小,类似人眼 |
物体对象(Mesh) | 包括二维物体(点、线、面)、三维物体、模型等等。 |
光源(Light) | 场景中的光照,如果不添加光照场景将会是一片漆黑,包括全局光、平行光、点光源等。 |
渲染器(Renderer) | 相当于咔擦一下的拍照动作,得到一张静态照片。取值代表渲染方式,如 WebGL 、canvas2D 、css3D 。 |
控制器(Control) | 可通过键盘、鼠标控制相机的移动。 |
视锥体是摄像机可见的空间,超出视锥体界面的部分会倍剪裁掉。
语法:new THREE.PerspectiveCamera(垂直视野角度 , width / height, 1, 1000 );
当远切面过近,导致模型在远切面之外,模型不会出现在画布上。
当近切面过近,导致模型在近切面之外,模型不会出现在画布上。
相机属性
(0,0,0)
(0,1,0)
。up属性必须设置在lookAt之前相机的拍摄方向由机位置和观察物体的点共同构成
// 设置相机的位置
camera.position.set(x,y,z);
camera.position.x = x;
camera.position.y = y;
camera.position.z = z;
// 设置相机观察的点
camera.lookAt(0, 0, 0);
1.创建一个项目
# 创建一个项目
pnpm create vite
pnpm install
pnpm run dev
# 安装依赖
pnpm install three
2.在App.vue
中创建第一个three.js应用
三维物体要渲染在二维屏幕上,首先需要创建一个场景来放置物体,然后将相机放在场景中某个位置进行拍摄,最终
由渲染器将拍摄内容渲染出来
import * as THREE from "three";
// 1.创建场景
const scene = new THREE.Scene();
// 2.创建透视相机 有几种不同的相机,案例使用透视摄像机
const camera = new THREE.PerspectiveCamera(
45, // 视野角度 单位是角度
window.innerWidth / window.innerHeight, // 相机宽高比 拍出图片的宽高比?涉及到图片压缩
// 当物体某些部分比摄像机的远截面远或者比近截面近的时候,该部分将不会被渲染到场景中。
0.1, // 近截面
1000 // 远截面
);
// 设置相机位置 Z轴正方向从屏幕中穿出来
camera.position.z = 5;
camera.lookAt(0, 0, 0); // 默认值相机看向原点
// 3.初始化渲染器,由渲染器将图片渲染在画布上
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染的尺寸大小
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 设置颜色 16进制
// 创建网格
const cube = new THREE.Mesh(geometry, material);
// 将网格添加到场景中
scene.add(cube);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);
// 渲染
// renderer.render(scene, camera);
// 设置渲染函数(可以将其称为渲染循环),方便观察
const animete = () => {
requestAnimationFrame(animete);
// 周期性旋转,每次旋转0.01弧度
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// 使用渲染器,通过相机将场景渲染进来
renderer.render(scene, camera);
};
animete();
treejs
渲染输出的结果就是一个Cavnas
画布,renderer.domElement
属性获取该画布。
// 初始化渲染器,由渲染器将图片渲染在画布上
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小,全屏渲染
renderer.setSize(window.innerWidth, window.innerHeight);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);
renderer.render(scene, camera);
全屏布局注意CSS设置
<style>
body{
overflow: hidden; // 不使用滚动条
margin: 0px;
}
</style>
canvas画布宽高度动态变化,需要更新相机和渲染的参数,否则无法正常渲染。
如果canvas宽高比发生变化
1.更新相机的宽高比camera.aspect
,该参数与canvas画布高宽相关(初始化时设置为window.innerWidth / window.innerHeight)
2.如果相机的属性发生变化,需要执行updateProjectionMatrix ()
方法更新相机的投影矩阵。
渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix,但是不会每渲染一帧,都通过相机的属性计算投影矩阵(节约计算资源)。所以如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵。
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
// 重置渲染器输出画布canvas尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 更新相机的投影矩阵
camera.updateProjectionMatrix();
};
坐标辅助器new THREE.AxesHelper( 坐标轴线的长度)
红色(R
)代表 X
轴. 绿色(G
)代表 Y
轴. 蓝色(B
)代表 Z
轴。
const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );
// 渲染循环函数
const animete = () => {
requestAnimationFrame(animete);
// 周期性旋转,每次旋转0.01弧度
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// 使用渲染器,通过相机将场景渲染进来
renderer.render(scene, camera);
};
animete();
语法: Clock( autoStart : Boolean )
.getDelta()
时自动开启时钟,默认值是 true
。类实例的方法与属性
属性/方法 | 描述 |
---|---|
autoStart = boolean | 默认为true,是否在第一次调用getDelta 时开启时钟 |
startTime = Float | 默认为0,时钟最后一次调用start 方法的时间,记录开启时钟的时间。 |
oldTime = Float | 默认为0,记录上一次时间(老时间) |
start () | 启动时钟 ① startTime 和 oldTime 设置为当前时间② elapsedTime 设置为 0③ running 设置为true |
getDelta () | 获取自 oldTime 设置后到当前的秒数① oldTime 设置为当前时间② autoStart=true 且时钟并未运行,则该方法同时启动时钟(可能是调用start 方法?) |
需求:需要求解两次渲染的时间差
import * as THREE from "three";
const clock = new THREE.Clock
function render(){
// 获取两次函数调用的时间差,返回值是秒
const spt = clock.getDelta()*1000;
console.log('两次渲染时间间隔(毫秒)',spt);
mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
renderer.render(scene, camera);
requestAnimationFrame(render);//请求再次执行渲染函数render,渲染下一帧
}
render();
语法:new OrbitControls(相机, 渲染画布的dom对象)
renderer.domElement
获取画布。本质是通过改变相机的属性,从而改变拍照的结果
比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围。
相机空间轨道控制器的使用
给控制器添加change
事件,每当控制器修改相机参数就会触发change
事件回调。
如果设置了渲染循环,相机控件OrbitControls
就不用设置change
监听执行renderer.render(scene, camera);
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener('change',()=>{
renderer.render(scene, camera);
})
相机控件 OrbitControls
会导致相机lookAt
失效,需要手动设置OrbitControls
的目标参数
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 相机控件.target属性在OrbitControls.js内部表示相机目标观察点,默认0,0,0
// console.log('controls.target', controls.target);
controls.target.set(1000, 0, 1000);
controls.update();//update()函数内会执行
使用Light
模拟光照对网格模型mesh
(物体表面)的影响,如果使用受光照影响的材质,在不开灯的情况下是看不见的。
MeshBasicMaterial
MeshLambertMaterial
MeshPhongMaterial
MeshPhysicalMaterial
MeshStandarMaterial
如果希望光源照在模型的外表面,就需要将光源放在模型的外面
点光源构造器:PointLight(color:Intege, intensity:Float, distance:Number, decay:Float)
0xffffff
白色创建一个点光源
const pointLight = new THREE.PointLight( 0xffffff, 1, 0 );
scene.add( light ); // 添加到场景中
构造器传参本质是修改点光源实例的对应属性
pointLight.color = 0xffffff
pointLight.intensity = 1
pointLight.distance = 100
/*
等价于
pointLight.position.x ,pointLight.position.y ,pointLight.position.z
*/
pointLight.position.set(x,y,z)
// 设置光源是否可见
pointLight.visible:boolean
PointLightHelper
点光源辅助观察对象可以可视化点光源
语法:PointLightHelper(light:PointLight,sphereSize:Float,color:Hex)
参数:
const pointLight = new THREE.PointLight( 0xffffff, 1, 0 );
const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
scene.add(pointLightHelper);
语法:DirectionalLight( color : Color, intensity : Float )
平行光的方向是一个矢量,平行光的属性如下:
directionalLight..isDirectionalLight
只读,用于检查对象的类型是否为 DirectionalLight
。directionalLight.position = Vector3
设置光源的起始位置directionalLight.target = Object3D
灯光从它的位置(position)指向目标位置,默认的目标位置为(0, 0, 0)
。
// 平行光
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);
平行光照射到网格模型Mesh表面,光线和模型表面(每个平面)构成一个入射角度,入射角度不同,对光照的反射能力不同。
反射的光线越多,光越亮,反射的光线越少光越暗。
DirectionalLightHelper
平行光辅助观察对象可以可视化平行光
语法:DirectionalLightHelper( light : DirectionalLight, size : Number, color : Hex )
\
// DirectionalLightHelper:可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5,0xff0000);
scene.add(dirLightHelper);
环境光AmbientLight
没有特定方向,整体改变场景的光照,会均匀的照亮场景中的所有物体。因为没有方向,所以不能用来投射阴影。
语法:AmbientLight( color : Color, intensity : Float )
//环境光:没有特定方向,整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);
three.js每执行WebGL渲染器.render()
方法一次,就在canvas
画布上得到一帧图像,不停地周期性执行.render()
方法就可以更新canvs画布内容。
通过stats.js库可以查看three.js当前的渲染性能,具体说就是计算three.js每秒钟完成的渲染次数。一般渲染达到每秒钟60次为最佳状态。
使用 Stats 需要做以下几步操作
stats.setMode(0)
。(0: fps, 1: ms, 2: mb)
0是默认模式,显示渲染帧数(一秒渲染次数),1显示渲染周期(渲染一帧需要多长时间ms)stats.update()
在使用 npm install three
下载的依赖包中已经包含了 Stats.js
了
// 1. 引入性能监视器`stats.js`
import Stats from 'three/addons/libs/stats.module.js';
// 1.创建stats对象
const stats = new Stats();
// 2.设置监视器面板,传入面板id(0: fps, 1: ms, 2: mb)
stats.setMode(0)
// 3.设置监视器位置 可以通过style修改样式
stats.domElement.style.position = 'absolute'
stats.domElement.style.left = '0px'
stats.domElement.style.top = '0px'
// 4.stats.domElement:web页面上输出计算结果,一个div元素,
document.body.appendChild(stats.domElement);
// 渲染函数
function render() {
// 5. 调用update刷新帧率
stats.update();
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
}
render();