最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    正文概述 掘金(陆丛)   2021-04-21   965

    1.“冰山一角”

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期 在使用echarts-gl地球的时候,会发现球体边缘是不规则的,例子见 Echarts GL Earth。代码很简单,只使用了贴图,并没有导入模型,“冰山一角”的凹凸如何形成引起了我的兴趣,于是开始了顺藤摸瓜之旅。

    2.echarts-gl觅芳踪

    安装好依赖之后,开始了debug之路。

    1. 注释掉heightTexture后,冰山一角凹凸性消失,形成了完美球,见下图。

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期 于是可以推断该效果由heightTexture 配置项影响。

    1. 在echarts-gl中搜索heightTexture

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    发现了函数getDisplacementTexture,使用该函数的地方有四处,通过debug,最关键一处在这里(下图)。

            ecModel.eachComponent('globe', function (globeModel, idx) {
                var globe = globeModel.coordinateSystem;
    
                // Update displacement data
                var displacementTextureValue = globeModel.getDisplacementTexture(); // 获取置换纹理
                var displacementScale = globeModel.getDisplacemenScale(); // 获取置换比例
    
                if (globeModel.isDisplacementChanged()) {
                    if (globeModel.hasDisplacement()) {
                        var immediateLoaded = true;
                        __WEBPACK_IMPORTED_MODULE_5__util_graphicGL__["a" /* default */].loadTexture(displacementTextureValue, api, function (texture) {
                            var img = texture.image;
                            var displacementData = getDisplacementData(img, displacementScale); //获取置换数据
                            globeModel.setDisplacementData(displacementData.data, displacementData.width, displacementData.height);
                            if (!immediateLoaded) {
                                // Update layouts
                                api.dispatchAction({
                                    type: 'globeUpdateDisplacment'
                                });
                            }
                        });
                        immediateLoaded = false;
                    }
                    else {
                        globe.setDisplacementData(null, 0, 0);
                    }
    
                    globe.setDisplacementData(
                        globeModel.displacementData, globeModel.displacementWidth, globeModel.displacementHeight
                    );
                }
            });
    

    这里其实核心在于设置displacementData,于是重点在如何生成displacementData与如何使用displacementData

    3.生成displacementData,从上面的代码块可知其通过getDisplacementData函数生成,函数定义如下:

    function getDisplacementData(img, displacementScale) {
        var canvas = document.createElement('canvas');
        var ctx = canvas.getContext('2d');
        var width = img.width;
        var height = img.height;
        canvas.width = width;
        canvas.height = height;
        ctx.drawImage(img, 0, 0, width, height);
        var rgbaArr = ctx.getImageData(0, 0, width, height).data;
    
        var displacementArr = new Float32Array(rgbaArr.length / 4);
        for (var i = 0; i < rgbaArr.length / 4; i++) {
            var x = rgbaArr[i * 4]; // 取红色分量
            displacementArr[i] = x / 255 * displacementScale;
        }
        return {
            data: displacementArr,
            width: width,
            height: height
        };
    }
    

    该函数将图片(也就是heightTexture提供的图片)画在canvas画布上,然后拿到ImageData,ImageData是一个一维数组,包含图片的像素信息,如下图。 Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期 由于每一个像素占四个位置(rgba四个分量),所以displacementArr的长度是rgbaArr.length / 4,然后取了红色(red, i * 4)分量, 其实取蓝色分量(i * 4 + 1)、绿色分量(i * 4 + 2)都可以,或者取三者的平均值也可以,效果之后些许不同,与置换纹理图片相关。最后得到的data如下图:

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    1. 使用displacementData。通过跟踪,摸到了_doDisplaceVertices这里,顾名思义,这个函数要做的是处理置换顶点,相当于将原geometry的顶点信息修改掉(怪不得会有凹凸感,原来是几何体的形状改变了。webgl的核心就是顶点着色器与片元着色器,这一部分知识可以看这里)。
        _doDisplaceVertices: function (geometry, globe) {
            // 置换顶点
            var positionArr = geometry.attributes.position.value; // 顶点的位置信息
            var uvArr = geometry.attributes.texcoord0.value; // uv坐标信息
    
            var originalPositionArr = geometry.__originalPosition; // 原始顶点位置信息
            if (!originalPositionArr || originalPositionArr.length !== positionArr.length) {
                originalPositionArr = new Float32Array(positionArr.length);
                originalPositionArr.set(positionArr);
                geometry.__originalPosition = originalPositionArr;
            }
    
            var width = globe.displacementWidth;
            var height = globe.displacementHeight;
            var data = globe.displacementData; // 我们的置换数据
            // 遍历顶点信息,更新每个位置坐标
            for (var i = 0; i < geometry.vertexCount; i++) {
                var i3 = i * 3; // 为什么乘以3, 因为每个顶点占3位
                var i2 = i * 2; // 为什么乘以2, 因为每个uv坐标占2位
                // 原始位置
                var x = originalPositionArr[i3 + 1];
                var y = originalPositionArr[i3 + 2];
                var z = originalPositionArr[i3 + 3];
    
                // 根据uv坐标在placementData中取scale值
                var u = uvArr[i2++];
                var v = uvArr[i2++];
    
                var j = Math.round(u * (width - 1));
                var k = Math.round(v * (height - 1));
                var idx = k * width + j;  
                var scale = data ? data[idx] : 0; // 每个位置的偏移量比例
                
                // 更新顶点信息,每一个点在原始点位置增加一个偏移量,这个偏移量由heightTexture图片某个像素的red值决定。
                positionArr[i3 + 1] = x + x * scale;
                positionArr[i3 + 2] = y + y * scale;
                positionArr[i3 + 3] = z + z * scale;
            }
    
            geometry.generateVertexNormals(); // 生成顶点法向量
            // 数据标记与更新包围盒
            geometry.dirty(); 
            geometry.updateBoundingBox(); 
        },
    

    uv贴图可见Three.js电子书第八章

    到这里,基本的逻辑跟踪就结束了。

    1. 原始的uv坐标信息在生成SphereGeometry的时候就已经确定放在attributes中了(根据widthSegmentswidthSegments生成),echarts-gl依赖的底层库是claygl,感兴趣可以查看其构造函数。

    3.three.js入场

    于是我想着用three.js来实现类似的效果。

    three.js来构建三维就像拍一部电影,场景演员组灯光组摄像组导演组缺一不可,只需要按照顺序执行就好,简单来讲可以按下图顺序按部就班。

          // atlast
          window.onload = async function () {
            initScene(); // 场景准备就绪
            await initMesh(); // 演员准备就绪
            initLight(); // 灯光组准备就绪
            initCamera(); // 摄影组准备就绪
            initRenderer(); // 导演剪辑渲染镜头给观众
    
            animate(); // 开启动画Action 大家开始动
          };
    

    贴图素材(先给需要的玩家,或者去echarts gallery抓包,掘金有水印)

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    普通贴图完整代码:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          html,
          body {
            height: 100%;
            margin: 0;
            padding: 0;
          }
        </style>
        <script src="three.js"></script>
        <script src="OrbitControls.js"></script>
      </head>
      <body>
        <div id="chart" style="height: 100%;"></div>
        <script>
          let container = document.getElementById("chart");
          let width = container.clientWidth; 
          let height = container.clientHeight;
          let SCENE, CAMERA, RENDERER;
    
          const ImageLoader = new THREE.ImageLoader();
    
          function initScene() {
            SCENE = new THREE.Scene();
          }
    
          async function initMesh() {
            let axisHelper = new THREE.AxesHelper(250);
            SCENE.add(axisHelper);
    
            let geometry = new THREE.SphereGeometry(5, 40, 40); // 3,2
    
            let img = await ImageLoader.load("./earth.jpg");
    
            let texture = new THREE.Texture(img);
    
            texture.needsUpdate = true;
            
            let material = new THREE.MeshStandardMaterial({
              map: texture,
            });
    
            let sphere = new THREE.Mesh(geometry, material);
            SCENE.add(sphere);
          }
    
          function initLight() {
            //点光源
            let point = new THREE.PointLight(0xffffff);
            point.position.set(400, 0, 0); //点光源位置
            SCENE.add(point); //点光源添加到场景中
            //环境光
            let ambient = new THREE.AmbientLight(0xffffff);
            SCENE.add(ambient);
          }
    
          function initCamera() {
            let k = width / height; //窗口宽高比
            let s = 10; //三维场景显示范围控制系数,系数越大,显示的范围越大
    
            //创建相机 CAMERA
            CAMERA = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
            CAMERA.position.set(200, 100, 100); //设置相机位置
            CAMERA.lookAt(SCENE.position); //设置相机方向(指向的场景对象)
          }
    
          function initRenderer() {
            // RENDERER
            RENDERER = new THREE.WebGLRenderer({
              antialias: true,
              alpha: true,
            });
            RENDERER.setSize(width, height); //设置渲染区域尺寸
            RENDERER.setClearColor(0x00000, 1); //设置背景颜色
            container.appendChild(RENDERER.domElement); //body元素中插入canvas对象
    
            new THREE.OrbitControls(CAMERA, RENDERER.domElement);
          }
    
          function animate() {
            RENDERER.render(SCENE, CAMERA); //执行渲染操作
            requestAnimationFrame(animate);
          }
    
          window.onload = async function () {
            initScene(); // 场景准备就绪
            await initMesh(); // 演员准备就绪
            initLight(); // 灯光组准备就绪
            initCamera(); // 摄影组准备就绪
            initRenderer(); // 导演剪辑渲染镜头给观众
    
            animate(); // 开启动画Action 大家开始动
          };
        </script>
      </body>
    </html>
    
    

    这样可以得到一个完美球

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    通过群友提示,three.jsMeshStandardMaterial材质有displacementMap(置换贴图配置),这里只需要修改initMesh

          async function initMesh() {
            let axisHelper = new THREE.AxesHelper(250);
            SCENE.add(axisHelper);
    
            let geometry = new THREE.SphereGeometry(5, 40, 40); // 3,2
    
            let img = await ImageLoader.load("./earth.jpg");
    
            let heightImg = await ImageLoader.load("./earth-high.jpg");
    
            let texture = new THREE.Texture(img);
    
            let heightTexture = new THREE.Texture(heightImg);
    
            texture.needsUpdate = true;
            
            let material = new THREE.MeshStandardMaterial({
              displacementMap: heightTexture,
              displacementScale: 1.1,
              displacementBias: 4,
              map: texture,
            });
    
            material.displacementMap.needsUpdate = true; // very important
    
            let sphere = new THREE.Mesh(geometry, material);
            SCENE.add(sphere);
          }
    

    注意需要添加material.displacementMap.needsUpdate = true,不然置换贴图将不生效。

    置换贴图(displacementMap)

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期 这要不仅可以看到“冰山一角”,也可以看到“世界屋脊”。 于是我又尝试了法线贴图凹凸贴图

    法线贴图(normalMap)

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    凹凸贴图(bumpMap)

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    4.置换贴图 vs 凹凸贴图 vs 法线贴图

    • 置换贴图:改变了Geometry的顶点位置,会产生大量的三角面,计算量极大,吃计算机配置(显卡、内存、CPU),效果最好,因为其实真正的模拟,凸出与凹入都会与光照阴影结合。

    • 凹凸贴图:凹凸贴图光照和材质在3D模型的表面制造出凹凸不平的质感的错觉。使用灰度(Grayscale)图和简单的光影技巧在对象的表面人为地制造这种质感,而不是真的在其表面扣出一个个的凸起和凹入。当灰度值在50%附近时,物体表面几乎不会有什么细节变化。当灰度值变亮(白),表面细节呈现为凸出,当灰度值变暗(黑),表面细节呈现为凹入。用凹凸贴图实现模型的微小细节非常棒。比如,皮肤上的毛孔和褶皱。

    • 法线贴图:法线贴图就是在原物体的凹凸表面的每个点上均作法线,通过RGB颜色通道来标记法线的方向,你可以把它理解成与原凹凸表面平行的另一个不同的表面,但实际上它又只是一个光滑的平面。对于视觉效果而言,它的效率比原有的凹凸表面更高,若在特定位置上应用光源,可以让细节程度较低的表面生成高细节程度的精确光照方向和反射效果。(百度百科)

    如何抉择:小细节用法线贴图或者凹凸贴图,大的轮廓使用置换贴图。

    5.最后

    claygl vs three

    dead game dota2 based on claygl example

    Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    dead game dota2 based on claygl code 里面有英雄模型数据哟


    起源地下载网 » Echarts 3D Earth "冰山一角"揭秘 | 创作者训练营第二期

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元