超详细——手把手教你用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

要在 three.js 中实现局部辉光,你可以使用 ShaderMaterial 和自定义的着色器来实现。以下是一个简单的示例代码,展示了如何在 three.js 中实现局部辉光效果: ```javascript // 创建一个具有局部辉光效果的材质 const glowMaterial = new THREE.ShaderMaterial({ uniforms: { glowColor: { value: new THREE.Color(0x00ff00) }, // 辉光颜色 coeficient: { value: 1.0 }, // 辉光强度系数 power: { value: 1.0 } // 辉光强度 }, vertexShader: ` varying vec3 vNormal; void main() { vNormal = normalize(normalMatrix * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 glowColor; uniform float coeficient; uniform float power; varying vec3 vNormal; void main() { float intensity = pow(coeficient - dot(vNormal, vec3(0.0, 0.0, 1.0)), power); gl_FragColor = vec4(glowColor, intensity); } `, side: THREE.FrontSide, blending: THREE.AdditiveBlending, transparent: true }); // 创建一个需要应用局部辉光的物体 const geometry = new THREE.BoxGeometry(1, 1, 1); const mesh = new THREE.Mesh(geometry, glowMaterial); // 将物体添加到场景中 scene.add(mesh); ``` 在上述代码中,我们创建了一个自定义的 ShaderMaterial,其中包含了顶点着色器和片段着色器。顶点着色器用于计算每个顶点的法线向量,片段着色器根据法线向量和辉光参数计算每个片段的颜色和强度。 你可以根据自己的需求调整着色器中的参数和逻辑来实现不同的局部辉光效果。希望这个示例能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值