龙年贴花——打造属于你的涂鸦之龙

525 阅读5分钟

前言

辞旧迎新,鞭炮阵阵。不知不觉中又过完了一年,我们即将迎来龙年,在这里先给大家拜个早年了!㊗️大家:

  • 在新的一年里,愿您的代码写得更加优雅,程序运行更加顺畅,生活也如同一段完美的代码,没有bug。
  • 2024年已经启动,愿您在新的计算年代里,事业能够实现高性能,幸福值达到最大。
  • 代码如龙,优化如风,祝您在新的一年里编织出更多精彩的程序,实现无限可能。
  • 新年伊始,让我们一起来解决新的问题,开发新的功能,共同构建更加智能的未来。
  • 愿您的网络连接畅通无阻,数据存储永不溢出,新年如同高速光纤一样快速而稳定。
  • 祝您的生活像源代码一样清晰易读,bug越来越少,功能升级越来越多。
  • 2024年,让我们一同迎接更高版本的自己,程序人生更上一层楼。
  • 祝您在新的一年里,操作系统稳如老石,软件升级如春风,硬件配置更上一层楼。
  • 愿您的密码坚如磐石,网络防火墙牢不可破,新年安全无虞。
  • 在这个数据时代,祝您的新年充满无限的数据流,生活充满精彩的算法。

当然,以上祝福语来自于ChatGPT,这也符合咱们程序员的一贯作风😁。

咳咳,好像有点跑题,咱们今天来整点活。龙年,让我想起来了计算机图形学界也有一条龙,它就是大名鼎鼎的斯坦福龙(Stanford Dragon),就是下面这条啦。这条龙竟然被用来当做各类渲染例子的模特。

而我们今天的主角就是这条龙没错啦!我们今天就在这条龙上涂鸦,打造一条属于我们自己的龙。

image-20240125161911443

编码实现及思路介绍

我们今天采用的技术栈当然就是使用THREEJS啦~ THREEJS为我们提供了非常便利的贴花功能来实现。大家可以直接参考THREEJS提供的官方Demo three.js examples (threejs.org) 和代码three.js/examples/we…

而我们今天做的就是对其进行小小的调整就可以啦~

官方的demo可以说是非常的详细了。贴花这项技术在官方demo中被称为“Decals”,其原理今天就过多赘述,后续再出一篇文章专门来讲decals背后的原理,今天我们就着重于使用即可。

不过在这之前我们还是对代码逻辑稍作梳理。

逻辑梳理

纵观整个代码,还是比较简单。主要分为以下几块:

  1. 加载模型

    1. 加载模型文件本身
    2. 加载贴花贴图
    3. 加载天空盒背景图
  2. 搭建场景

    1. 设置相机
    2. 设置光照
  3. 射线检测

    1. 当鼠标在场景中移动时,检测是否与模型相交
  4. 添加贴花到场景中

    1. 如果鼠标点击时与模型相交,就在此处加一个贴花
  5. 执行主循环渲染

我们要做的主要就是增加贴花的纹理数量,还有就是对写一个自定义的材质让贴花看起来更加的明显。

增加纹理数量这个很简单,我们只需要使用一个数组来保存就好啦~比如:

 const decalTextureUrls = [
     withBase('decals/bianpao.png'),
     withBase('decals/denglong.png'),
     withBase('decals/dragon.png'),
     withBase('decals/fu-cute.png'),
     withBase('decals/fu.png'),
     withBase('decals/qianbi.png'),
     withBase('decals/qiandai.png'),
     withBase('decals/yuanbao.png'),
 ];
 const decalTextures = decalTextureUrls.map(url => {
     return textureLoader.load(url);
 });

接下来我们需要声明一个自定义Shader的材质来让贴花的渲染更加的明显!!!

 function initDecalMaterial(): THREE.Material {
     const customMaterial = new THREE.ShaderMaterial({
         vertexShader: plainVert,
         fragmentShader: plainFrag,
         depthTest: true,
         depthWrite: false,
         uniforms: {
             mainTex: {
                 value: decalTextures[0],
             },
         },
         polygonOffset: true,
         polygonOffsetFactor: -4,
         wireframe: false,
         blending: THREE.CustomBlending,
         blendSrc: THREE.SrcAlphaFactor,
         blendDst: THREE.OneMinusSrcAlphaFactor,
     });
     return customMaterial;
 }

我们需要注意的是我们需要开启透明度混合,否则PNG图片在模型上透明的部分看起来就是黑黢黢的一片。所以我们要通过设置 blending, blendSrcblendDst 这三个值来保证混合的正确性。

还有另外一点也很重要,就是polygonOffsetpolygonOffsetFactorpolygonOffsetpolygonOffsetFactor 是两个与深度缓冲相关的属性,它们通常用于解决Z-缓冲冲突(Z-fighting)的问题。

  1. polygonOffset

    • polygonOffset 是一个布尔值,默认为 false。当设置为 true 时,它启用多边形偏移。多边形偏移是一种通过微调深度值来解决Z-缓冲冲突的技术。
  2. polygonOffsetFactor

    • polygonOffsetFactor 是一个浮点数,表示在深度值偏移中的比例因子。它控制多边形偏移的具体程度。值越大,偏移越明显。

当启用了 polygonOffset 并设置了合适的 polygonOffsetFactorpolygonOffsetUnits(后者是另一个属性,表示深度值偏移的单位),渲染引擎会根据多边形的深度值进行微小的调整,从而避免Z-缓冲冲突。这对于处理近似平行的表面或者使用透明度的材质时特别有用。

我们上面使用的自定义shader的代码如下:

顶点着色器 plainVert:

 #include <common>
 varying vec2 vUv;
 void main() {
     vUv = uv;
     vec4 mvPosition = vec4(position, 1.0);
     mvPosition = modelViewMatrix * mvPosition;
     gl_Position = projectionMatrix * mvPosition;
 }

片段着色器:

 uniform sampler2D mainTex;
 varying vec2 vUv;
 
 void main () {
     vec4 color = texture(mainTex, vUv);
     gl_FragColor = color;
 }

上面的代码都非常的简单,就不再赘述啦~

最后的最后我们只需要在shoot函数中生成贴花的地方给贴图加入一点随机性,就好啦

 function shoot() {
     position.copy(intersection.point);
     orientation.copy(mouseHelper.rotation);
     if (params.rotate) orientation.z = Math.random() * 2 * Math.PI;
 
     const scale =
         params.minScale +
         Math.random() * (params.maxScale - params.minScale);
     size.set(scale, scale, scale);
 
     const material = customMaterial.clone();
     let index = Math.floor(Math.random() * decalTextures.length);
     (material as THREE.ShaderMaterial).uniforms.mainTex.value =
         decalTextures[index];
     // 省略下面的代码
 }

经过我们的略微改造,我们的最终实现效果如下:

总结

今天我们利用THREEJS的贴花功能和斯坦福龙完成了一个龙年的小小创意。代码很简单,希望这简短的代码能为你的生活带来一抹亮色。

最后的最后,还是要祝各位新年快乐啦~