likes
comments
collection
share

用Three.js搞个3D金字塔

作者站长头像
站长
· 阅读数 25

1.金字塔

用Three.js搞个3D金字塔

金字塔组成:顶部一个四棱锥体,下面是上窄下宽依次递增的四棱台。

那么需要计算一下依次下来的每个面大小,上面的形状底部作为下面形状的顶面,这样就每层都能看起来连续。

 let top = idx * width;//顶面正方形宽度
            let bottom = (idx + 1) * width;//底面正方形宽度
            let geometry = new THREE.CylinderGeometry(
              isDown ? bottom : top,
              isDown ? top : bottom,
              height,
              4,//四棱台
              4
            );
            
            
           //对应形状的需要放置的高度 
           //this.intervalH = 0.3 形状与形状之间的间隔距离
   let y =
              (isDown ? idx + 1 : list.length - idx) * height -
              (isDown ? -idx : idx) * height * this.intervalH;
          

当顶面宽度为0时即为四灵锥。

当顶面宽底面窄时,即可形成倒金字塔

用Three.js搞个3D金字塔

2.金字塔立体化

可以看到,单纯的一种颜色,让金字塔看起来不太立体,这时候需要给每个面设置同色系的相近颜色,通过每个面的颜色差来达到这种效果。

四棱台有六个面,四棱椎有五个面,那么就取五颜色值,侧面的四个面分别一个颜色,顶面和底面一个颜色。

/**
 * 获取暗色向渐变颜色
 * @param {string} color 基础颜色
 * @param {number} step  数量
 * @returns {array} list 颜色数组
 */
export function getShadowColor(color, step) {
  let c = getColor(color);
  let { red, blue, green } = c;
  console.log('%ccolor', `background:${color}`);
  const s = 0.8;
  const r = parseInt(red * s),
    g = parseInt(green * s),
    b = parseInt(blue * s);
  console.log('%cshadow', `background:rgb(${r},${g}, ${b})`);
  const rr = (r - red) / step,
    gg = (g - green) / step,
    bb = (b - blue) / step;

  let list = [];
  for (let i = 0; i < step; i++) {
    list.push(
      `rgb(${parseInt(red + i * rr)},${parseInt(green + i * gg)},${parseInt(blue + i * bb)})`
    );
  }
  return list;
}
//获取颜色系
export function getDrawColors(cs, cLen) {
  let list = [];
  for (let i = 0; i < cs.length; i++) {
    list.push(getShadowColor(cs[i], cLen));
  }
  return list;
}

this.cLen=5;
 this.colors = getDrawColors(that.colors, this.cLen);

给每个面的三角形设置对应材质索引materialIndex,注意,四棱椎与四棱台的面数是不同的,但画面的顺序是相同的,先画侧面,再画顶面和底面

if (idx == 0) {
//四棱椎
              geometry.faces.forEach((f, fIdx) => {
                if (fIdx < 28) {
                  geometry.faces[fIdx].materialIndex = parseInt(fIdx / 7);
                } else {
                  geometry.faces[fIdx].materialIndex = 3;
                }
              });
            } else {
            //四棱台
              geometry.faces.forEach((f, fIdx) => {
                if (fIdx < 32) {
                  geometry.faces[fIdx].materialIndex = parseInt(fIdx / 8);
                } else {
                  geometry.faces[fIdx].materialIndex = 4;
                }
              });
            }
            
             let cs = this.colors[idx % this.colors.length];
            let ms = [];
            for (let k = 0; k < cs.length; k++) {
              ms.push(getBasicMaterial(THREE, cs[k]));
            }
            let mesh = new THREE.Mesh(geometry, ms);

四棱椎每个侧面有7个三角形组成,前28个三角形都是侧面。

四棱台每个侧面有8个三角形组成,前32个三角形是侧面

用Three.js搞个3D金字塔

这样看起来终于有了3D的效果了。

3.加上精灵文本

/**
 * 生成文本canvas
 * @param {array} textList [{text:文本,color:文本颜色}]
 * @param {number} fontSize 字体大小
 * @returns
 */
export function getCanvasTextArray(textList, fontSize) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  ctx.font = fontSize + 'px Arial';
  let textLen = 0;
  textList.forEach((item) => {
    let w = ctx.measureText(item.text + '').width;
    if (w > textLen) {
      textLen = w;
    }
  });
  canvas.width = textLen;
  canvas.height = fontSize * 1.2 * textList.length;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.font = fontSize + 'px Arial';
  textList.forEach((item, idx) => {
    ctx.fillStyle = item.color;
    ctx.fillText(item.text, 0, fontSize * 1.2 * idx + fontSize);
  });

  return canvas;
}

/**
 *生成多行文本精灵材质
 * @param {THREE.js} THREE
 * @param {array} textlist 文本颜色数组
 * @param {number} fontSize 字体大小
 * @returns
 */
export function getTextArraySprite(THREE, textlist, fontSize) {
  //生成五倍大小的canvas贴图,避免大小问题出现显示模糊
  const canvas = getCanvasTextArray(textlist, fontSize * 5);

  return { ...getCanvaMat(THREE, canvas, 0.1), canvas };
}

let textList = [
              { text: item.name, color: fontColor },
              { text: item.value + '', color: fontColor }
            ];

            const { mesh: textMesh } = getTextArraySprite(THREE, textList, height * 0.5);
            textMesh.material.depthTest = false;
            textMesh.name = 'f' + idx;
            textMesh.position.z = idx == 0 ? width : (idx + 0.5) * width;
            textMesh.position.y = y;
            textMesh.position.x = 0;
            this.objGroup.add(textMesh);

canvas文本贴图一定要放大倍数,否则会出现近看模糊

用Three.js搞个3D金字塔

4.使用

 var myPyramid = new MyPyramid();
      window.myPyramid = myPyramid;
      myPyramid.initThree(document.getElementById('canvas'));
      myPyramid.createChart({
        //颜色
        colors: ['#fcc02a', '#f16b91', '#187bac'],
        //数据
        data: [
          { name: '小学', value: 100 },
          { name: '中学', value: 200 },
          { name: '大学', value: 300 }
        ],
        //是否倒金字塔
        isDown: false,
        //每层高度
        pHeight: 40,
        //递增宽度
        pWidth: 20,
        //字体颜色
        fontColor: 'rgb(255,255,255)',
        //相机位置
        cameraPos: {
          x: 178.92931795958233,
          y: 210.63511436762354,
          z: 357.5498605603872
        },
        //控制器位置
        controlsPos: {
          x: -4.895320674125236,
          y: 27.139140036227758,
          z: 1.5576536521931232
        }
      });

用Three.js搞个3D金字塔

5.渐变3D金字塔

可能上面的效果有点不够设计感,那么就来点好看的渐变色吧!

获取一个比当前颜色浅的亮色

 getLightColor(color) {
          let c = getColor(color);
          let { red, blue, green } = c;
          console.log('%ccolor', `background:${color}`);

          const l = 0.5;
          const r = red + parseInt((255 - red) * l),
            g = green + parseInt((255 - green) * l),
            b = blue + parseInt((255 - blue) * l);
          console.log('%clight', `background:rgb(${r},${g}, ${b})`);
          return `rgb(${r},${g}, ${b})`;
        }

渐变shader

uniform vec3 topColor; //顶面颜色
  uniform vec3 bottomColor;//底面颜色
      varying vec2 vUv;  
      varying vec3 vNormal;  
		 void main() { 
        if(vNormal.y==1.0){//顶面
          gl_FragColor = vec4(topColor, 1.0 );
        }else if(vNormal.y==-1.0){//底面
          gl_FragColor = vec4(bottomColor, 1.0 );
        }else{//根据uv的y坐标混合两种颜色,形成渐变
          gl_FragColor = vec4(mix(bottomColor,topColor,vUv.y), 1.0 );
        } 
	}

获取渐变ShaderMaterial

 
export function getGradientShaderMaterial(THREE, topColor, bottomColor) {
  const uniforms = {
    topColor: { value: new THREE.Color(getRgbColor(topColor)) },
    bottomColor: { value: new THREE.Color(getRgbColor(bottomColor)) }
  };

  return new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vertexShader,
    fragmentShader: barShader,
    side: THREE.DoubleSide
  });
}

将材质换成渐变材质

let cs = that.colors[idx % that.colors.length];
            let ms = getGradientShaderMaterial(THREE, this.getLightColor(cs), cs);

            let mesh = new THREE.Mesh(geometry, ms);

用Three.js搞个3D金字塔

Github地址

https://github.com/xiaolidan00/my-three