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

上一篇文章

voidjay,公众号:web前端可视化超详细——手把手教你用threejs实现一个酷炫的模型发光扫描效果(二)

上一篇文章已完成基本效果的实现,本文则完成整个项目的灵魂:发光效果以及模型优化。

unrealBloom实现辉光效果

接下来才是重头戏,前文基本功能已完成,但总有一种买家秀与卖家秀的感觉,接下来我们将通过辉光(UnrealBloom)实现效果美化,将买家秀彻底转变为买家秀。

买家秀

卖家秀

先介绍一下辉光的实现原理:

我们通常的三维场景是使用render渲染器渲染出来的,当我们想给三维场景加一层“滤镜”时,就需要使用后处理(post-processing)技术了,后处理可以给模型增加一个或多个图形效果,例如景深、发光、胶片微粒或是各种类型的抗锯齿等,UnrealBloom就是后处理的一种,UnrealBloom可以说是实现科技风必备的一种效果。

要使用后处理,需要先基于render添加一个效果合成器,在效果合成器中添加基本的渲染通道,也就是原场景和相机的通道,再添加辉光通道(UnrealBloomPass),最后将composer的render方法代替render的render方法就行了。整个流程跟我们PS图片类似,将原图作为底图,基于原图增加一些滤镜效果,最后将P过的图片替代原图进行保存。

由于我们生成的辉光的部分只有模型边缘轮廓及扫描线部分,因此我们需要实现局部辉光效果。局部辉光效果要比全局辉光复杂很多,我一点一点解释,局部辉光核心代码如下:

const bloomParams = {
  exposure: 1,
  bloomThreshold: 0,
  bloomStrength: 1,
  bloomRadius: 0.2,
};

const bloomVertext = `
varying vec2 vUv;
void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;

const bloomFragment = `
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
  gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}
`;

  function renderBloom() {
   // 添加效果合成器
    bloomComposer = new EffectComposer(renderer);
    bloomComposer.renderToScreen = false;
    // 添加基本的渲染通道
    const renderPass = new RenderPass(scene, camera);

    const bloomPass = new UnrealBloomPass(
      new THREE.Vector2(window.innerWidth, window.innerHeight)
    )
    bloomPass.threshold = bloomParams.bloomThreshold;
    bloomPass.strength = bloomParams.bloomStrength;
    bloomPass.radius = bloomParams.bloomRadius;
    bloomComposer.addPass(renderPass);
    // 把通道加入到组合器
    bloomComposer.addPass(bloomPass);
    const finalPass = new ShaderPass(
      new THREE.ShaderMaterial({
        uniforms: {
          baseTexture: { value: null },
          bloomTexture: { value: bloomComposer.renderTarget2.texture },
        },
        vertexShader: bloomVertext,
        fragmentShader: bloomFragment,
        defines: {},
      }),
      'baseTexture'
    );
    finalPass.needsSwap = true;
    // 初始化实际效果合成器
    finalComposer = new EffectComposer(renderer);

    finalComposer.addPass(renderPass);
    finalComposer.addPass(finalPass);
  }

我们需要添加两个效果合成器:bloomComposer及finalComposer,bloomComposer用于生成辉光材质,finalComposer用于渲染整个场景。

为了区分辉光对象和非辉光对象,我们需要改变其图层编号。将需要变为辉光的对象的图层编号设为1,其余默认为0,在原renderEffect方法中加入如下代码,注意带+号的部分:

 // 区分辉光与非辉光层
const ENTIRE_SCENE = 0,
  BLOOM_SCENE = 1;
const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_SCENE);

  function renderEffect(model) {
      let edgeGroup = new THREE.Group();
      model.traverse((obj) => {
          if(obj.name == '立方体') {
              // 将扫描框转为辉光材质
  +        obj.layers.toggle(BLOOM_SCENE);
            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;
          }
        // 设置样式
        else 
        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', //ignored by WebGLRenderer
        linejoin: 'round', //ignored by WebGLRenderer
      });
      const line = new THREE.LineSegments(edges, lineBasematerial);
       // 将外框转为辉光材质
+       line.layers.toggle(BLOOM_SCENE);
      return line;
    }
  }

最后,我们把不需要辉光的部分转为黑色材质,使其辉光效果失效。

// 将材质转为黑色材质
function darkenNonBloomed(obj) {
    if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
      materials[obj.uuid] = obj.material;
      obj.material = darkMaterial;
    }
  }

设置一个还原材质的方法,将转为黑色材质的物体进行还原。

 // 还原材质
  function restoreMaterial(obj) {
    if (materials[obj.uuid]) {
      obj.material = materials[obj.uuid];
      delete materials[obj.uuid];
    }
  }

在render方法中先转换材质,生成辉光效果,然后还原材质,最后渲染整个场景,从而实现部分辉光的效果。

function bloomRender() {
    scene.traverse((obj) => darkenNonBloomed(obj));
    bloomComposer.render();
    scene.traverse((obj) => restoreMaterial(obj));
    finalComposer.render();
    requestAnimationFrame(() => {
        calcHeight()
        bloomRender();
    });
}
init()
// render()
bloomRender()

模型压缩

在web项目中,网页性能十分重要,对模型大小的控制也有着极为严苛的要求,普遍模型大小需小于10M,因此3d模型的压缩就是必须要考虑的了。本项目模型原大小为47.3M,经过压缩后为2.8M,如下图,大小降低了近20倍。

我们使用Draco对模型进行压缩,通过 glTF 配合 Draco 压缩的方式,可以在视觉效果近乎一致的情况下,让3D模型文件成倍缩小。下面具体介绍 glTF 格式及 Draco 压缩工具。

  1. glTF格式介绍

glTF 称为“ 3D 界的 JPEG”,使用了更优的数据结构,为应用程序实时渲染而生。glTF 有以下几大特点:

  1. 由现有 OpenGL 的维护组织 Khronos 推出,目的就是为了统一用于应用程序渲染的 3D 格式,更适用于基于 OpenGL 的引擎;

2. 减少了 3D 格式中除了与渲染无关的冗余信息,最小化 3D 文件资源;

3. 优化了应用程序读取效率和和减少渲染模型的运行时间;

4.支持 3D 模型几何体、材质、动画及场景、摄影机等信息。

glTF 导出格式有两种后缀格式可供选择:.gltf 和 .glb

glTF 文件导出时一般会输出两种文件类型,一是 .bin 文件,以二进制流的方式存储顶点坐标、顶点法线坐标和贴图纹理坐标、贴图信息等模型基本数据信息;二是 .gltf 文件,本质是 json 文件,记录对bin文件中模型顶点基本数据的索引、材质索引等信息,方便编辑,可读性较好;

glb 文件格式只导出一个 .glb 文件,将所有数据都输出为二进制流,通常来说会更小一点,若不关心模型内的具体数据可直接选择此类型。

另外,建议所有3d开发人员都要简单学习一下建模的基本知识!需要学习的建模软件在这里只推荐Blender,原因有几点:

  1. Blender可以导出glTF模型,而很多建模软件是不能导出glTF模型的,项目开发时建模师可能会给你其他格式的模型,此时就需要自己转换一下了。

  2. 建模师给出的模型可能并不适合当前业务,如模型拆分,模型分组命名,模型材质等,都需要微调一下,这时候你自己顺手修改一下模型会节省很多时间。

  3. 不懂建模的3d开发人员就和不懂html的web前端开发人员一样,项目开发时,需要和建模师进行沟通磨合,如果你对建模一窍不通的话,项目就没办法推进下去了。

    2. 模型压缩

Draco 是 Google 推出的一个用于 3D 模型压缩和解压缩的工具库,glTF 资源可通过基于 Draco 开发的命令行工具 gltf-pipeline 进行编码压缩,gltf-pipeline 可通过 npm 的方式安装使用。使用方法如下:

#全局安装
npm install -g gltf-pipeline

#压缩glb文件 -b表示输出glb格式,-d表示压缩
gltf-pipeline -i model.glb -b -d

#压缩glb文件并将纹理图片分离出来
gltf-pipeline -i model.glb -b -d -t

#更多参数查阅
gltf-pipeline -h

通过 Draco 进行压缩基本上是有损的,有两点表现:

- Draco 通过 Edge breaker 3D 压缩算法改变了模型的网格数据的索引方法,缺少了原来的网格顺序;

- Draco 通过减少顶点坐标、顶点纹理坐标等信息的位数,以减少数据的存储量。

本文我们使用命令:gltf-pipeline -i model.glb -b -d,对模型进行压缩,模型大小基本可以满足要求,如果贴图较大,可以分离出纹理图片,对纹理图片进行压缩处理,以更进一步减少模型大小。

  1. 纹理图片压缩

纹理图片压缩采用.basis格式,将 png 转换为 basis 文件后,大小与 jpg 格式差不多,但在 GPU 上比 png/jpg 小6-8倍。

可通过 basisu 命令行工具压缩 png,直接从github 官网下载Release版本或者通过 CMake 编译源码,以 Mac 系统为例(Windows 系统将命令改为 basis.exe),列举几种常用用法:

# 进入执行目录
cd bin-osx

# 将.png格式转为 .basis
./basisu xxx.png

# 针对法线/金属/粗糙贴图等linear颜色空间的贴图 需加上-linear
./basisu xxx.png -linear

# 最大限度保证图片质量的转换
./basisu xxx.png -comp_level 5 -max_endpoints 16128 -max_selectors 16128 -no_selector_rdo

# 最大限度压缩linear颜色空间的贴图
./basisu xxx.png -linear -global_sel_pal -no_hybrid_sel_cb

生成的 .basis 文件需要在程序中通过转码器转成设备的压缩纹理格式,例如在ThreeJS 中可通过 basisTextureLoader 转换,具体用法可查阅ThreeJS 官网。

至此,本项目已基本完成开发,大家有什么意见及问题欢迎在评论区交流!


欢迎关注我的公众号获取webgl资料及最新文章,您也可以添加我的微信进行沟通交流:
公众号:web前端可视化
微信:voidjay

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值