大家好,我是日拱一卒的攻城师不浪,专注可视化、数字孪生、前端、nodejs、AI学习、GIS等学习沉淀,这是2024年输出的第21/100篇文章;
前言
静态页面写倦了,就来玩玩3D
,你会发现又开启了一个新大陆~
好了,废话也不多说了,今天我们来用three
从零到一实现一个夏威夷度假海岛
,假装自己去了夏威夷...
场景初始化
dart
// 场景
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渲染引擎;
javascript
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
logarithmicDepthBuffer: true, // 对数深度缓存,提高性能
})
// 将输出canvas的大小调整为(window.innerWidth, window.innerHeight)并考虑设备像素比
renderer.setSize(window.innerWidth, window.innerHeight)
抗锯齿
一定要设置为true,不然绘制出来的几何体会张牙舞爪
~
帧渲染动画
很重要 ,three在浏览器里的动画渲染都是经过每一帧
进行不断的渲染才形成的,所以用到了浏览器(js)里的帧动画函数requestAnimationFrame。
scss
const render = () => {
// 场景渲染
renderer.render(scene, camera)
requestAnimationFrame(render)
}
canvas挂载
由于我们是在vue
中进行的渲染,所以需要在dom节点
渲染完成之后才能将canvas节点
进行挂载,所以这些操作都要在onMounted生命周期中进行。
scss
<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度环绕视觉感
arduino
// 绘制球体
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)
云彩动起来
这里我们先简单搞一下,使用视频
去渲染动态云彩和天空,这里的视频也可以作为球体的材质
。
我们还加了一个控制天空是否动态的按钮,只有点击才播放。
ini
// 视频纹理
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 大类,支持加载gltf
和glb
格式类型的三维模型。
javascript
// 添加小岛模型
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场景中,我们需要手动给场景添加光源,才能照亮场景中的模型等。
添加光源
这里我们直接选择平行光
即可,用平行光来模拟太阳光,无限远射。
csharp
// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(-100, 100, 10)
scene.add(light)
OK,成功渲染模型。
添加水面
我们选择用一个圆形几何体CircleGeometry去构建水面区域。
arduino
// 创建水面
const waterGeo = new THREE.CircleGeometry(300, 64)
几何体构建好之后,要给它上材质,我们直接使用three官方案例里的水材质,已经为我们封装好了
javascript
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中,坐标轴遵顼右手定则
。
相信现在应该有人举起了你的黄金右手了😀
如果还是不知道要怎么旋转整个水面看,新手可以在场景中添加坐标轴辅助。
csharp
scene.add(new THREE.AxesHelper(1000))
这下很明显了把,我们是不是应该绕着X轴
,让它逆时针旋转90度呢。
ini
waterMat.rotation.x = -Math.PI / 2
OK,为了让场景效果更逼真一点,我们可以把水面稍稍太高,让石头若隐若现,呈现一种朦胧美。
arduino
// 水平面抬高3米淹没石头
waterMat.position.y = 3
好的,已经有那个感觉了。
但是,还是有一点点瑕疵,仔细观察一下,椰子树的树叶下边,也就是背光的地方,黑乎乎的一片,显得有点格格不入了。
这也难不倒我们,使用环境纹理去优化它!
javascript
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模型的表面。
这下是不是就感觉舒服多了,这场景有点类似于我的世界
里的场景一隅了。
【开源地址】:github.com/day-day-dre...
有需要进技术产品开发交流群(可视化&GIS)可以加我:brown_7778,也欢迎
数字孪生可视化领域
的交流合作。
最后,如果觉得文章对你有帮助,也希望可以一键三连👏👏👏,支持我持续开源和分享~