关于前端:关于从入门threejs到做出3d地球这件事第四篇-贴图地球

36次阅读

共计 7995 个字符,预计需要花费 20 分钟才能阅读完成。

对于从入门 three.js 到做出 3d 地球这件事(第四篇: 贴图地球)

相干代码能够由此 github 查看

本篇介绍

     通过前三篇的学习基础知识咱们曾经储备差不多了, 这一篇咱们要做一个 贴图地球, 这种地球也是不少公司当初在应用的计划, 但毛病也比拟显著就是它无奈精准的选中某个国家, 所以一些精密的操作它做不到, 然而学习这个技术仍旧是一件令人欢快的事件, 也没准你不须要选中国家的性能, 闲言少叙咱们全军出击吧。

1. 绘制一个木块

     咱们这一篇只探讨规定的矩形木块, 生存中更常见的不规则木块咱们在 3d 模型 篇再聊, 绘制木块的原理就是学生成 geometry, 把它的材质定义为木头图片, 使其材质平均有规定的散布在geometry 外表, 这样在咱们眼里就成了木块。

下载一个木块的图片

     我是间接百度的一张木头纹理图片, 你也能够用我这张, 同时新建一个 img 文件夹用来寄存图片。

新的概念 “ 加载器 ”
const loader = new THREE.TextureLoader();

     下面代码咱们生成了一个加载器, 能够用这个实例进行一系列的加载操作, 外部应用 ImageLoader 来加载文件, 顾名思义 Texture 是纹理的意思所以能够叫它 纹理加载器 , 前面章节降到加载 3d 模型的时候还会介绍更多的 加载器

const loader = new THREE.TextureLoader();
        loader.load(
            './img/ 木块.jpeg',
            (texture) => {
                const material = new THREE.MeshBasicMaterial({map: texture})
                const geometry = new THREE.BoxGeometry(2, 2, 1);
                // 退出纹理
                const mesh = new THREE.Mesh(geometry, material)
                // 放入几何
                scene.add(mesh);
            },
            (xhr) => {
                // 进度
                console.log(`${xhr.loaded / xhr.total * 100}%`)
            },
            (err) => {
                // 谬误
                console.log(err)
            }
        )
  1. 第一个参数 要加载的资源的门路。
  2. 第二个参数 加载胜利后的回调, 会返回纹理对象。
  3. 第三个参数 进度, 将在加载过程中进行调用。参数为 XMLHttpRequest 实例,实例蕴含 total 和 loaded 字节, 请留神 three.js r84 遗弃了 TextureLoader 进度事件, 咱们其实能够填undefined
  4. 第四个参数 谬误的回调。

以后咱们间接关上咱们的 html 文件他会报如下的谬误:

     每次遇到这种跨域报错, 咱们第一工夫应该想到把资源放在服务器上, 然而以后有更简洁的形式。

配置 vscode 插件


在咱们的我的项目页面点击右下角的 Go live 启动一个服务。

此时咱们就能够失去如下的成果:

残缺代码:

<html>
<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>
    <script src="../utils/OrbitControls.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.z = 20;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xffffff)
        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
        document.body.appendChild(renderer.domElement);

        const axisHelper = new THREE.AxisHelper(4)
        scene.add(axisHelper)

        // 为物体减少材质
        const loader = new THREE.TextureLoader();
        loader.load(
            './img/ 木块.jpeg',
            (texture) => {console.log(texture)
                const material = new THREE.MeshBasicMaterial({map: texture})
                const geometry = new THREE.BoxGeometry(2, 2, 1);
                // 退出纹理
                const mesh = new THREE.Mesh(geometry, material)
                // 放入几何
                scene.add(mesh);
            },
            (xhr) => {// 进度(已废除)
                console.log(`${xhr.loaded / xhr.total * 100}%`)
            },
            (err) => {
                // 谬误
                console.log(err)
            }
        )

        const animate = function () {requestAnimationFrame(animate);
            renderer.render(scene, camera);
        };
        animate();

    </script>
</body>

</html>

2. 纹理属性的详谈

     咱们来谈谈纹理的几个属性吧, 木块的图片想看出差异不显著, 咱们在 img 文件夹外面再放一张 鸣人 的图片。

代码外面咱们只改门路即可。

loader.load(
    './img/ 螺旋丸.jpeg',
    (texture) => {...//


     从上图咱们能够看出, 六个面上都是残缺的图片, 然而因为宽高比的不同图像被相应的压缩, 接下来咱们就介绍几个比拟罕用的属性。

反复repeat

     咱们把加载到的纹理进行解决 texture.repeat.x = 0.5 定义他的 x 轴反复值。

把它的数值调大至 5。

     从下面的成果能够看得出, 这个 repeat.x 相似在物体 x 轴方向的画面个数, 也就是说 0.5 就是 x 轴方向铺满须要 0.5 个图片, 5 就是须要 5 张图片能力充斥, 那么与之绝对的就是 y 轴的反复正如下图:


这看起来像个礼品盒的绳子, 那么接下来咱们让这个图铺满外表。

回环wrapS wrapT

t 是图片的 y 轴咱们设置一下:

texture.wrapT = THREE.RepeatWrapping;


同理设置 x 轴, 留神 x 轴叫s:

textureObj.wrapS = THREE.RepeatWrapping

纹理不是咱们这个系列的重点就不扩大了, 有趣味的同学本人玩一玩
残缺代码如下:

<html>
<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
    <script src="../utils/OrbitControls.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.z = 14;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xffffff)
        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
        document.body.appendChild(renderer.domElement);
        const axisHelper = new THREE.AxisHelper(4)
        scene.add(axisHelper)
        // 为物体减少材质
        let textureObj = null;
        const loader = new THREE.TextureLoader();
        loader.load(
            './img/ 螺旋丸.jpeg',
            (texture) => {
                textureObj = texture;
                const material = new THREE.MeshBasicMaterial({map: texture})
                const geometry = new THREE.BoxGeometry(2, 2, 1);
                // 退出纹理
                const mesh = new THREE.Mesh(geometry, material)
                // 放入几何
                scene.add(mesh);
            },
            (xhr) => {// 进度(已废除)
                console.log(`${xhr.loaded / xhr.total * 100}%`)
            },
            (err) => {
                // 谬误
                console.log(err)
            }
        )
        const pames = {
            repeatx: 5,
            repeaty: 5,
        }
        function createUI() {const gui = new dat.GUI();
            gui.add(pames, "repeatx", 0, 5).name("repeatx")
            gui.add(pames, "repeaty", 0, 5).name("repeaty")
        }
        const animate = function () {if (textureObj) {
                textureObj.repeat.x = pames.repeatx
                textureObj.repeat.y = pames.repeaty
                textureObj.wrapT = THREE.RepeatWrapping;
                textureObj.wrapS = THREE.RepeatWrapping
            }
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        };
        createUI()
        animate();
    </script>
</body>
</html>

3. 搭建 vue 我的项目 ( 主线工作 终于开始)

     初始化一个洁净的 vue 我的项目, 这个过程我就不在这里说了, 咱们就从引入 three.js 开始, 这里要十分注意 three.js 的版本很重要, 同样的逻辑在不同版本外面成果居然不一样, 所以想要和本篇一样编写代码的同学能够和我临时对立版本:

yarn add three@0.123.2 

App.vue 改装成如下的样子

<template>
  <div id="app">
    <cc-map id="map"></cc-map>
  </div>
</template>

<script>
import ccMap from "./components/cc_map.vue";
export default {
  name: "App",
  components: {ccMap,},
};
</script>

<style>
#app {
  overflow: hidden;
  border: 1px solid #ccc;
  width: 700px;
  height: 600px;
  margin: 20px auto;
}
</style>

从下面代码能够看出, <cc-map></cc-map>这个就是我 第一篇文章 里提到的专门的 vue 组件, 接下来的篇章里咱们就都是围绕着开发这个组件的性能了, 除非零散的知识点我会单开一个html 文件讲, 大部分都是主线工作了。

临时新建这样三个文件夹与文件。

4. 要应用的贴图

思否 不让上传超过 4M 的图, 所以上面是个含糊的截图, 想看原图的盆友能够看我我的项目里的, 这里的图片处于 assets > images 的地位。

config > earth.config.js内配置两个参数。

export default {
    r: 80, // 半径
    earthBg: require("../assets/images/ 地图.png"), // 贴图门路
}

以后初步 components > cc_map.vue 的模板构造, 留神习惯引入 ’three’ 的形式。

<template>
  <div class="map" ref="map"></div>
</template>

<script>
import * as THREE from "three";
import envConifg from "../config/earth.config";

export default {
  name: "ccMap",
  data() {return {};
  },
  methods: { },
  mounted() {},
};
</script>

<style scoped>
.map {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
}
</style>

5. 把根底环境的搭建抽成办法

都是之前篇章提到的办法, 先把 data 数据初始化好

data() {
    return {
      scene: null,
      camera: null,
      mapDom: null,
      renderer: null,
      orbitControls: null,
      object: new THREE.Object3D(),
      axisHelper: new THREE.AxesHelper(120),
      textureLoader: new THREE.TextureLoader(),};
  },
第一步: 初始 场景 应用initTHREE (之后根本不改)
initTHREE() {
  this.renderer = new THREE.WebGLRenderer({antialias: true,});
  this.mapDom = this.$refs.map;
  this.renderer.setSize(this.mapDom.clientWidth, this.mapDom.clientHeight);
  this.renderer.setClearColor(0xffffff, 1.0);
  this.mapDom.appendChild(this.renderer.domElement);
},
第二步: 初始 相机 应用initCamera(之后根本不改)
initCamera() {
  this.camera = new THREE.PerspectiveCamera(
    45,
    this.mapDom.clientWidth / this.mapDom.clientHeight,
    1,
    2000
  );
  this.camera.position.z = 300;
  this.camera.up.set(0, 1, 0);
  this.camera.lookAt(0, 0, 0);
},
第三步: 初始 容器 应用initScene(之后根本不改)
this.scene = new THREE.Scene();
第四步: 初始 辅助线 应用initAxisHelper(之后根本不改)
this.scene.add(this.axisHelper);
第五步: 初始 光源 应用initLight(之后根本不改)
const ambientLight = new THREE.AmbientLight(0xffffff);
this.scene.add(ambientLight);

前期能够模仿太阳光照耀, 到时候咱们加个平型光就很像回事了。

第六步: 初始 轨道 应用initOrbitControls(之后根本不改)
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls.js";
// ...
initOrbitControls() {const os = new OrbitControls(this.camera, this.renderer.domElement);
  os.target = new THREE.Vector3(0, 0, 0); // 管制焦点
  os.autoRotate = false; // 将主动旋转敞开
  os.enablePan = false; // 不禁止鼠标平移, 能够用键盘来平移
  os.maxDistance = 1000; // 最大外挪动
  os.minDistance = 100; // 向内最小外挪动
  this.orbitControls = os;
},
第七步: 初始 地球背景 应用initBg
  • 之后会有一张专门讲物体的绘制的, 到时候咱们再详聊圆形

    initBg() {
      // 把背景图加载过去当做纹理。const texture = this.textureLoader.load(envConifg.earthBg);
      // 这个绘制球体
      const geometry = new THREE.SphereGeometry(envConifg.r, 50, 50);
      // 放入纹理
      const material = new THREE.MeshLambertMaterial({map: texture,});
      const mesh = new THREE.Mesh(geometry, material);
      this.scene.add(mesh);
    },
第八步: 初始 渲染函数 应用glRender
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.glRender);

这里必定不能间接叫render

end: 开关模式
  mounted() {this.initTHREE();
    this.initCamera();
    this.initScene();
    this.initAxisHelper();
    this.initLight();
    this.initOrbitControls();
    this.initBg();
    this.glRender();},


     这里的贴图地图其实曾经能够满足局部的需要场景了, 不要看它简略它也能够很炫的。

6. ps 加文字, 但会扭曲

     贴图地球有它的局限性, 比方下面地图上当初是空空的没有相应的国家名, 然而如果我在图片中 ps 上国家名, 让咱们看看成果。

     ps 上究竟不是最灵便的方法, 而且如果你认真看会发现文字有点向上蜿蜒, 因为图片是附着在球体上的, 所以越凑近南北极越汇聚成一个点, 所以这样加文字的模式只针对多数面积大并在赤道左近的国家有用。

7. 有意思的球体

下面咱们设置的球体咱们独自拿进去玩一下, 这里咱们只聊前三个参数, 前面会有 专门介绍几何体的文章

![image.png](/img/bVcRbTv)
  1. r 就是半径, 这个决定了球体的大小。
  2. 程度分段数(沿着经线分段),最小值为 3,默认值为 8, 比如说一个圆圈由 100 个点相互线段链接组成, 那么这参数就是这个 100。
  3. 垂直分段数(沿着纬线分段),最小值为 2,默认值为 6。

来吧展现: 当我把程度分段数变成 5 new THREE.SphereGeometry(envConifg.r, 5, 50);

来吧展现: 当我把垂直分段数变成 5 new THREE.SphereGeometry(envConifg.r, 50, 5);

8. 贴图地球的局限性

  1. 如下面所说, 很难为国家区域加名称。
  2. 无奈具体的选中某个国家。
  3. 无奈让某个地区高亮或者呈现红色边框。
  4. 视角拉近之后有些失真。
  5. 无奈悬停显示详情信息
这里是发的比照

1x

2x

3x

end.

     下一篇开始正式绘制咱们的矢量 3d 地球 了, 会波及一些数学知识, 比方三角函数你是否曾经不会背了, 那我就带你钻研?
     这次就是这样, 心愿和你一起提高。

正文完
 0