# 性能监测
ms 表示每帧渲染时间;fps 表示每秒帧数,也就是每秒渲染次数;
# 模型绘制
默认情况下,只有正面的面片才会被绘制,而如果需要双面绘制,需要如下设置:
if (child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide
}
# 阴影
在 threeJS 中,能形成阴影的光源只有 THREE.DirectionalLight 、THREE.SpotLight、THREE.PointLight、THREE.RECTAreaLight,而相对的,能表现阴影效果的材质只有 THREE.LamberMaterial 与 THREE.PhongMaterial。
开启阴影的步骤:
- 我们需要在初始化时,告诉渲染器渲染阴影:
renderer.shadowMapEnable = true
- 然后,对于光源以及所有要产生阴影的物体调用:
xxx.castShadow = true
- 对于接收阴影的物体调用:
xxx.receiveShadow = true
比如场景中一个平面有一个正方体,想要让聚光灯照射在正方体上,产生的阴影投射在平面上,那么就需要对聚光灯和正方体调用 castShadow = true,对于平面调用 receiveShadow = true。
以上就是产生阴影效果的必要步骤了,不过通常还需要设置光源的阴影相关属性,才能正确显示出阴影效果。
- 对于聚光灯,需要设置 shadowCameraNear、shadowCameraFar、shadowCameraFov 三个值,类比我们在第二章学到的透视投影照相机,只有介于 shadowCameraNear 与 shadowCameraFar 之间的物体将产生阴影,shadowCameraFov 表示张角。
- 对于平行光,需要设置shadowCameraNear、shadowCameraFar、shadowCameraLeft、shadowCameraRight、shadowCameraTop 以及shadowCameraBottom 六个值,相当于正交投影照相机的六个面。同样,只有在这六个面围成的长方体内的物体才会产生阴影效果。
为了看到阴影照相机的位置,通常可以在调试时开启light.shadowCameraVisible = true。阴影效果已经能正常显示了;
# 轴
threejs 中,可以开启辅助轴,x 轴着色为红色,y 轴着色为绿色,z 轴着色为蓝色;开启代码如下:
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
renderer.setClearColorHex(0xEEEEEE)
renderer.setSize(window.innerWidth, window.innerHeight)
const axes = new THREE.AxisHelper(20)
scene.add(exes)
document.getElementById('webgl-output').appendChild(renderer.domElement)
renderer.render(scene, camera)
# 场景对浏览器的自适应
当浏览器缩小放大时,场景也要跟着进行变化,如下代码:
window.addEventListener('resize', onResize, false)
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
}
# Mesh 相关
# name 属性
我们可以在添加 mesh 对象的时候,设置他的 name 属性;可以方便我们直接通过名字来获取场景中的对象:
THREE.Scene.getObjectByName(name)
# translate
translate() 方法可以改变对象的位置,但是该方式设置的不是物体的绝对位置,而是物体相对于当前位置的平移距离。
# copy
可以通过 position.copy 方法来拷贝一份数据;如下代码:
directionalLight.position.copy(sphereLightMesh.position)
# 设置联合材质
这里需要注意的是,对于 MeshBasicMaterial,要把 transparent 属性设置为 true,并且需要指定一个融合模式。如果不将 transparent 指定为 true,会得到一个纯色的物体;因为 Threejs 不会执行任何操作;如果将 transparent 设置为 true,threejs 会去检查 blending 属性以查看 MeshBasicMaterial 材质如何与背景相互作用;这里所说的背景是 MeshDepthMaterial 材质渲染的结果;
blending 是融合属性,该属性决定物体上的材质如何与背景融合。一般的融合模式是 THREE.NormalBlending,在这种模式下只显示材质的上层;THREE.MultiplyBlending 模式会把前景色和背景色相乘,得到想要的结果。
const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMaterial = new THREE.MeshDepthMaterial()
const colorMaterial = new THREE.MeshBasicMaterial({
color: control.color,
transparent: true,
blending: THREE.MultiplyBlending
})
const cube = new THREE.SceneUtils.createMultiMaterialObject(cubeGeometry, [
colorMaterial,
cubeMaterial
])
# 在单个几何体上使用多种材质
如下代码:
const mats = [];
mats.push(new THREE.MeshBasicMaterial({
color: 0x009e60
}));
mats.push(new THREE.MeshBasicMaterial({
color: 0x0051ba
}));
mats.push(new THREE.MeshBasicMaterial({
color: 0xffd500
}));
mats.push(new THREE.MeshBasicMaterial({
color: 0xff5800
}));
mats.push(new THREE.MeshBasicMaterial({
color: 0xC41E3A
}));
mats.push(new THREE.MeshBasicMaterial({
color: 0xffffff
}));
const cybeG = new THREE.BoxGeometry(3, 3, 3)
const cubeG = new THREE.Mesh(cybeG, mats)
scene.add(cubeG)
在 threejs 中,几何体的每一个面都具有一个 materialIndex 属性。该属性指定了该面使用哪一个具体的材质:
cubeGeom.faces.forEach((p, i) => console.log(`face ${i} : ${p.materialIndex}`))
# Scene 相关
# traverse 方法
该方法可以将一个方法作为参数传递;这个传递来的方法将会在每一个子对象上执行。由于 THREE.Scene 对象存储的是对象树,如果子对象本省还有子对象,traverse 方法会在所有的子对象上执行,直到遍历完场景树种所有对象为止;
scene.traverse(obj => {
if (obj instanceof THREE.Mesh) {
console.log(obj)
}
})
# overrideMaterial 属性
使用这个属性可以保证场景中的所有物体都会使用该材质,而不需要在每个 THREE.Mesh 对象上显示地声明他:
const scene = Three.Scene()
scene.overrideMaterial = new THREE.MeshDepthMaterial()
# camera 相关
# lookAt 函数
可以在某个特定的位置设置摄像机。使用该方法还可以让摄像机追随场景中的某个物体。比如追随某个网格对象,代码如下:
camera.lookAt(mesh.position)
# 材质
# 基本属性
- flatShading:该属性控制物体表面发现的生成方式,从而影响光照效果。属性值为 true 时,在两个相邻但不共面的三角形之间,光照会因为生硬过渡而产生棱角;为 false 时则会产生非常平滑的过渡效果;
比如我们设置材质为:
const meshMatrial = new THREE.MeshBasicMaterial({
color: 0x7777ff,
name: 'basic material',
flatShading: true
})
如果设置为 false 那么不会产生棱角,整个四面体应用的话,会看不到边;设置 true,会看到边;
# geometry
我们获取 geometry 的属性的时候,需要通过他的 parameters 属性去获取,而不是直接 geometry.width 去获取:
const planeGeometry = new THREE.PlaneGeometry(20, 20, 4, 4)
console.log(planeGeometry.parameters.width)
# 选择对象
鼠标选择场景中的对象:
const projector = new THREE.Projector()
function onDocumentMouseDown(event) {
let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
vector = vector.unproject(camera);
const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
const intersects = raycaster.intersectObjects([sphere, cylinder, cube]);
if (intersects.length > 0) {
console.log(intersects[0]);
intersects[0].object.material.transparent = true;
intersects[0].object.material.opacity = 0.1;
}
}
上面的操作实现如下步骤:
- 首先,基于屏幕上的点击位置会创建一个 THREE.Vector3 向量;
- 接着,使用 vector.unproject 方法将屏幕上的点击位置转换成 Three.js 场景中的坐标。换句话说,就是将屏幕坐标转换成三维场景中的坐标;
- 然后,创建 THREE.Raycaster。使用 THREE.Raycaster 可以向场景中发射光线;上面代码是从相机的位置向鼠标的点击位置发射光线;
- 最后,我们使用 raycaster.intersectObjects 方法来判断指定的对象中哪些被光线照射到了。
选择的对象会有下面几个比较重要的属性:
- face 和 faceIndex 指的是该网格中被选中的面。
- distance:从摄像机到被点击对象的距离;
- point:表明网格上哪个点被点击了;
- uv:点击位置所对应的 2D 纹理的 uv 值;
# 相机的几种 control
如下图