1.金字塔
金字塔组成:顶部一个四棱锥体,下面是上窄下宽依次递增的四棱台。
那么需要计算一下依次下来的每个面大小,上面的形状底部作为下面形状的顶面,这样就每层都能看起来连续。
js
let top = idx * width;//顶面正方形宽度 let bottom = (idx + 1) * width;//底面正方形宽度 let geometry = newTHREE.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时即为四灵锥。
当顶面宽底面窄时,即可形成倒金字塔
2.金字塔立体化
可以看到,单纯的一种颜色,让金字塔看起来不太立体,这时候需要给每个面设置同色系的相近颜色,通过每个面的颜色差来达到这种效果。
四棱台有六个面,四棱椎有五个面,那么就取五颜色值,侧面的四个面分别一个颜色,顶面和底面一个颜色。
js
/** * 获取暗色向渐变颜色 * @param {string} color 基础颜色 * @param {number} step 数量 * @returns {array} list 颜色数组 */exportfunctiongetShadowColor(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;}//获取颜色系exportfunctiongetDrawColors(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
,注意,四棱椎与四棱台的面数是不同的,但画面的顺序是相同的,先画侧面,再画顶面和底面
js
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 = newTHREE.Mesh(geometry, ms);
四棱椎每个侧面有7个三角形组成,前28个三角形都是侧面。
四棱台每个侧面有8个三角形组成,前32个三角形是侧面
这样看起来终于有了3D的效果了。
3.加上精灵文本
js
/** * 生成文本canvas * @param {array} textList [{text:文本,color:文本颜色}] * @param {number} fontSize 字体大小 * @returns */exportfunctiongetCanvasTextArray(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 */exportfunctiongetTextArraySprite(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文本贴图一定要放大倍数,否则会出现近看模糊
4.使用
js
var myPyramid = newMyPyramid(); 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 } });
5.渐变3D金字塔
可能上面的效果有点不够设计感,那么就来点好看的渐变色吧!
获取一个比当前颜色浅的亮色
js
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
c++
uniform vec3 topColor; //顶面颜色 uniform vec3 bottomColor;//底面颜色 varying vec2 vUv; varying vec3 vNormal; voidmain() { if(vNormal.y==1.0){//顶面 gl_FragColor = vec4(topColor, 1.0 ); }elseif(vNormal.y==-1.0){//底面 gl_FragColor = vec4(bottomColor, 1.0 ); }else{//根据uv的y坐标混合两种颜色,形成渐变 gl_FragColor = vec4(mix(bottomColor,topColor,vUv.y), 1.0 ); } }
获取渐变ShaderMaterial
js
exportfunctiongetGradientShaderMaterial(THREE, topColor, bottomColor) { const uniforms = { topColor: { value: newTHREE.Color(getRgbColor(topColor)) }, bottomColor: { value: newTHREE.Color(getRgbColor(bottomColor)) } }; returnnewTHREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vertexShader, fragmentShader: barShader, side: THREE.DoubleSide });}
将材质换成渐变材质
js
let cs = that.colors[idx % that.colors.length]; let ms = getGradientShaderMaterial(THREE, this.getLightColor(cs), cs); let mesh = newTHREE.Mesh(geometry, ms);