用three.js从零做一个海滩小岛
1.初始化
初始化呢在我之前的文章里有详细的描述,跟着步骤走就好啦,特别快就能弄好:three.js学习(1)
2.加入场景、相机
这里我们先把css代码改一点:
* {
margin: 0;
padding: 0;
}
body {
background-color: #000000;
}
::-webkit-scrollbar {
display: none;
}
一切的3D的画面都来自场景,想要看见物体需要相机。
import * as THREE from '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)
3.创建渲染器
最终要将它们渲染出来,所以需要一个渲染器renderer。
//初始化渲染器
const renderer = new THREE.WebGLRenderer({
//为了更好的效果,设置为 抗锯齿
antialias: true,
})
//设置渲染器输出环境的编码 为了好看一些
renderer.outputEncoding = THREE.sRGBEncoding
//设置渲染器宽高
renderer.setSize(window.innerWidth, window.innerHeight)
//监听屏幕的大小改变,修改渲染器的宽高,相机的比例
window.addEventListener("resize",()=>{
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
//渲染器最终会渲染出一个canvas画布 在renderer.domElement
//将渲染器的画布添加到页面上
document.body.appendChild(renderer.domElement)
//我们需要不断地渲染画面,才能让画面动起来
//因此这里写一个渲染函数
const render = ()=>{
//渲染场景,相机
renderer.render(scene,camera)
//引擎自动更新渲染器 通过调用‘请求动画帧requestAnimationFrame()’不断渲染
requestAnimationFrame(render)
}
//调用渲染函数
render()
4.添加控制器
想要实现拖动我们的3D场景的话,我们需要添加控制器。
//导入控制器
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
//实例化控制器 导入我们的相机和渲染的画布
const controls = new OrbitControls(camera,renderer.domElement)
5.添加内容
1.天空
这里呢,我们要先跑一下程序yarn dev,这样他会自己打包出一个dist文件夹来。接着我们在dist文件夹中创建一个textures文件夹来放材质。
模拟我们的地球呢,我们也创造出一个大球来。然后将贴图贴上去。这样就好像置身于内部。
//创建一个巨大的天空球体 参数:半径 细分程度(相当于我们看地图的时候 经度 纬度)
const skyGeometry = new THREE.SphereGeometry(1000, 60, 60) //创建几何体
const skyMaterial = new THREE.MeshBasicMaterial({ //创建材质
//使用纹理加载器加载纹理 也就是一张天空的图片
map: new THREE.TextureLoader().load('./textures/sky.jpg')
})
//由于正常的创建,一个几何体只有外围是能看见的(为了节省资源)
//所以当我们置身于球的内部,是看不见东西的,因此需要将几何体的z轴(我们眼睛看的方向)颠倒
skyGeometry.scale(1, 1, -1)
//创建天空球 几何体+材质
const sky = new THREE.Mesh(skyGeometry, skyMaterial)
//加入场景中
scene.add(sky)
不过嘞,像我们在网上找的图片大部分情况下,最左端和最右端是不相接的,所以会有一个缝合的边线哈哈。如果想要360°的效果的话,可以去找或者自己去用那种 鱼眼摄像机 拍一个360°的天空。(试了一下好像也是怪怪的QAQ)
当然,如果有一段会动的天空的视频的话,加进来效果也是杠杠的。最好是找一个跟图片一样的视频这样就很连贯,而且记住视频是有声音的哦 连声音都会播放。
//视频纹理
const video = document.createElement('video') //创建视频
video.src = './textures/sky.mp4'
video.loop = true //循环播放
//由于浏览器会禁止我们直接进行循环播放视频,所以我们可以加一点交互
window.addEventListener('mousemove', (e) => {
//当鼠标移动的时候,播放视频
if (video.paused) { //判断是否暂停状态
video.play()
skyMaterial.map = new THREE.VideoTexture(video) //将天空材质的纹理从图片变为视频
skyMaterial.map.needsUpdate = true //自动更新
}
})
2.水面
three.js呢,有给我们提供一个水面的设置嘿嘿。
//导入水面
import {Water} from 'three/examples/jsm/objects/Water2'
//创建水面
const waterGeometry = new THREE.CircleGeometry(300, 64) //圆形几何体
const water = new Water(waterGeometry, {
textureHeight: 1024,
textureWidth: 1024,
color: 0x0080ff,
flowDirection: new THREE.Vector2(1, 1), //流动方向
scale: 1, //水面波纹的大小
})
//将水面旋转90度,把它放平
// water.rotation.x = -Math.PI / 2
scene.add(water)
我试了超久!找不到资料,我的水面是没有波纹的QAQ 哭死 不知道为啥,可能是更新了,我询问了一位博主,他说是没有加贴图,但我查了很多资料都是直接flowDirection就可以了,而且flowDirection和flowMap不能同时存在。我也尝试了加贴图,不过好像还是没有效果。有没有大佬帮忙解答疑难TAT
这个问题还待解决,目前我不确定是我电脑的问题还是其他问题,解决的话会更新文章,先继续学习加入模型的部分
3.小岛
那么小岛的模型它是一种GLB类型的文件。这是一个压缩过的模型,不然会太大了。所以呢一会儿得导入一个用来解压的库。在dist中呢创建一个draco文件夹,放入解压库。建立一个model文件夹来放.glb模型。
Draco是谷歌团队在2017年1月发布的一个Draco是用于压缩和解压缩3D几何网格和点云的库。
//导入gltf载入库
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
//导入解压库
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
//添加小岛模型
//实例化gltf载入库
const loader = new GLTFLoader()
//实例化解压库
const dracoLoader = new DRACOLoader()
//添加draco载入库
dracoLoader.setDecoderPath('./draco/')
loader.setDRACOLoader(dracoLoader)
//添加模型
loader.load("./model/island2.glb", (gltf) => {
scene.add(gltf.scene);
});
ok 那么这么加进来的模型呢,我们会发现是黑的,为什么呢,因为我们的天空不是一个hdr的图片只是一个jpg,它是没有光照属性的,因此需要引入一个hdr的环境纹理。
//RGBELoader用于导入HDR图
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'
//载入环境纹理hdr
const hdrLoader = new RGBELoader()
hdrLoader.loadAsync("./assets/050.hdr").then((texture) => {
//纹理映射 球面映射
texture.mapping = THREE.EquirectangularReflectionMapping
scene.background = texture
scene.environment = texture
})
//添加平行光 提高亮度
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(-100, 100, 10)
scene.add(light)
当然也可以不加上面的hdr,只加个平行光也行,只不过就没那么真实。