超详细——手把手教你用threejs实现一个酷炫的模型发光扫描效果(二)

上一篇内容

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方法,我们通过此方法,将模型材质转换为线条材质,覆盖到原模型上,这里具体做了如下几个操作:

  1. 边缘几何体**(EdgesGeometry)**:根据汽车模型构建其边缘几何体,这个方法一般是作为一个辅助对象来查看geometry的边缘。

  2. 基础线条材质(LineBasicMaterial):一种用于绘制线框样式几何体的材质。

  3. 线段(LineSegments):在若干对的顶点之间绘制的一系列的线。

由于汽车由许多mesh组成,因此需要将所有的mesh都转换为EdgesGeometry材质,组合成LineSegments的网格对象,将所有新生成的网格对象放入一个group中,在场景中加入,就可以得到带有边框的汽车模型了。

ShaderMaterial生成扫描特效

接下来我们要实现一个从上到下扫描汽车模型的一个矩形线条效果,这里的实现思路为:生成一个模型的包围盒,通过**着色器材质(ShaderMaterial)**修改包围盒的材质,让其高度根据时间动态显示一部分,其余部分调整为透明,就可以实现上图的效果了,我们一步一步来实现一下吧。

  1. 生成包围盒

生成包围盒我们有两种思路可以实现。

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要用Pytorch手写一个Transformer模型,可以按照以下步骤进行: 1. 导入所需的库和模块,包括torch、torch.nn、torch.nn.functional以及Transformer模型所需的子模块如EncoderLayer和DecoderLayer。 2. 定义Transformer模型的编码器部分。编码器由多个EncoderLayer组成,每个EncoderLayer包含自注意力机制(Self-Attention)、前馈神经网络和残差连接。 3. 定义Transformer模型的解码器部分。解码器也由多个DecoderLayer组成,每个DecoderLayer包含自注意力机制、编码器-解码器注意力机制和前馈神经网络。 4. 定义Transformer模型本身。它包含编码器和解码器,以及最后的线性层用于生成输出。 5. 实现模型的前向传播函数。在前向传播函数中,输入数据将分别经过编码器和解码器,并返回最后的输出。 6. 初始化模型并定义损失函数和优化器。 7. 定义训练循环。在每个训练迭代中,将输入数据传递给模型进行前向传播,计算损失值,并进行反向传播和参数更新。 8. 进行模型训练。根据实际情况,可以调整参数、训练数据和训练次数等。 请注意,以上步骤是一个大致的框架,具体的实现细节可能会有所不同。可以参考引用中提到的huggingface提供的transformer模型代码,以及Transformer模型的论文《Attention is All You Need》来进行更详细实现。 huggingface官方文档: [link] Transformer模型图: [link]<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [手把手教你用Pytorch代码实现Transformer模型详细的代码解读)](https://blog.csdn.net/qq_43827595/article/details/120394042)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值