【前端转3D】去不了夏威夷,那我就用three实现一个

大家好,我是日拱一卒的攻城师不浪,专注可视化、数字孪生、前端、nodejs、AI学习、GIS等学习沉淀,这是2024年输出的第21/100篇文章;

前言

静态页面写倦了,就来玩玩3D,你会发现又开启了一条新大陆~
请添加图片描述
好了,废话也不多说了,今天我们来用three从零到一实现一个夏威夷度假海岛,假装自己去了夏威夷…

场景初始化

// 场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000)
// 设置相机位置
camera.position.set(-50, 50, 130);
// 更新摄像头宽高比例,换句话说就是分辨率
camera.aspect = window.innerWidth / window.innerHeight
// 更新摄像头投影矩阵
camera.updateProjectionMatrix();
// 相机添加进场景中
scene.add(camera)

选择three的渲染引擎

这里我们直接选择WebGL渲染引擎;

const renderer = new THREE.WebGLRenderer({
  antialias: true, // 抗锯齿
  logarithmicDepthBuffer: true, // 对数深度缓存,提高性能
})
// 将输出canvas的大小调整为(window.innerWidth, window.innerHeight)并考虑设备像素比
renderer.setSize(window.innerWidth, window.innerHeight)

抗锯齿一定要设置为true,不然绘制出来的几何体会张牙舞爪~

请添加图片描述

帧渲染动画

很重要,three在浏览器里的动画渲染都是经过每一帧进行不断的渲染才形成的,所以用到了浏览器(js)里的帧动画函数requestAnimationFrame

const render = () => {
  // 场景渲染
  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

canvas挂载

由于我们是在vue中进行的渲染,所以需要在dom节点渲染完成之后才能将canvas节点进行挂载,所以这些操作都要在onMounted生命周期中进行。

<template>
  <div ref="container"></div>
</template>
const container = ref()
onMounted(() => {
  // 添加轨道控制器,可以使得相机围绕目标进行轨道运动
  const controls = new OrbitControls(camera, container.value)
  // 阻尼效果
  controls.enableDamping = true
  container.value.appendChild(renderer.domElement)
  render()
})

绘制天空

请添加图片描述

把整个场景放置到一个球体当中,形成一个360度环绕视觉感

// 绘制球体
const skyGeo = new THREE.SphereGeometry(1000, 60, 60);
// 添加天空材质
const skyTex = new THREE.TextureLoader().load("/images/sky.jpg")
const skyMat = new THREE.MeshBasicMaterial({
  map: skyTex
})
// 视角进入球体内部
skyGeo.scale(1, 1, -1)
// 几何体+材质形成一个场景
const sky = new THREE.Mesh(skyGeo, skyMat)
scene.add(sky)

云彩动起来

这里我们先简单搞一下,使用视频去渲染动态云彩和天空,这里的视频也可以作为球体的材质

我们还加了一个控制天空是否动态的按钮,只有点击才播放。

// 视频纹理
const createVideo = () => {
  const video = document.createElement('video')
  video.src = '/video/sky.mp4'
  video.loop = true
  window.addEventListener('click', (e) => {
    if (video.paused) {
      video.play()
      const texture = new THREE.VideoTexture(video)
      skyMat.map = texture
      skyMat.map.needsUpdate = true
    }
  })
}
// 涉及到dom渲染的,都要在onMounted生命周期执行
onMounted(() => {
  //....
  createVideo();
  render();
})

添加海岛模型

使用GLTFLoader大类,支持加载gltfglb格式类型的三维模型。

// 添加小岛模型
const loader = new GLTFLoader()
// 压缩三维模型
const dracoLoader = new DRACOLoader()
// 添加draco载入库
dracoLoader.setDecoderPath('/js/draco/')
loader.setDRACOLoader(dracoLoader)

loader.load('/glb/island2.glb', (gltf) => {
  const isLand = gltf.scene
  scene.add(isLand)
})

请添加图片描述

咦?怎么是黑的?

这也是很多three初学者经常会碰到的一个疑问,废了九牛二虎之力才把模型加载到场景中了,发现黑乎乎的一坨,跟在模型师的电脑上看到的完全不一样。

这是因为在three场景中,我们需要手动给场景添加光源,才能照亮场景中的模型等。

添加光源

这里我们直接选择平行光即可,用平行光来模拟太阳光,无限远射。

// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(-100, 100, 10)
scene.add(light)

请添加图片描述

OK,成功渲染模型。

添加水面

我们选择用一个圆形几何体CircleGeometry去构建水面区域。

// 创建水面
const waterGeo = new THREE.CircleGeometry(300, 64)

几何体构建好之后,要给它上材质,我们直接使用three官方案例里的水材质,已经为我们封装好了

import { Water } from 'three/examples/jsm/objects/Water2'
//...
const waterTexLoader = new THREE.TextureLoader()
const waterMat = new Water(waterGeo, {
  textureWidth: 1024,
  textureHeight: 1024,
  color: 0xeeeeff,
  flowDirection: new THREE.Vector2(1, 1),
  scale: 2,
  normalMap0: waterTexLoader.load('/images/Water_1_M_Normal.jpg'),
  normalMap1: waterTexLoader.load('/images/Water_2_M_Normal.jpg')
})

请添加图片描述

突然发现这水有点上天了…

此时是几何体的位置是垂直的,所以我们要调整它的坐标轴进行旋转,设置为水平方向。

这就又涉及到three轴坐标轴的知识了,在three中,坐标轴遵顼右手定则

请添加图片描述

相信现在应该有人举起了你的黄金右手了😀

如果还是不知道要怎么旋转整个水面看,新手可以在场景中添加坐标轴辅助。

scene.add(new THREE.AxesHelper(1000))

请添加图片描述

这下很明显了把,我们是不是应该绕着X轴,让它逆时针旋转90度呢。

waterMat.rotation.x = -Math.PI / 2
```![请添加图片描述](https://img-blog.csdnimg.cn/direct/53ddd5f096e048e29ecd04524102b992.png)


OK,为了让场景效果更逼真一点,我们可以把水面稍稍太高,让石头若隐若现,呈现一种朦胧美。

![](https://img-blog.csdnimg.cn/img_convert/57e187459048246db7b5a5c53df58303.png)

```js
// 水平面抬高3米淹没石头
waterMat.position.y = 3

好的,已经有那个感觉了。

但是,还是有一点点瑕疵,仔细观察一下,椰子树的树叶下边,也就是背光的地方,黑乎乎的一片,显得有点格格不入了。

这也难不倒我们,使用环境纹理去优化它!

import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'

// 载入环境纹理
const hdrLoader = new RGBELoader()
hdrLoader.loadAsync('/hdr/050.hdr').then((texture) => {
  // 材质映射
  texture.mapping = THREE.EquirectangularReflectionMapping
  scene.background = texture
  scene.environment = texture
})

THREE.EquirectangularReflectionMapping:一个材质映射模式,用于将纹理映射到3D对象上。这种映射方式特别适用于环境映射,即反射映射,它可以创建出非常逼真的反射效果。

注意:使用全景纹理可能会对性能有所影响,因为它需要对纹理进行较多的处理来适应3D模型的表面。

这下是不是就感觉舒服多了,这场景有点类似于我的世界里的场景一隅了。

【开源地址】:https://github.com/day-day-dreamer/threejs-learning/blob/public/src/views/example/island.vue

有需要进技术产品开发交流群(可视化&GIS)可以加我:brown_7778,也欢迎数字孪生可视化领域的交流合作。

最后,如果觉得文章对你有帮助,也希望可以一键三连👏👏👏,支持我持续开源和分享~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值