对于从入门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。

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

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

8. 贴图地球的局限性

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

1x

2x

3x

end.

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