复刻掘金健康生活的3D宣传图

875 阅读3分钟

image.png

很熟悉吧,经常逛掘金的童鞋们,一定见过这张图咯,今天我们就用前端技术还原出里面部分的3d模型的,首先我们分析一下里面的元素

分析

image.png

除了基础的场景,背景布,灯光,还有一些文字,贴图和各种材质,我们一步一步的做,一个一个元素的往里加

布景

背景布

image.png

颜色使用吸管工具大致取一下,不过会受到灯光的影响

// 创建背景布
function initGround () {
  const geometry = new THREE.PlaneGeometry(80, 80, 1, 1);
  const material = new THREE.MeshLambertMaterial({ 
    color: 0x5abe64,  // 画布颜色
    side: THREE.DoubleSide // 双面可见
  });
  const plane = new THREE.Mesh(geometry, material);
  plane.receiveShadow = true // 是否接受阴影
  return plane
}

const plan = initGround()
plan.rotation.x = -0.5 * Math.PI
scene.add(plan)
第一个元素

image.png

白色材质的一个倒圆角的立方体,制作立方体,首先要有顶点信息,有了顶点之后,再连线,通过线条,进行挤压,最终形成一个立方体,带着这个思路,我们首先要做一下顶点

image.png

需要8个连接点,还有所有的圆角顶点,圆角顶点所使用的是 椭圆曲线 EllipseCurve 四个椭圆曲线顺序链接就可以形成一个闭环的圆角矩形,说干就干

因为大部分都是这种圆角矩形的结构,所以获取圆角矩形的顶点信息写一个公共的方法getpoint


/**
    * Perform an A* Search on a graph given a start and end node.
    * @param {R} radius
    * @param {L} length
    * @param {S} subsection
    * @return {points} points
    */
function getPoint (R, L, S) {
  const curve1 = new THREE.EllipseCurve(
    L, L,            // ax, aY
    R, R,           // xRadius, yRadius
    0, 0.5 * Math.PI,  // aStartAngle, aEndAngle
    false,            // aClockwise
    0              // aRotation
  );
  const curve2 = new THREE.EllipseCurve(
    -L, L,            // ax, aY
    R, R,           // xRadius, yRadius
    0, 0.5 * Math.PI,  // aStartAngle, aEndAngle
    false,            // aClockwise
    0.5 * Math.PI                // aRotation
  );
  const curve3 = new THREE.EllipseCurve(
    -L, -L,            // ax, aY
    R, R,           // xRadius, yRadius
    0, 0.5 * Math.PI,  // aStartAngle, aEndAngle
    false,            // aClockwise
    Math.PI                // aRotation
  );
  const curve4 = new THREE.EllipseCurve(
    L, -L,            // ax, aY
    R, R,           // xRadius, yRadius
    0, 0.5 * Math.PI,  // aStartAngle, aEndAngle
    false,            // aClockwise
    -0.5 * Math.PI                // aRotation
  );

  // 创建组合曲线对象CurvePath
  var CurvePath = new THREE.CurvePath();
  // 把多个线条有顺序的插入到CurvePath中
  CurvePath.curves.push(curve1, curve2, curve3, curve4);
  //分段数S
  var points = CurvePath.getPoints(S);
  return points
}

image.png

最终导出的是一组二维坐标 vector2

将这组二维坐标通过lineloop的api进行线的渲染,看一下效果

var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
// setFromPoints方法从points中提取数据改变几何体的顶点属性vertices
geometry.setFromPoints(points);
console.log(points)
//材质对象
var material = new THREE.LineBasicMaterial({
  color: 0x000000
});
//线条模型对象
var line = new THREE.LineLoop(geometry, material);
scene.add(line); //线条对象添加到场景中

image.png

现在了顶点,形成了线条,那么下面就将顶点使用挤压缓冲几何体(ExtrudeGeometry)进行挤压,形成一个真正的圆角矩形立方体,


/**
    * Perform an A* Search on a graph given a start and end node.
    * @param {points} 顶点信息vector2
    * @param {option} 挤压的参数
    * @param {option.steps} 用于沿着挤出样条的深度细分的点的数量。
    * @param {option.depth} 挤出的形状的深度
    * @param {option.bevelEnabled} 对挤出的形状应用是否斜角
    * @param {option.bevelSize} 斜角与原始形状轮廓之间的延伸距离
    * @param {option.bevelOffset} 斜角的分段层数,默认值为3。
    * @param {option.bevelSegments} 斜角与原始形状轮廓之间的延伸距离
*/
function extrudeGeometry (points, option) {
  const shape = new THREE.Shape();
  // 循环每一个点放在形状
  shape.moveTo(points[0].x, points[0].y);
  for (let i = 1; i < points.length; i++) {
    shape.lineTo(points[i].x, points[i].y);
  }
  // 设置挤压属性
  const extrudeSettings = {
    steps: option.steps || 32, //用于沿着挤出样条的深度细分的点的数量。
    depth: option.depth || 0.2,// 挤出的形状的深度
    bevelEnabled: !!option.bevelEnabled, // bool,对挤出的形状应用是否斜角
    bevelThickness: option.bevelThickness || 0.2, // 设置原始形状上斜角的厚度
    bevelSize: option.bevelSize || 0.1, // 斜角与原始形状轮廓之间的延伸距离
    bevelOffset: option.bevelOffset || 0.05,
    bevelSegments: option.bevelSegments || 32 // 斜角的分段层数,默认值为3。
  };
  // 返回挤压的几何体包含顶点信息等
  const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
  return geometry
}
第一个模型

制作第一个白色的模型,加材质


// 第一个白色
const firstGeo = extrudeGeometry(points, {
  steps: 32,
  depth: 0.2,
  bevelEnabled: true,
  bevelThickness: 0.13,
  bevelSize: 0.1,
  bevelOffset: 0.1,
  bevelSegments: 32
})
// 设置材质
const material = whiteMaterialBool();
// 生成模型
const mesh = new THREE.Mesh(firstGeo, material);
// 调整位置等信息
mesh.position.y = 0.4
mesh.rotation.x = 0.5 * Math.PI
mesh.castShadow = true // 是否产生阴影
mesh.receiveShadow = true // 是否接受阴影
// 将模型加入到场景中
scene.add(mesh);


漫反射(反射插件)

image.png

同样的原理,把再上面的一层绿色的模型加上去,然后再加一层反光面

    <script src="../../three.js-master\examples\js/objects/Reflector.js"></script>
function initReflector() {
  // 反光面
  reflector = new THREE.Reflector(new THREE.PlaneGeometry(20, 20), {
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
    color: 0x81f98d
  });
  reflector.position.y = 1.35;
  reflector.rotation.x = -0.5 * Math.PI
  scene.add(reflector);
}

镜面效果如果在3D软件可以用漫反射直接做出来,代码敲漫反射有点复杂,直接调用插件,镜面上面放一个模型 试试效果

image.png

堆砌元素

先从简单的入手,一层一层往上加,金属的那的外层稍后再加一下,接下来就是文字后面的大的绿色模块

image.png

跟底板的绿色是一样的效果 我把下面效果相同的都先堆出来

image.png

这里的代码就不铺了 就是各种调参数,可以去源码扒拉扒拉

文字的制作

接下来制作文字部分,没引入字体,直接用ai软件画一个svg导出来,找到一款差不多的字体

image.png

导出到svg文件

image.png

接下来将文字导入到场景中

function loadSvg () {
  var loader = new THREE.SVGLoader();
  loader.load(
    "./svg/club.svg",
    function (data) {
      // 将svg挤出高度
      var paths = data.paths;
      for (var i = 0; i < paths.length; i++) {
        var path = paths[i];
        var shapes = path.toShapes(true);
        shapes.forEach((shape) => {
          let extrudeGeo
          extrudeGeo = new THREE.ExtrudeGeometry(shape, {
            bevelEnabled: true,
            depth: 20,
            steps: 32,
            bevelSegments: 32,
            bevelSize: 2,
          });
          extrudeGeo.scale(0.05, 0.05, 0.05);
          var mesh = new THREE.Mesh(extrudeGeo, greenMaterialBool());
          mesh.rotation.x = -Math.PI
          mesh.position.set(-4.2, 6.2, 8.5)
          scene.add(mesh)
        });
      }
    })
}


logo及其他部分

image.png

导入之后尺寸和方向都不合适 稍微调整一下,再加上上面的logo部分,稍微调整一下灯光,找一下角度,就酱

image.png

大致复刻成这样,再有的就是一些耐心,铺设所有的内容,再加上阴影调整细节