线框转换效果
条评论示例效果
这是3D可视化教程系列的文章,如果第一次阅读请先阅读《3D可视化教程导读》,这里展示简单的模型拆解效果,可访问d-wireframe-transfer-effect 展示网址:
源码及3D项目文件
源码及工程项目都放到github上。
源码:threejs-example。
3D文件是直接使用three.js官方例子里的模型(跑动的马)。
原理
根据Youtube - Daily Blender Secrets - Wireframe Effect效果,跟着使用Blender实现了一下,本质就是多制作一个线框模型,再配合使用boolean modify来实现控制,特定时候原模型会被遮挡,线框模型会被显示。
搜索了一下,three.js并没有相类似的boolean效果,本来想通过shader来实现,尝& 试一下发现并不太行(本身对shader开发也不会),但发现可以使用深度函数来实现。three.js相关教程会很少提及到这玩意,而在WebGL基础知识学习时,这是必然会提到的内容。在二维平面里绘制三维物体,必然会出现有些在前面的东西遮挡了后面的东西,那么渲染器会根据深度信息来判断,只有在前面的东西才会进行渲染,而被遮挡的物体(体现在要渲染的东西更深)则无需再渲染了,否则就出问题了(即本来被遮挡的东西被渲染到前面去了)。具体可参考WebGL教程:WebGL 三维正射投影
在深度判断上,默认是深度越小的代表越在前面,只会渲染前面的物体。但有时会有特殊的需求(为了追求各种视觉效果就会有各种特别的需求),比如说可以关闭深度检测,物体不管是否被遮挡都会被渲染。也可以反过来,选择深度大的渲染,即被遮挡的反而渲染出来。three.js里的depthFunc有几个值:NeverDepth、AlwaysDepth、LessDepth、LessEqualDepth、EqualDepth、GreaterEqualDepth、GreaterDepth、NotEqualDepth
分别对应WebGL里的gl.NEVER、gl.ALWAYS、...
1 | switch ( depthFunc ) { |
最终核心代码,本质就是做两个模型,一个是原模型,另一个是线框模型,特别地还放一个透明的遮挡模型,当遮挡模型移动时,原模型被档住不渲染(默认的),而线框模型被遮挡后反而需要渲染,所以修改其depthFunc为GreaterDepth
。另外还要提一下,深度是取决于物体与摄像头之间的距离,所以移动摄像头到特定角度时会出现其它效果,本质就是深度值变化了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37const loader = new GLTFLoader();
loader.load( "./static/3d/Horse.glb", function ( gltf ) {
const horse = gltf.scene.children[0]
horse.scale.set(0.01,0.01,0.01)
const horse2 = horse.clone()
horse2.material = horse.material.clone()
horse.material.depthWrite = false
horse.material.transparent = true
// horse2.position.set(1,0,0)
horse2.scale.set(0.0099,0.0099,0.0099)
horse2.material.depthFunc = THREE.GreaterDepth;
horse2.material.wireframe = true
horse2.material.transparent = true
scene.add( horse );
scene.add( horse2 );
// 利用不同的深度计算方法来实现类型于blender里的boolean效果,但并非所有角度都可行。
const cubeGeometry = new THREE.BoxGeometry( 3, 6, 2 );
const maskMaterial = new THREE.MeshBasicMaterial({color:0x000000})
maskMaterial.opacity = 0
// maskMaterial.opacity = 0.2
const maskCube = new THREE.Mesh( cubeGeometry,maskMaterial );
maskCube.position.set(0,0,5);
scene.add( maskCube );
setInterval(() => {
maskCube.position.z -= 0.1
if(maskCube.position.z<-3){
maskCube.position.z = 5
}
}, 50);
} );