相关代码能够由此github查看html
经过前三篇的学习基础知识咱们已经储备差很少了, 这一篇咱们要作一个贴图地球
, 这种地球也是很多公司如今在使用的方案, 但缺点也比较明显就是它没法精准的选中某个国家, 因此一些精细的操做它作不到, 可是学习这个技术依旧是一件使人愉快的事情, 也没准你不须要选中国家的功能, 闲言少叙咱们全军出击吧。vue
咱们这一篇只讨论规则的矩形木块, 生活中更常见的不规则木块咱们在3d模型
篇再聊, 绘制木块的原理就是先生成geometry
, 把它的材质定义为木头图片, 使其材质均匀有规则的分布在geometry
表面, 这样在咱们眼里就成了木块。git
我是直接百度的一张木头纹理图片, 你也能够用我这张, 同时新建一个img
文件夹用来存放图片。github
const loader = new THREE.TextureLoader();
上面代码咱们生成了一个加载器, 能够用这个实例进行一系列的加载操做, 内部使用ImageLoader
来加载文件, 顾名思义Texture
是纹理的意思因此能够叫它纹理加载器
,后面章节降到加载3d模型的时候还会介绍更多的加载器
。ajax
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
启动一个服务。
此时咱们就能够获得以下的效果:app
完整代码:dom
<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>
咱们来谈谈纹理的几个属性吧, 木块的图片想看出差异不明显, 咱们在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>
主线任务
终于开始) 初始化一个干净的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
文件讲, 大部分都是主线任务了。
暂时新建这样三个文件夹与文件。
思否
不让上传超过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>
都是以前篇章提到的方法, 先把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
mounted() { this.initTHREE(); this.initCamera(); this.initScene(); this.initAxisHelper(); this.initLight(); this.initOrbitControls(); this.initBg(); this.glRender(); },
这里的贴图地图其实已经能够知足部分的需求场景了, 不要看它简单它也能够很炫的。
贴图地球有它的局限性, 好比上面地图上如今是空空的没有相应的国家名, 可是若是我在图片中ps上国家名, 让咱们看看效果。
ps上终究不是最灵活的办法, 并且若是你仔细看会发现文字有点向上弯曲, 由于图片是附着在球体上的, 因此越靠近南北极越会聚成一个点, 因此这样加文字的模式只针对少数面积大并在赤道附近的国家有用。
上面咱们设置的球体咱们单独拿出来玩一下, 这里咱们只聊前三个参数, 后面会有专门介绍几何体的文章
。

来吧展现: 当我把水平分段数变成5new THREE.SphereGeometry(envConifg.r, 5, 50);
来吧展现: 当我把垂直分段数变成5new THREE.SphereGeometry(envConifg.r, 50, 5);
1x
2x
3x
下一篇开始正式绘制咱们的矢量3d地球
了, 会涉及一些数学知识, 好比三角函数你是否已经不会背了, 那我就带你研究? 此次就是这样, 但愿和你一块儿进步。