共计 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)
}
)
第一个参数
要加载的资源的门路。第二个参数
加载胜利后的回调, 会返回纹理对象。第三个参数
进度, 将在加载过程中进行调用。参数为 XMLHttpRequest 实例,实例蕴含 total 和 loaded 字节, 请留神 three.js r84 遗弃了 TextureLoader 进度事件, 咱们其实能够填undefined
。第四个参数
谬误的回调。
以后咱们间接关上咱们的 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)
- r 就是半径, 这个决定了球体的大小。
- 程度分段数(沿着经线分段),最小值为 3,默认值为 8, 比如说一个圆圈由 100 个点相互线段链接组成, 那么这参数就是这个 100。
- 垂直分段数(沿着纬线分段),最小值为 2,默认值为 6。
来吧展现: 当我把程度分段数变成 5 new THREE.SphereGeometry(envConifg.r, 5, 50);
来吧展现: 当我把垂直分段数变成 5 new THREE.SphereGeometry(envConifg.r, 50, 5);
8. 贴图地球的局限性
- 如下面所说, 很难为国家区域加名称。
- 无奈具体的选中某个国家。
- 无奈让某个地区高亮或者呈现红色边框。
- 视角拉近之后有些失真。
- 无奈悬停显示详情信息
这里是发的比照
1x
2x
3x
end.
下一篇开始正式绘制咱们的矢量 3d 地球
了, 会波及一些数学知识, 比方三角函数你是否曾经不会背了, 那我就带你钻研?
这次就是这样, 心愿和你一起提高。