用 raymarching 画一个精灵球

1,943 阅读2分钟

我正在参加 码上掘金体验活动,详情:show出你的创意代码块

前言

大家好,这里是 CSS 兼 WebGL 魔法使——alphardex。本文就让我们来用kokomi.jsraymarching组件来画一个精灵球吧~

泼给莫,给托打贼!

绘画开始

首先fork下这个模板

codesandbox.io/s/kokomi-js…

看到“从这开始吧~”的代码块,我们将从那里开始

材质

精灵球有3种颜色:黑色、白色和红色

首先定义好3种颜色的材质

// 材质
const BLACK_MAT = "1.0";
const WHITE_MAT = "2.0";
const RED_MAT = "3.0";

const mat = new marcher.SDFMaterial();
mat.addColorMaterial(BLACK_MAT, 0, 0, 0);
mat.addColorMaterial(WHITE_MAT, 255, 255, 255);
mat.addColorMaterial(RED_MAT, 255, 0, 0);
mar.setMaterial(mat);

球内

精灵球的内部是一个小黑球

const sphere = new marcher.SphereSDF({
  sdfVarName: "d1",
  materialId: BLACK_MAT,
});
layer.addPrimitive(sphere);

LK4vIP.png

按钮

中间有个白色的按钮,其实就是个圆柱体

const button = new marcher.CylinderSDF({
  sdfVarName: "d2",
  materialId: WHITE_MAT,
  radius: 0.1,
  height: 0.54,
});
button.rotate(90, "x");
layer.addPrimitive(button);

LK5GIx.png

上下球壳

raymarching里只能创造整个球体,那如果只想要球体的一半呢?

创建一个临时用的方块,将其移动到球的半侧,再和球体求交集,就能获取一半的球体了

// 球壳(上)
const shellUpper = new marcher.SphereSDF({
  sdfVarName: "d3",
  materialId: "3",
  radius: 0.55,
});

const clipBoxUpper = new marcher.BoxSDF({
  sdfVarName: "d4",
  width: 0.55,
  height: 0.55,
  depth: 0.55,
});
clipBoxUpper.hide();
clipBoxUpper.translate(0, -0.6, 0);

layer.addPrimitive(clipBoxUpper);
layer.addPrimitive(shellUpper);

shellUpper.intersect(clipBoxUpper);

// 球壳(下)
const shellLower = new marcher.SphereSDF({
  sdfVarName: "d6",
  materialId: WHITE_MAT,
  radius: 0.55,
});

const clipBoxLower = new marcher.BoxSDF({
  sdfVarName: "d5",
  width: 0.55,
  height: 0.55,
  depth: 0.55,
});
clipBoxLower.hide();
clipBoxLower.translate(0, 0.6, 0);

layer.addPrimitive(clipBoxLower);
layer.addPrimitive(shellLower);

shellLower.intersect(clipBoxLower);

LKI0pT.png

这里法线中间的按钮还是被球壳给挡住了,我们就要把挡住的部分给挖空

中间镂空

创建2个圆柱体(类似之前的按钮),半径和高度稍微调大点,用它们分别对上下的球壳进行扣除操作就能起到挖空中间的效果

// 球壳(上):挖除中间镂空部分后
const clipCylinderCenter1 = new marcher.CylinderSDF({
  sdfVarName: "d7",
  radius: 0.15,
  height: 0.6,
  materialId: RED_MAT,
});
clipCylinderCenter1.rotate(90, "x");

layer.addPrimitive(clipCylinderCenter1);

clipCylinderCenter1.subtract(shellUpper);

shellUpper.hide();

// 球壳下:挖除中间镂空部分后
const clipCylinderCenter2 = new marcher.CylinderSDF({
  sdfVarName: "d8",
  radius: 0.15,
  height: 0.6,
  materialId: WHITE_MAT,
});
clipCylinderCenter2.rotate(90, "x");

layer.addPrimitive(clipCylinderCenter2);

clipCylinderCenter2.subtract(shellLower);

shellLower.hide();

LKoECV.png

美化

这里可以自由发挥,不论是光照、相机视角还是材质等要素都可以优化

笔者优化后的结果如下

LKobMF.gif

项目地址

code.juejin.cn/pen/7085983…