threejs 打造 world.ipanda.com 同款3D首页


theme: jzman

2023-04-28 18.22.58.gif

最近丫丫回国掀起一股熊猫热,本人也没闲着,在查资料时候无意间发现 熊猫世界 的3D首页,心一痒痒,就扒了一下数据,刚好有模型,那索性按照熊猫世界的首页复刻一下;

本文没什么高的技术含量,完全觉得这个页面比较好看,所以大概做了做,涉及到的api光线投射RaycasterCSS 2D渲染器(CSS2DRenderer)TextureLoader 等等;

扒下来的数据分几个部分 背景图、模型、贴图、icon、旗帜、等

素材

image.png

这是地图的分层贴图

image.png

这是一些图标的图片

场景基础要素

设置场景背景贴图

typesctipt scene = new Scene(); const sceneTexture = await loadTexture('../src/assets/textures/bg.jpeg') sceneTexture.wrapS = THREE.RepeatWrapping; sceneTexture.wrapT = THREE.RepeatWrapping; sceneTexture.repeat.set(1, 1); scene.background = sceneTexture

贴图加载公共方法

loadTexture是提取的一个公共方法,后续在加载earth模型的时候也需要用到,下面是提取的方法,返回一个Promise

javascript // 加载纹理 export function loadTexture(grid: string) { return new Promise<Texture>((resolve, reject) => { textureLoader.load(grid, (texture) => { resolve(texture) }); }) }

至于贴图的一些属性,我在之前的文章也有提到过,这里不赘述,可以看一下文档

渲染器

WebGLRenderer

javascript renderer = new WebGLRenderer(); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.needsUpdate = true;

CSS2DRenderer

2drenderer的作用是渲染国家旗帜用的,最后渲染出来的是一个element,在控制器渲染时,通过改变element的transform和position保证元素与3d向量对齐

javascript labelRenderer = new CSS2DRenderer(); labelRenderer.setSize(window.innerWidth, window.innerHeight); labelRenderer.domElement.style.position = "absolute"; labelRenderer.domElement.style.top = "0"; labelRenderer.domElement.style.pointerEvents = "none";

image.png

灯光

javascript // 灯光 ambientLight = new AmbientLight(0xffffff, 2.2); scene.add(ambientLight)

用普通的环境光,能照亮场景就可以了,如果需要做一些其他氛围渲染,可以选用平行光或者点光源

控制器

javascript controls = new OrbitControls(camera, renderer.domElement)

使用控制器可以改变相机的位置,可以从各个角度观察场景

复刻渲染

加载模型

earthGroup = new THREE.Group() 定义一个地球的组,将地球模型add进去,使用封装好的loadFbx方法将地图模型加载到场景中

javascript earth = await loadFbx('../src/assets/models/diqiu.fbx', async (event: ProgressEvent) => { const { total, loaded } = event const count = Math.min(Math.ceil(loaded / total * 100), 100) rendeDom(count) if (count === 100 && !scene) { loading.style.display = "none" } })

方法支持两个属性,第一个属性为模型地址,字符串格式,第二个为加载中的回调,(event: ProgressEvent) => void类型

loadFbx

javascript // 加载FBX模型 export function loadFbx(modelUrl: string, cb?: (event: ProgressEvent) => void): Promise<Object3D> { return new Promise((resolve, reject) => { fbxLoader.load(modelUrl, function (loadedModel) { resolve(loadedModel) }, (a) => { cb&&cb(a) }); }) }

加载中的回调主要的作用就是加载进度条

地球贴图

javascript // 地图贴图 const diqiuTexture = await loadTexture('../src/assets/textures/diqiu.jpeg') earth.children[0].material.map = diqiuTexture

效果

image.png

渲染2d element

我在网站上没扒到国家位置的信息,而且在地球加载的过程中,原始网站提供的位置肯定跟咱们复刻的位置不会相同,所以另辟蹊径,从模型上下手

我的想法就是对照原始网站的大概位置,找到复刻的位置坐标,然后再渲染出2d元素,将位置设置到相应位置,利用鼠标点击射线,点击地图后,获取地图和鼠标的交叉点,说干就干\~

提取坐标

javascript let mouse = new THREE.Vector2(); //鼠标位置 var raycaster = new THREE.Raycaster(); window.addEventListener("click", (event) => { mouse.x = (event.clientX / document.body.offsetWidth) * 2 - 1; mouse.y = -(event.clientY / document.body.offsetHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); var raylist = raycaster.intersectObjects(earthGroup.children); console.log(raylist) });

image.png

Raycaster射线方法在 官网 上也有一些案例,可以翻阅文档查看具体api

javascript console.log(JSON.stringify({ vector: new Vector3(raylist[0].point.x, raylist[0].point.y, raylist[0].point.z,), country: '日本', mapUrl: '../', iconUrl: '../' }))

打印出组合的数据,这样就可以获取到具体的坐标,再组合一下其他数据,比如地图上的国家旗帜地址,列表的旗帜地址,或者直接存到localstore缓存中,我这里用的是最笨的方法,一个一个复制到文件里

image.png

添加元素

```javascript // 渲染2d文字 function initThreeFloorName(d: any) { var image = document.createElement("img"); image.className = "country-image"; image.setAttribute('src', d.mapUrl) if(d.style) { for(let key in d.style ) { const s = d.style[key] image.style[key] = s

}
}
var earthLabel = new CSS2DObject(image);
earthLabel.position.copy(d.vector);
group2D.add(earthLabel);

} ```

group2D是存图标的组,每个元素都存在一个单独的组,后续好操作,

image.png

隐藏遮挡元素

从图中可以看到,地图正面背面的元素都展示在一起了,那是因为2d元素和3d元素不在同一个图层里,

image.png

这导致地图并不能遮挡到本应在地图后面的2d元素

那就需要在渲染的时候进行一些操作,来隐藏被遮挡的元素,还是利用射线;

从摄像机position发送一条射线,到2d元素的position,射线如果和地图及地图里的元素产生交叉,则隐藏当前2d元素,说干就干\~

javascript // 物体之间的射线 function rayMesh() { group2D.traverse((text: any) => { if (!text.isGroup) { const opt = pointRay(camera.position, text.position, sphereGroup); text.element.style.opacity = Number(!opt).toString(); } }); }

pointRay 点到点射线交互

```javascript function pointRay(star, end, children) { let nstar = star.clone(); // 克隆一个新的位置信息,这样不会影响传入的三维向量的值 let nend = end.clone().sub(nstar).normalize(); // 克隆一个新的位置信息,这样不会影响传入的三维向量的值

const raycaster = new THREE.Raycaster(nstar, nend); // 创建一个正向射线
const intersects = raycaster.intersectObjects(
    children.children,
    true
);
let jclang = 0
let textlang = 0
if (intersects.length != 0) {
    // 计算起点到交互点的曼哈顿距离
    jclang = star.distanceTo(intersects[0].point)
    // 起点到结束点的曼哈顿距离
    textlang = star.distanceTo(end)
}
return jclang < textlang;

} ```

image.png

一目了然,如果射线交叉点比终点的距离短,那视为隐藏 返回true 标记为有遮挡,

透明度降为0 text.element.style.opacity = Number(!opt).toString();

检测是否遮挡

// 渲染函数
function render() {
    renderer.render(scene, camera);
    labelRenderer.render(scene, camera);

    if (isUpdate) {
        isUpdate = false
        rayMesh()
        let timeout: any = setTimeout(() => {
            isUpdate = true
            clearInterval(timeout)
            timeout = null
        }, 100)
    }

}

render中调用rayMesh方法,我这里写的是一个简易的防抖,并不代表本人观点,哈哈哈(努力撇清关系),那么遮挡效果就做好了,还有图标的点击跳转功能,留给大家发挥,可以用射线检测,也可以用link跳转,随意

最终效果

2023-04-28 18.03.58.gif

文档

祝大家五一快乐

历史文章

# Javascript基础之写一个好玩的点击效果

# Javascript基础之鼠标拖拉拽

# three.js 打造游戏小场景(拾取武器、领取任务、刷怪)

# threejs 打造 world.ipanda.com 同款3D首页

# three.js——物理引擎

# three.js——镜头跟踪

# threejs 笔记 03 —— 轨道控制器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙华鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值