赞
踩
上一篇内容
voidjay,公众号:web前端可视化超详细——手把手教你用threejs实现一个酷炫的模型发光扫描效果(一)
上篇文章搭建了一个基本的3d场景,本篇则主要完成模型效果的开发,包括:模型外边缘线框,扫描线框,通过这两步实现整个项目的核心效果。
模型外边缘线框
这里需要将原模型材质修改为EdgesGeometry,并覆盖到基础模型上,代码如下:
const renderEffect = function(model) { let edgeGroup = new THREE.Group(); model.traverse((obj) => { // 由于汽车由许多mesh组成,因此需要将所有的mesh都转换为EdgesGeometry材质 if(obj.type === 'Mesh') edgeGroup.add(_renderFrameMesh(obj)); }); scene.add(edgeGroup); // 重置变换 function _renderFrameMesh(obj) { const edges = new THREE.EdgesGeometry(obj.geometry); let color = new THREE.Color(0.1, 0.3, 1); var lineBasematerial = new THREE.LineBasicMaterial({ color: color, side: THREE.FrontSide, linecap: 'round', linejoin: 'round', }); const line = new THREE.LineSegments(edges, lineBasematerial); return line; } }
此部分核心方法是_renderFrameMesh方法,我们通过此方法,将模型材质转换为线条材质,覆盖到原模型上,这里具体做了如下几个操作:
边缘几何体**(EdgesGeometry)**:根据汽车模型构建其边缘几何体,这个方法一般是作为一个辅助对象来查看geometry的边缘。
线段(LineSegments):在若干对的顶点之间绘制的一系列的线。
由于汽车由许多mesh组成,因此需要将所有的mesh都转换为EdgesGeometry材质,组合成LineSegments的网格对象,将所有新生成的网格对象放入一个group中,在场景中加入,就可以得到带有边框的汽车模型了。
接下来我们要实现一个从上到下扫描汽车模型的一个矩形线条效果,这里的实现思路为:生成一个模型的包围盒,通过**着色器材质(ShaderMaterial)**修改包围盒的材质,让其高度根据时间动态显示一部分,其余部分调整为透明,就可以实现上图的效果了,我们一步一步来实现一下吧。
生成包围盒我们有两种思路可以实现。
1)通过建模软件添加一个矩形。
2)使用立方缓冲几何体(BoxGeometry)直接在场景中添加外接矩形。
本文使用第一种建模软件Blender添加,后面会说明一下第二种方法的实现思路。
下载并打开Blender,加载模型后如图所示操作添加一个立方体。
将立方体调整到覆盖模型外围的大小,导出为glb格式。
2.修改立方体材质为ShaderMaterial
这里先简单说明一下着色器材质(ShaderMaterial):
shader是一个用GLSL编写的小程序 ,GLSL是用来在OpenGL中编写着色器(顶点着色器vertexShader和片元着色器fragmentShader)程序的语言,在GPU上运行。threejs内置了很多材质,当这些材质无法满足我们的需求的时候,就需要自定义一个ShaderMaterial去实现更多模型效果。顶点着色器和片元着色器是OpenGL的核心,其中顶点着色器控制模型所有顶点位置的绘制,片元着色器则控制所有像素点的颜色,两个着色器相互配合。
GLSL语言与JS没有任何关系,是一门单独的语言,计算机图形学中使用的较多,可直接控制GPU同时对屏幕所有像素进行控制,是threejs进阶必学的一门语言。
我们在renderEffect加入一段修改立方体材质的代码如下:
var scanConfig = { value: 1.0, start: 0, end: 0, during: 3, } function renderEffect(model) { ........ model.traverse((obj) => { // 根据名称判断外接矩形 if(obj.name == '立方体') { let shaderMaterial = new THREE.ShaderMaterial({ transparent: true, side: THREE.DoubleSide, uniforms: { height: scanConfig, uFlowColor: { value: new THREE.Vector4(0.0, 1.0, 1.0, 1.0), }, uModelColor: { value: new THREE.Vector4(0.0, 0.0, 0.0, 0.0), }, }, vertexShader: uperVertext, fragmentShader: uperFragment, }) obj.material = shaderMaterial; let boundingBox = obj.geometry.boundingBox; // 初始化扫描配置,y轴上下需留出一定空间,防止把上下平面扫描出来 scanConfig.start = boundingBox.min.y+0.1 || 0; scanConfig.end = boundingBox.max.y-0.1 || 0; scanConfig.value = scanConfig.start; } ....... }
首先,我们定义一个scanConfig变量用来保存扫描线所有需要使用的数据,包括:value:当前高度,start:起始高度,end:终止高度,during:持续时间,将value传入ShaderMaterial的height变量中。
获取立方体外接矩形boundingBox的坐标,用于初始化scanConfig的参数。还记得我们上面说的**【使用立方缓冲几何体(BoxGeometry)直接在场景中添加外接矩形】**方法吗?借助boundingBox可以获取最小的外接矩形坐标,我们只需要修改boundingBox中x,y,z坐标,将其外扩一定距离,就可以获取到我们想要的包围盒了。
接下来就要完善材质部分,其中顶点着色器(vertexShader)和片元着色器(fragmentShader)代码如下:
const uperVertext = ` varying vec3 vPosition; void main() { vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1 ); } `; const uperFragment = ` varying vec3 vPosition; uniform float height; uniform vec4 uFlowColor; uniform vec4 uModelColor; void main() { //模型的基础颜色 vec4 distColor=uModelColor; // 流动范围当前点z的高度加上流动线的高度 float topY = vPosition.y +0.02; if (height > vPosition.y && height < topY) { // 颜色渐变 distColor = uFlowColor; } gl_FragColor = distColor; }`;
给着色器传入三个变量:扫描光颜色uFlowColor,透明颜色uModelColor,动态height变量。当绘制的片元在height范围内时,修改颜色为扫描光颜色,接下来只需要控制好height变量就可以了。
我们通过calcHeight方法控制每帧高度变化,将其放入render中去逐帧计算当前高度,核心代码如下:
function calcHeight() {
let length = scanConfig.end - scanConfig.start;
// 扫描动态效果实现
scanConfig.value += length / scanConfig.during / 60;
if (scanConfig.value >= scanConfig.end) {
scanConfig.value = scanConfig.start;
}
}
function render() {
renderer.render(scene, camera);
calcHeight()
controls.update()
requestAnimationFrame(render);
}
至此,我们就完成了基本效果的开发,下一章节介绍发光效果的实现,敬请期待哦。
欢迎关注我的公众号获取webgl资料及最新文章,您也可以添加我的微信进行沟通交流:
公众号:web前端可视化
微信:voidjay
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。