three.js 学习笔记

three.js 学习笔记

openGL是一个跨平台3D/2D的绘图标准,WebGLopenGL在浏览器中的一个实现,默认右手系。

编程人员可以直接用WebGL接口进行编程,但需要一定的学习成本且代码量大。Three.jsWebGL进行了封装,更为轻松地进行Web 3D开发,降低使用门槛的同时,提高了开发效率。

学习资源
three.js中文文档
three.js学习视频
three.js学习文档

基础概念

属性名称描述
场景(Scene)是一个三维空间,所有物品的容器,可以把场景想象成一个空房间,接下来会往房间里放要呈现的物体、相机、光源等。可以配合 chrome 插件使用,抛出 window.scene 即可实时调整 obj 的信息和材质信息。
相机(Camera)必须要往场景中添加一个相机,相机用来确定位置、方向、角度,相机看到的内容就是我们屏幕中看到的内容。

正交相机:无论物体距离相机距离远或者近,最终渲染的图片大小都保持不变,适用于渲染2D场景或者UI元素

透视相机:近大远小,类似人眼
物体对象(Mesh)包括二维物体(点、线、面)、三维物体、模型等等。
光源(Light)场景中的光照,如果不添加光照场景将会是一片漆黑,包括全局光、平行光、点光源等。
渲染器(Renderer)相当于咔擦一下的拍照动作,得到一张静态照片。取值代表渲染方式,如 WebGLcanvas2Dcss3D
控制器(Control)可通过键盘、鼠标控制相机的移动。

three.js的世界并没有任何单位,只有数字大小的运算。

第一个three.js应用

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();

threejs画布尺寸和布局

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画布宽高度动态变化,需要更新相机和渲染的参数,否则无法正常渲染。

如果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();
};

实现动画效果

requestAnimationFrame

requestAnimationFrame学习笔记

// 渲染循环函数
const animete = () => {
  requestAnimationFrame(animete);
  // 周期性旋转,每次旋转0.01弧度
  cube.rotation.x += 0.01; 
  cube.rotation.y += 0.01; 
  // 使用渲染器,通过相机将场景渲染进来
  renderer.render(scene, camera);
};
animete();
时间相关属性和方法 THREE.Clock类

中文文档Clock

语法: Clock( autoStart : Boolean )

  • autoStart 可选,在第一次调用 .getDelta() 时自动开启时钟,默认值是 true

类实例的方法与属性

属性/方法描述
autoStart = boolean默认为true,是否在第一次调用getDelta时开启时钟
startTime = Float默认为0,时钟最后一次调用start方法的时间,记录开启时钟的时间。
oldTime = Float默认为0,记录上一次时间(老时间)
start ()启动时钟
startTimeoldTime设置为当前时间
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();

辅助工具

坐标辅助器 THREE.AxesHelper

语法:new THREE.AxesHelper( 坐标轴线的长度)
说明:红色(R)代表 X 轴. 绿色(G)代表 Y 轴. 蓝色(B)代表 Z 轴。
继承链:Object3D → Line → LineSegments →AxesHelper

const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );

网格地面辅助观察 THREE.GridHelper

语法:new GridHelper( size : number, divisions : Number, colorCenterLine : Color, colorGrid : Color )
说明:创建一个尺寸为size的有divisions个格子的网格地面
参数
1.size整体网格地面(正方体)的大小,默认10
2.divisions 垂直方向与水平方向的格子数量
3.colorCenterLine 中线颜色,默认0x444444
4.colorGrid 坐标格网格线颜色,默认0x888888
继承链:Object3D → Line →GridHelper

网格地面默认是在xoz平面且y=0的位置

// 添加一个辅助网格地面
const gridHelper = new THREE.GridHelper(300,6,0x00ff00,0xff0000);
scene.add( gridHelper  );
// 调整网格地面的位置
gridHelper.position.y = 1;//适当偏移,不与坐标系重合

在这里插入图片描述

相机 camera

  • 正投影相机 OrthographicCamera 平行投影
  • 透视投影相机 PerspectiveCamera 近大远小

透视相机

视锥体是摄像机可见的空间,超出视锥体界面的部分会倍剪裁掉。

语法:new THREE.PerspectiveCamera(垂直视野角度 , width / height, 1, 1000 );

  • 摄影机视锥体垂直视野角度,角度越大可以看到渲染范围更大,远小近大的视觉效果更明显。
  • 相机宽高比:表示输出图像的宽和高之比
  • 摄像机视锥体近切面
  • 摄像机视锥体远切面在这里插入图片描述

当远切面过小与当近切面过大,模型在视锥体之外的情况
在这里插入图片描述

相机基础属性
  • position: 相机的位置,默认(0,0,0)
    设置为范围值就可以全部显示
    方法1:比如工厂尺寸范围大概200米数量级,可设置camera.position=(200,200,200)
    方法2:可以借助OrbitControls可视化旋转或缩放,然后选择一个合适的渲染效果,浏览器控制台记录下此时的相机位置。
  • lookAt:相机看向哪一个点,默认看向Z轴负半轴方向
  • up:相机的朝向,哪一根坐标向上,默认(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); 

在这里插入图片描述

模型在canvas画布居中 - camera.lookAt()

希望模型的哪个位置在正中间,就让camera.lookAt()指向哪个坐标。camera.lookAt()指向canvas画布的正中间

注意点
相机控件OrbitControls会导致lookAt失效,初始化相机控件后,相机控件的.target属性在控件内部表示相机目标观察点(初始化时修改了lookAt的值)。
相机控件.update()时会执行camera.lookAt(controls.target)

camera.lookAt(100, 0, 0);
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 相机控件.target属性在OrbitControls.js内部表示相机目标观察点,默认0,0,0
// console.log('controls.target', controls.target);
controls.target.set(100, 0, 0);
controls.update();//update()函数内会执行camera.lookAt(controls.targe)

相机控件 - 轨道控制器OrbitControls

语法:new OrbitControls(相机, 渲染画布的dom对象)
说明:使相机围绕目标进行轨道运动,本质是通过改变相机的属性,从而改变拍照的结果
1.相机:控制器改变哪一个相机
2.渲染的画布dom对象:监控哪一片区域的事件,可以通过renderer.domElement获取画布。

相机控件OrbitControls辅助设置相机参数

操作本质
旋转:拖动鼠标左键改相机位置属性属性.camera.positon
缩放:滚动鼠标中键修改相机位置属性属性.camera.positon
平移:拖动鼠标右键修改相机目标观察点属性属性.camera.lookAt

可以通过相机控件旋转或缩放三维场景,同时通过浏览器控制台观察相机位置变化。

使用场景:可以先随便设置相机位置,然后借助 OrbitControls,在render的时候打印浏览器控制台,选择一个合适的位置。

function render() {
  requestAnimationFrame(render);
  // 浏览器控制台查看相机位置变化
  console.log('camera.position',camera.position);
  // 浏览器控制台查看controls.target变化,辅助设置lookAt参数
  console.log('controls.target',controls.target);
}
render();
camera的lookAt函数与OrbitControls的target冲突

语法:new OrbitControls(相机, 渲染画布的dom对象)

相机控件的.target属性在控件内部表示相机目标观察点

由于在相机控件update更新时会修改cameralookAt,所以有OrbitControls的情况下,设置相机的lookAt后又会被重置为orbitControls.target

class OrbitControls extends EventDispatcher {
	// object = camera
	constructor( object, domElement ) {
		let scope = this;
		this.object = object;
		this.domElement = domElement;
		this.domElement.style.touchAction = 'none';

		this.target = new Vector3(); // 相机控件的target
	}
	this.update = function (){
		// .....
		scope.object.lookAt( scope.target );  // 修改相机lookA等于相机控件的target
	}
}
相机空间轨道控制器的使用

给控制器添加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);
})

场景 scene

background与environment

  • background:Object在渲染场景时设置为背景(最先渲染),
  • environment : Texture该纹理贴图会被设为场景中所有物理材质的环境贴图,不能覆盖已存在的、已分配给MeshStandardMaterial.envMap 的贴图。

THREE.Fog 线性雾 - THREE.FogExp2 指数雾

Fog雾
语法:new THREE.Fog( color : Integer, near : Float, far : Float )
描述:模拟真实世界雾的效果,让场景中模型在原理相机的地方逐渐变得模糊,雾的程度线性成长。
参数
color:表示雾的颜色,是一个整数值
near:表示开始应用雾的最小距离,默认为1
far:表示应用雾的最大距离,默认为1000

scene.fog = new THREE.Fog( 0xcccccc, 1, 1000 );

FogExp2指数雾
语法:new THREE.FogExp2( color : Integer, density : Float )
描述:距离相机越远,雾的浓度随着指数增长越快,雾的程度指数成长。
参数
color:表示雾的颜色
density 定义雾的密度将会增长多块,默认值是0.00025.

scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );

模型 = 几何体 + 材质

模型笔记链接

外部三维模型

还有其他很多格式的外部文件,本章以GLIF为例子

ThreeJs仅提供了常见几何体,复杂的模型一般通过3D建模软件实现。比如使用Blender三维建模软件导出gltf格式模型,然后再通过threejs加载三维模型。

GLTF格式是一个JSON(Web3D领域JPG)

1..gltf格式文件(json格式)几乎可以包含所有的三维模型相关信息的数据,比如模型层级关系、PBR材质、纹理贴图、骨骼动画、变形动画。
2.有些GlTF文件会关联一个或多个.bin文件,.bin文件以二进制形式存储了模型的顶点数据等信息。 .bin文件中的信息其实就是对应GItf文件中的buffers属性。
3..glb就是gltf格式的二进制文件,比如可以把.gltf模型和贴图信息全部合成得到一个.glb。文件中体积更小,网络传输自然更快。

{
  "asset": {
    "version": "2.0",
  },
...
// 模型材质信息
  "materials": [
    {
      "pbrMetallicRoughness": {//PBR材质
        "baseColorFactor": [1,1,0,1],
        "metallicFactor": 0.5,
        "roughnessFactor": 1
      }
    }
  ],
  // 网格模型数据
  "meshes": ...
  // 纹理贴图
  "images": [
        {
            // uri指向外部图像文件
            "uri": "贴图名称.png"//图像数据也可以直接存储在.gltf文件中
        }
   ],
     "buffers": [
    // 一个buffer对应一个二进制数据块,可能是顶点位置 、顶点索引等数据
    {
      "byteLength": 840,
      //这里面的顶点数据,有些时候单独以.bin文件的形式存在  
      "uri": "data:application/octet-stream;base64,AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/.......
    }
  ],
}
模型加载全流程

gltf格式模型文件,有不同的组织形式,加载代码无区别

  • 单独的gltf json格式
  • 单独的glb 二进制格式
  • gltf+bin+贴图文件 不随意修改其目录结构 ,以免找不到资源。

1.引入对应格式模型的加载器并创建加载器

引入Three.js扩展库gltf格式模型以及创建加载器new GLTFLoader()

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

// 创建GLTF加载器对象
const loader = new GLTFLoader();

2.gltf加载器方法.load()

load方法类似纹理加载器textureLoaderload方法。
gltf的场景属性gltf.scene包含模型信息,比如几何体BufferGometry、材质Material、网格模型Mesh。

//GLTFLoader加载器的load方法没有返回值,成功的gitf作为第二个参数函数对象的参数
loader.load( 'gltf模型.gltf', function ( gltf ) {
  console.log('控制台查看加载gltf文件返回的对象结构',gltf);
  console.log('gltf对象场景属性',gltf.scene);
  // 返回的场景对象gltf.scene插入到threejs场景中
  scene.add( gltf.scene );
})

threejs解析gltf模型默认材质一般是MeshStandardMaterialMeshPhysicalMaterial,这两个材质属于PBR物理材质,可以提供更加真实的材质效果

纹理贴图颜色偏差问题解决-渲染器的outputColorSpace 属性

问题描述:three.js加载gltf模型的时候,可能会遇到渲染结果颜色偏差
解决办法:修改WebGL渲染器的输出编码方式.outputEncoding = sRGBEncoding (新版本属性名该为outputColorSpace 默认值为SRGBColorSpace

//解决加载gltf格式模型纹理贴图和原图不一样问题
// 新版本中,SRGB颜色控件为默认值
renderer.outputColorSpace = THREE.SRGBColorSpace;
外部模型材质可能存在共享问题

问题描述:改变一个模型颜色其它模型跟着变化,是因为多个模型对象共享了材质。
解决方法:
1.三维建模软件中设置,需要代码改变材质的Mesh不要共享材质,要独享材质。
2.代码批量更改:克隆材质对象,重新赋值给mesh的材质属性

//用代码方式解决mesh共享材质问题
gltf.scene.getObjectByName("小区房子").traverse(function (obj) {
    if (obj.isMesh) {
        // .material.clone()返回一个新材质对象,和原来一样,重新赋值给.material属性
        obj.material = obj.material.clone();
    }
});
加载压缩过的模型 DRACOLoader

独立的Draco文件后缀为.drc,包含顶点坐标,法线,颜色和其他的属性, Draco文件不包含材质,纹理,动画和节点结构。

语法:new DRACOLoader( manager : LoadingManager )
描述:创建一个新的DRACOloader,用于加载经过Draco压缩的模型。
继承链:Loader → DRACOLoader

使用
1.在官方文件找到对模型进行解析所需要的文件夹(解码库所在的文件夹),大概在examples/js/libs/draco/gltf,将这三个文件复制到项目中,一般会复制到public文件夹下

在这里插入图片描述
draco_decoder.js Emscripten 编译的解码器,与任何现代浏览器兼容。
draco_decoder.wasmWebAssembly 解码器,与较新的浏览器和设备兼容。
draco_wasm_wrapper.js WASM 解码器的 JavaScript 包装器。

2.引入DRACOLoader解析器

// 引入DRACOLoader扩展库
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const dracoLoader= new DRACOLoader();

3.使用setDecoderPath指定包含 WASM/JS 解码库的文件夹路径,设置为步骤1复制到的文件夹位置。setDecoderConfig属性可以设置使用哪一种解码器

// 这里设置为步骤1复制到的文件夹位置
dracoLoader.setDecoderPath = 

案例

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath ('./draco/') ; //设置包含JS和WASM解压缩库的文件夹路径
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader); // 设置解码器

//加载模型
gltfLoader.load('/model/scene.glb',(gitf) => {
   const model = gitf.scene;
   scene.add(model);
})

灯光

使用Light模拟光照对网格模型mesh(物体表面)的影响,如果使用受光照影响的材质,在不开灯的情况下是看不见的。

如果希望光源照在模型的外表面,就需要将光源放在模型的外面

点光源

在这里插入图片描述
点光源构造器:PointLight(color:Intege, intensity:Float, distance:Number, decay:Float)

  • color:可选,点光源照射出的光线颜色,十六进制,默认是0xffffff白色
  • intensity:可选,光照的强度,默认1
  • distance:从光源到光照强度为0的位置,也就是说光源可以照射到的最大距离,如果将其设置为0,光永远不会消失(距离无穷大),默认0
  • decay:沿着光照距离的衰退量,默认是2,不想衰退可以设置为1
    想象灯光,越靠近光源,光线越亮。光线会随距离的增而衰弱

创建一个点光源

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点光源辅助观察对象可以可视化点光源

语法:PointLightHelper(light:PointLight,sphereSize:Float,color:Hex)
参数:

  • light 模拟的光源,光源构造器返回值
  • sphereSize 可选,球形辅助对象的尺寸,默认为1
  • color 可选,默认使用光源的颜色
const pointLight = new THREE.PointLight( 0xffffff, 1, 0 );
const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
scene.add(pointLightHelper);

平行光

在这里插入图片描述
语法:DirectionalLight( color : Color, intensity : Float )

  • color 可选,光源颜色,默认白色
  • intensity 可选,光照的强度默认值为 1。

平行光的方向是一个矢量,平行光的属性如下:

  • directionalLight..isDirectionalLight 只读,用于检查对象的类型是否为 DirectionalLight
  • directionalLight.position = Vector3 设置光源的起始位置
  • directionalLight.target = Object3D 灯光从它的位置(position)指向目标位置,默认的目标位置为(0, 0, 0)
    • 对于目标的位置,如果要改为除默认值之外的其他位置,该位置必须被添加到场景(scene)中去。
// 平行光
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表面光线反射规律

平行光照射到网格模型Mesh表面,光线和模型表面(每个平面)构成一个入射角度,入射角度不同,对光照的反射能力不同。

反射的光线越多,光越亮,反射的光线越少光越暗。
在这里插入图片描述

平行光辅助观察 DirectionalLightHelper

DirectionalLightHelper平行光辅助观察对象可以可视化平行光

语法:DirectionalLightHelper( light : DirectionalLight, size : Number, color : Hex )\

  • light 被模拟的平行光
  • size 可选的,平面的尺寸默认为 1
  • color 可选的,如果没有设置颜色将使用光源的颜色
// DirectionalLightHelper:可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5,0xff0000);
scene.add(dirLightHelper);

环境光AmbientLight

环境光AmbientLight没有特定方向,整体改变场景的光照,会均匀的照亮场景中的所有物体。因为没有方向,所以不能用来投射阴影。

语法:AmbientLight( color : Color, intensity : Float )

  • color 可选,光源颜色,默认白色
  • intensity 可选,光照的轻度,默认值为1
//环境光:没有特定方向,整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

stats性能监视器

three.js每执行WebGL渲染器.render()方法一次,就在canvas画布上得到一帧图像,不停地周期性执行.render()方法就可以更新canvs画布内容。

通过stats.js库可以查看three.js当前的渲染性能,具体说就是计算three.js每秒钟完成的渲染次数。一般渲染达到每秒钟60次为最佳状态。

使用 Stats 需要做以下几步操作

  1. 实例化 Stats
  2. 设置初始面板 stats.setMode(0)
    参数是传入面板id(0: fps, 1: ms, 2: mb) 0是默认模式,显示渲染帧数(一秒渲染次数),1显示渲染周期(渲染一帧需要多长时间ms)
  3. 设置监视器的位置
  4. 将监视器添加到页面中
  5. 刷新帧数 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();

场景中的物体

//BoxGeometry:长方体 xyz
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:矩形平面 xy
const geometry = new THREE.PlaneGeometry(100,50);
// CircleGeometry:圆形平面 半径
const geometry = new THREE.CircleGeometry(50);

WebGL渲染器

const renderer = new THREE.WebGLRenderer();
// 设置渲染器输出的画布大小
renderer.setSize(window.innerWidth, window.innerHeight); 
document.body.appendChild(renderer.domElement);
  • 语法:new WebGLRenderer( parameters : Object )
    • antialias: 是否执行抗锯齿,默认为false。

设置设备像素比renderer.setPixelRatio方法(通用设置基本都会设置)
设备像素比:window.devicePixelRatio

// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
// 如果硬件设备像素比window.devicePixelRatio=1,那么是否执行.setPixelRatio()不会有明显差异,不过为了适应不同的硬件设备屏幕,通常需要执行该方法。
renderer.setPixelRatio(window.devicePixelRatio);

设置背景颜色renderer.setClearColor()方法
renderer.setClearColor(0x444444, 1); //设置背景颜色

dat.gui.js库

作用:快速创建控制三维场景的UI交互界面(threejs三维空间很多参数可以借助可视化的方式调试出来)

1.threejs官方案例扩展库中提供了gui.js(本质是一个前端js库),可以直接引入使用

// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

2.创建一个GUI对象 - 页面中会创建一个交互控制器的菜单,默认创建总菜单

// 实例化一个gui对象
const gui = new GUI();

在这里插入图片描述

GUI对象的属性与方法

属性/方法描述
domElement获取gui界面的DOM元素
add(控制对象,对象具体属性,其他参数)创建一个交互控制器,交互界面是一个拖动条,可以在一个区间改变控制对象具体的属性值,参数3参数4表示该属性的拖动范围。
支持链式调用
addColor(控制对象,‘color’)创建改变颜色的交互控制器
addFolder()创建交互控制器的子菜单,GUI控制的属性比较多时,可以使用addFolder()进行分组。子菜单同样具有总菜单的方法和属性,所以子菜单可以嵌套子菜单。

案例

// 1.改变GUI界面默认的style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';

add(控制对象,对象具体属性,其他参数)方法

说明: 创建一个UI交互控制器,交互界面是一个拖动条,可以在一个区间改变控制对象具体的属性值。
参数:前两个参数说明创建的交互控制器控制哪一个对象的哪一个属性。其他参数可以一个或多个,gui会自动根据参数形式,自动生成对应的交互界面。

其他属性描述
参数3、参数4:数字交互控制器是拖动条,参数3参数4表示拖动条的范围
参数3是一个数组交互控制器是下拉菜单
参数3是一个对象交互控制器是下拉菜单,显示参数3对象的key,生效的是该key对应的值(改变对象具体属性的值为参数3该key对应的值)
参数对象的具体属性是一个布尔值(参数2)交互控制器就是一个单选框

返回值:一个对象(这里我称为交互控制器对象)

//创建一个对象,对象属性的值可以被GUI库创建的交互界面改变
const obj = {
    x: 30,
};
gui.add(obj, 'x', 0, 100); // 参数3 参数4 修改范围0-100

使用案例
需要打开循环渲染函数,gui相当于是添加了交互事件,通过交互获取到的值去修改js对应值,修改后需要重新render出来

gui改变threejs光照强度测试

const ambient = new THREE.AmbientLight(0xffffff,0.4)
gui.add(ambient, 'intensity', 0, 2.0); // 随便选范围,通过UI交互选择出一个合适值

gui改变threejs模型位置测试

const mesh = new THREE.Mesh(geometry, material);
gui.add(mesh.position, 'x', 0, 180);
gui.add(mesh.position, 'y', 0, 180);
gui.add(mesh.position, 'z', 0, 180)

在这里插入图片描述
gui控制一个对象是否旋转

const obj = {
    bool: false,
};
gui.add(obj, 'bool').name('旋转动画');
// 点击单选框,控制台打印obj.bool变化
gui.add(obj, 'bool').onChange(function (value) {
    console.log('obj.bool',value);
});

// 渲染循环
function render() {
    // 当gui界面设置obj.bool为true,mesh执行旋转动画
    if (obj.bool) mesh.rotateY(0.01);
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

交互控制器的方法:支持链式调用

  • name() 设置交互控制器显示的名字,默认显示修改的属性
  • step() 设置交互界面每次改变属性值间隔
  • onChange() 控制的属性值改变时的回调函数,参数是改变后的值

每一个交互控制器的名字默认显示修改的属性,所以容易出现重名的情况,比如修改不同光源的强度属性intensity

const gui = new GUI();//创建GUI对象 
gui.add(ambient, 'intensity', 0, 2.0).name('环境光强度');

子菜单

菜单默认展开子菜单/菜单.open(),设置子菜单/菜单.close()关闭菜单展开。

在这里插入图片描述

// 1.创建材质子菜单
const obj = {
    color: 0x00ffff,// 材质颜色
    specular: 0x00ffff,
};
const matFolder = gui.addFolder('材质');
matFolder.close();
matFolder.addColor(obj, 'color').onChange(function(value){
    material.color.set(value);
});
matFolder.addColor(obj, 'specular').onChange(function(value){// 材质高光颜色specular
    material.specular.set(value);
});

// 2.创建环境光子菜单
const ambientFolder = gui.addFolder('环境光');
ambientFolder.add(ambient, 'intensity',0,2); // 环境光强度
const dirFolder = gui.addFolder('平行光');

// 3.创建平行光强度
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);

光线投射技术 THREE.Raycaster

语法:new THREE.Raycaster()
作用:光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。
参数
origin:光线投射的原点向量。
direction:向射线提供方向的方向向量(标准化)

向量归一化(标准化)将向量的方向保持不变,大小归一化到1

鼠标拾取
场景最终被输出在一个画布canvas元素上,所以没办法获取鼠标hover处模型的DOM对象。
在2D中,判断鼠标是否选中的标准是鼠标位置是否在该区域中。在3D场景中,由于屏幕是2D显示的,判断选中的标准是从相机为起点位置穿过鼠标位置的射线是否也穿过模型,如果穿过则认为该模型被选中。
在这里插入图片描述

案例:选中的模型变为红色

canvas与webGL坐标转换

webGL坐标

数学库

三维向量 Vector3

语法:new Vector3( x : Float, y : Float, z : Float )
说明:建一个三维向量Vector3

属性与方法描述
isVector3 : Boolean只读属性,是否是三维向量
x : Float三维向量的x分量
y : Float三维向量的y分量
z : Float三维向量的z分量
set ( x : Float, y : Float, z : Float )设置向量的x,y,z分量,setX ( x : Float )、setY ( y : Float )、setZ ( z : Float ) 单独设置每个分量
克隆.clone()和复制.copy()

three.js中大多数对象都具有这两个方法

语法:.clone () : Vector3
描述:返回一个新的Vector3,其具有和当前这个向量相同的x、y和z。

语法:.copy ( v : Vector3 )
描述:将所传入Vector3的x、y和z属性复制给这一Vector3

欧拉角 Euler

语法:new Euler( x : Float, y : Float, z : Float, order : String )
说明:描述一个旋转变换,按照指定顺序,分别绕该轴正方向旋转一定角度。

属性与方法描述
x:Float用弧度表示x轴旋转量。 默认值是 0。
y:Float用弧度表示y轴旋转量。 默认值是 0。
z:Float用弧度表示z轴旋转量。 默认值是 0。
order表示旋转顺序的字符串,默认为’XYZ’(必须是大写
  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会为您解答关于WebGL three.js的阴影与实现物体动画的问题。首先,让我们来了解一下WebGL three.js是什么。 WebGL three.js是一款基于WebGL的JavaScript 3D库,可以帮助我们快速搭建3D场景和应用。接下来我们来讲解阴影和实现物体动画的方法。 一、阴影 阴影是模拟物体之间的阴影效果,让3D场景更加真实。在three.js中,我们可以通过设置Mesh的castShadow和receiveShadow属性来实现阴影效果。 1. 首先,我们需要在场景中添加光源,例如SpotLight或DirectionalLight。 2. 然后,在需要投射阴影的物体上设置castShadow为true。 3. 最后,在需要接收阴影的物体上设置receiveShadow为true。 代码示例: ```javascript // 添加光源 const light = new THREE.SpotLight(0xffffff); light.position.set(0, 100, 0); light.castShadow = true; scene.add(light); // 添加需要投射阴影的物体 const cube = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10), new THREE.MeshLambertMaterial({ color: 0xff0000 })); cube.castShadow = true; scene.add(cube); // 添加需要接收阴影的物体 const plane = new THREE.Mesh(new THREE.PlaneGeometry(200, 200, 1, 1), new THREE.MeshLambertMaterial({ color: 0xffffff })); plane.receiveShadow = true; plane.rotation.x = -Math.PI / 2; scene.add(plane); ``` 二、物体动画 在three.js中,我们可以通过Tween.js库来实现物体的动画效果。Tween.js是一款JavaScript动画库,可以帮助我们实现非常丰富的动画效果。 1. 首先,我们需要在HTML文件中引入Tween.js库文件。 2. 然后,在需要动画的物体上设置初始状态。 3. 最后,通过Tween.js库来设置物体的目标状态和动画效果,例如缓动动画(ease)或弹跳动画(bounce)。 代码示例: ```javascript // 引入Tween.js库文件 <script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/18.6.4/tween.min.js"></script> // 添加需要动画的物体 const cube = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10), new THREE.MeshLambertMaterial({ color: 0xff0000 })); cube.position.set(0, 0, 0); scene.add(cube); // 设置初始状态 const start = { x: 0, y: 0, z: 0 }; // 设置目标状态 const end = { x: 50, y: 50, z: 50 }; // 设置动画效果 const tween = new TWEEN.Tween(start) .to(end, 1000) .easing(TWEEN.Easing.Quadratic.InOut) .onUpdate(() => { cube.position.set(start.x, start.y, start.z); }) .start(); ``` 以上是关于WebGL three.js阴影与实现物体动画的方法,希望能够对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值