基于Threejs的楼层光照热力效果

1,892 阅读2分钟

前言

从研究生开始接触Webgl,后来也一直做着跟webgl相关的工作。期间结合Cesium,或者Threejs做过不少的项目,但是从来都是自己学习,并没有进行相关知识的写作,反思一下这样并不利于自己对于知识的积累和巩固,因此从本篇开始,我将陆陆续续将自己过往及以后学习到的知识进行一个总结并输出原创文章。

本篇需要对Threejs有一定的了解。

本篇文章是我第一次真正意义上的技术博客,有诸多错误之处还请海涵。

效果

image.png

准备

楼层数据,包括地面点位坐标、侧面贴图、顶面贴图

image.png 其中floor表示楼层层数,geometry为构建楼层的多边形点位序列。

具体实现

实现楼层的光照热力步骤: 首先,构建楼层,包括楼层侧面构建和楼顶构建;楼层的侧面构造比较简单,即通过给地面的点序列增加不同的高度来构建不同层级的侧面;顶面则需要通过点序列计算顶点索引、纹理坐标等等来构建。 然后,根据日期计算当天的光照时长、日出日落时间(可以谷歌相关资料),根据日出日落时间得到太阳位置,根据射线计算楼层的日照时长。 最后,根据,每层的日照时长给楼层渲染不同的颜色。

要点

  1. 楼层的日照时长从开始就确定了的,所以可以在初始化的时候就开始计算并记录到每层楼的对象之上;
  2. 计算日照时间可以自己设置一个精度,我选的是40分钟计算一次;
  3. 构建楼层的时候不共享点位,便于渲染不同面的颜色

代码片段

        // 16*2.5  每40分钟计算一次
        const delta = 16;
        for (let i = startIndex; i < endIndex; i += delta) {
            const sunPosition = new THREE.Vector3(points[i * 3], points[i * 3 + 1], points[i * 3 + 2]);
            const {array} = floorObj.geometry.attributes.position;
            for (let j = 6, length = array.length; j < length; j += 12) {
                // -获取每个面的四个角点
                let leftBottom, leftTop, rightBottom, rightTop;
                leftBottom = new THREE.Vector3(array[j], array[j + 1], array[j + 2]);
                leftTop = new THREE.Vector3(array[j + 3], array[j + 4], array[j + 5]);
                if (j < length - 12) {
                    rightBottom = new THREE.Vector3(array[j + 6], array[j + 7], array[j + 8]);
                    rightTop = new THREE.Vector3(array[j + 9], array[j + 10], array[j + 11]);
                } else {
                    rightBottom = new THREE.Vector3(array[0], array[1], array[2]);
                    rightTop = new THREE.Vector3(array[3], array[4], array[5]);
                }
                // -获取每个面的中心点
                const center = this.calFaceCenter({
                    leftBottom,
                    leftTop,
                    rightBottom,
                    rightTop
                }).add(new THREE.Vector3(0, floorHeight * 0.25, 0));
                // -根据中心点得到raycaster的方向
                const normal = originPoint.clone().sub(sunPosition.clone()).normalize();
                const origin = (center.clone()).sub(normal.clone().multiplyScalar(100));
                const faceNormal = this.getFaceNormal([leftTop, rightTop, rightBottom]);
                // -如果光线方向与面夹角大于90度则肯定照不到
                if (normal.dot(faceNormal) > 0) continue;


                threeNode.raycaster.set(origin, normal);
                const intersects = threeNode.raycaster.intersectObjects(resultArr, false);
                // -如果根据面中心射线与面所在的对象相交,则计算日照
                if (intersects.length > 0 && intersects[0].object === floorObj) {
                    const {faceIndex} = intersects[0];
                    // -如果相交的面正好为计算的面则该面记一次日照
                    if (faceIndex === (j / 3) || faceIndex === (j / 3 + 1)) {

                        if (floorObj.userData.sunFace[faceIndex] !== undefined) floorObj.userData.sunFace[faceIndex] += delta * 2.5;
                        else floorObj.userData.sunFace[faceIndex] = delta * 2.5;
                    }
                }
            }
        }