three.js 使用总结

1,554 阅读4分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

three.js 基础

Three.js 现学现卖 这篇文章会让你对 Three.js 有全面的了解。

实战经验

实时绘制

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

通过 requestAnimationFrame 实现了 Three.js 的渲染帧,只要我们改变场景类物体的属性,渲染帧就能实时绘制出属性变化后的物体。

几何体 Geometry

尺寸和位置

几何体 Geometry 提供了 .scale ( x, y, z ).scale.x = xxx 来实现尺寸的缩放,他们的区别主要在缩放的参照物。

一般来说,调用方法的都是基于几何体的当前的属性,直接更改属性值的基于几何体创建时的原始属性:

  1. .scale() 缩放基于当前已缩放过的尺寸
  2. .scale.x = xxx 缩放基于原始的尺寸

使用 .translate(x, y, z).translate.x === xxx 移动以及``.rotateX().rotate.x` 旋转时也遵循这个规律。

计算尺寸

一旦进行了缩放,物体的尺寸就发生了变化,我们可以通过以下方式获取最新的尺寸信息:

geometry.computeBoundingBox() // 获取几何体的包围盒 
let { y: height, x: width, z: depth } = geometry.boundingBox.getSize()

材质 Material

更改材质颜色

Material 的 color 属性都是 THREE.Color 的实例,它拥有多种改变颜色的方法,推荐如下方式:

material.color.setStyle(newColor)

newColor 可以是: "rgb(250, 0,0)", "rgb(100%, 0%, 0%)", "hsl(0, 100%, 50%)", "#ff0000", "#f00", or "red"

注意:不支持 rgba

透明颜色

首先,Three.js 不支持透明颜色(存疑,待验证)。目前使用的是透明材质+颜色的方式:

material = new this.THREE.MeshLambertMaterial({
  color: 0xff3c60, 
  transparent: true
});
material.color.setStyle(color)
material.opacity = opacity

替换材质

其实更简单就是, 更改 Mesh 的 Material 属性

let cube = new THREE.Mesh(geometry, material)
cube = newMaterial // 更改为新的材质

立方体贴图

let materials = [ 
  rightMaterial, // right
  leftMaterial, // left
  topMaterial, // top
  bottomMaterial, // bottom 
  frontMaterial, // front 
  backMaterial, // back
]
let cube = new THREE.Mesh(geometry, material)

场景等比缩放

一般调整窗口大小时我们用下文的 resize 方法实现

// 创建渲染器
let renderer = new THREE.WebGLRenderer();
let camera = new THREE.PerspectiveCamera(75, width / height, 1, 800)

// 调整大小
function resize (width, height) {
  camera.aspect = width / height
  renderer.setSize(width, height);
}

要想做到物体在场景内占有的比率一定,我们可以使用如下方式:

// 以下为伪代码
const designW = W
const designH = H
const trueW = W1
const trueH = W2

let renderer = new THREE.WebGLRenderer();
let camera = new THREE.PerspectiveCamera(75, designW / designH, 1, 800)

// 绘制物体按照 designW 下对应的尺寸绘制

// 适配到当前场景宽高
camera.aspect = trueW / trueH
renderer.setSize(trueW, trueH);

动画

简单动画

在渲染帧内改变物体的属性就可以形成动画

function animate() {
  requestAnimationFrame(animate);

  // 改变物体的属性
  boxMesh.rotateY(speed)

  renderer.render(scene, camera);
}

Tween.JS 动画

利用 Tween.JS 我们可以轻松实现许多动画,详情参见 Tween.JS 文档

let ani = new TWEEN.Tween({ rotation: totalRotation1 })
  .to({ rotation: totalRotation2 }, 1000)
  .onUpdate(({rotation}) => {
    boxMesh.rotation.y = rotation
  })
  .onComplete(() => {
    console.log('ani complete')
  })
  .start()


function animate() {
  requestAnimationFrame(animate);

  // 改变物体的属性
  ani.update()

  renderer.render(scene, camera);
}

光源种类

光源和阴影

不能产生投影的光源有:环境光(AmbientLight)、半球光(HemisphereLight)

// 渲染器启用阴影
renderer.shadowMap.enabled = true

// 指定哪个光源能产生阴影
spotLight.castShadow = true

// 指定哪个物体能投射阴影,哪个物体能接受阴影
plane.receiveShadow = true
cube.receiveShadow = true

// 更改阴影质量

// 更改渲染器的投影类型,默认值是 THREE.PCFShadowMap
renderer.shadowMap.type = THREE.PCFSoftShadowMap

// 更改光源的阴影质量,默认值是 512
spotLight.shadow.mapSize.width = 1024 
spotLight.shadow.mapSize.height = 1024

canvas 纹理

精灵文字

// 绘制canvas
let { fontSize, fontFamily, fontBold, color } = style
const canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
  
const fontStyle = `${fontBold ? 'bold' : 'normal'} ${fontSize}px ` + fontFamily
ctx.font = fontStyle

const { width } = ctx.measureText(text)

canvas.width = width
canvas.height = fontSize

ctx.font = fontStyle
ctx.fillStyle = color;
ctx.fillText(text, 0, fontSize);

// canvas 材质
let texture = new THREE.CanvasTexture(canvas)

// 生成纹理

渐变色的实现

let context = canvas.getContext( '2d' );

let gradient = context.createLinearGradient( 0, 0, 0, canvas.height);
gradient.addColorStop( 0, c1 );
gradient.addColorStop( 1, c2 );

context.fillStyle = gradient;

context.fillRect( 0, 0, canvas.width, canvas.height );

let canvasTexture = new this.THREE.CanvasTexture( canvas );

let canvasMaterial = new this.THREE.MeshLambertMaterial( { map: canvasTexture } );

3D 模型

加载 3D 模型

默认内置的模型都是 json 形式的,如果要加载其它格式的,需要自行按照官方示例引入对应的 Loader。

模型中心点

模型的中心点可能不在其几何中心,通过如下方式,可以重置中心点并将模型移动到三维坐标 (0,0,0) 处:

geometry.center()

若模型为 Three.Group 则要进行复杂的计算。

模型加载后纹理为纯黑色的处理

loader.load( 'xxx.obj', function ( object ) {
  //需要添加的部分
  object.scene.traverse(function ( child ) {
    if ( child.isMesh ) {
      child.material.emissive = child.material.color;
      child.material.emissiveMap = child.material.map ;
    }
  });
  scene.add( gltf.scene );
})

相关资料:

点击物体

let raycaster = new THREE.Raycaster()
let mouse = new THREE.Vector2()


onClick = (event) => {
  // x, y 为左上点坐标
  this.mouse.x = ((event.clientX - x) / width) * 2 - 1;
  this.mouse.y = -((event.clientY - y) / height) * 2 + 1;

  // update the picking ray with the camera and mouse position
  this.raycaster.setFromCamera( this.mouse, this.camera.camera );

  // calculate objects intersecting the picking ray
  let intersects = this.raycaster.intersectObjects(this.boxGroup.children );
}

参考资料:ThreeJS中的点击与交互——Raycaster的用法

其他细节

  1. 在同一位置绘制多个平行平面时,会出现交错失真现象,可以调整数值,使平面在它的法线方向错开位置。

参考资料