在上一篇文章《webGPU高效渲染任意形状草地》中,我们介绍了如何用一张自定义颜色贴图来生成任何形状的草地;使用类似的技巧再结合blender建模工具,我们还可以生成河床洼地等任意形状的流水地貌,再结合rapier这样的物理引擎就可以直接做出一个小小的3D游戏世界。

用颜色贴图生成任意流水地貌

与上次不同的是,这次我们在blender中绘制河床和草地的颜色贴图,blender中的材质绘制模式(Texture Paint)可以很好的完成这项工作,选择合适的笔刷和笔触让地形的边缘有些过渡效果。

新建一个平面模型对象,然后用我们刚才绘制好的颜色贴图对平面做变形处理,生成水床洼地的凹陷地形;这个我们可以借助blender中的geometry Nodes来轻松实现。

Blender 中的 ​​Geometry Nodes(几何节点)​​ 是一个基于节点编辑的建模与程序化生成工具,它允许用户通过连接节点来创建复杂的几何体操作和动画效果,无需编写代码即可实现参数化设计.

可以看到我们用贴图中的蓝色通道对模型进行了形变处理,生成了凹陷地形。

this.terrainColorNode = Fn(([]: any) => {
  const terrainUv = this.terrainUvNode(positionWorld.xz);
  const terrainData = this.terrainDataNode(terrainUv);
  // 地面颜色
  const baseColor = color(this.dirtColorUniform).toVar();
  // 草地颜色
  baseColor.assign(mix(baseColor, this.grassColorUniform, terrainData.g));
  // 流水颜色
  baseColor.assign(
    mix(
      baseColor,
      this.waterSurfaceColorUniform,
      smoothstep(0, 0.3, terrainData.b)
    )
  );
  baseColor.assign(
    mix(
      baseColor,
      this.waterDepthColorUniform,
      smoothstep(0.3, 1, terrainData.b)
    )
  );
  return baseColor.rgb;
});

借助一些TSL技巧我们就可以用颜色通道贴图在three.js中生成流水地形。

生成水面波纹效果

还是利用刚才的颜色通道贴图再加上些shader技巧,我们可以用TSL生成唯美的水面波纹效果,当然这个不是物理真实的效果,只是简单的模拟,但对于我的需求来说已经足够了。

this.waterMaterial.outputNode = Fn(() => {
  const terrainUv = this.terrainUvNode(positionWorld.xz);
  const terrainData = this.terrainDataNode(terrainUv);
  const noise = texture(this.noiseTexture, positionWorld.xz.mul(0.1)).r;
  const ripple = terrainData.b
    .add(time.mul(0.1))
    .mul(10)
    .mod(1)
    .sub(terrainData.b.oneMinus())
    .add(noise);
  ripple.greaterThan(0.01).discard();
  return vec4(vec3(1), 1);
})();

通过对颜色贴图采样添加噪声,我们得到了一个不错的水波动画效果。

水痕和物理引擎

角色和地形的碰撞物理引擎使用的是rapier.js,使用起来也非常方便,该引擎已经内置到了three.js核心库中,大家可以参考官方的案例,这里就不过多的介绍了。

最后为了让场景更丰富些,我加入了之前介绍过的草地和天空背景。

当角色走入河床低洼处,人物的部分身体是处于水面之下的,我们可以通过TSL为人物添加水痕的效果让水面的效果更逼真些。

character.material.colorNode = Fn(([]: any) => {
  const baseColor = color(child.material.color).toVar();
  const waterMix = positionWorld.y
    .remapClamp(
      this.waterThreshold,
      this.waterThreshold.sub(this.waterAmplitude),
      1,
      0
    )
    .mul(positionWorld.y.step(this.waterThreshold))
    .pow(this.waterPower);
  baseColor.assign(mix(baseColor, color("#ffffff"), waterMix));
  return baseColor;
})();

总结

用好颜色通道贴图和blender建模工具可以极大的提高我们3D开发的效率,它们可以方便用来进行场景搭建,关卡设计等任务,还不赶快实践起来!