停止了好久没有更新three这方面的文章了,从上两年还是vue2,一下子都换到vue3了,下面这些three都是基于vue3来进行开发的哈,先看一下这篇文章实现的效果哈。其中关于模型什么的资源都放在Git上了
初始化场景
安装three就直接通过npm i three
安装到一个vue3项目当中即可。其中很多的内容可以参考该文:单击前往
<script lang="ts" setup>
import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls';
// 创建场景、创建相机
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(1, 1, 1);
scene.add(camera);
// 环境光
const ambientLight = new THREE.AmbientLight('white', 1);
scene.add(ambientLight);
const light = new THREE.DirectionalLight(0xffffff, 3);
scene.add(light);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// xyz辅助坐标系
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 渲染
const render = () => {
renderer.render(scene, camera);
requestAnimationFrame(render);
};
onMounted(() => {
document.getElementById('home')?.appendChild(renderer.domElement);
render();
});
</script>
<template>
<div id="home" class="w-full h-full"></div>
</template>
这里是vue3+ts搭建的项目,如果引入的three标红的话可以在根目录下的env.d.ts
文件当中添加:
declare module 'three'
declare module 'three/examples/jsm/objects/Water2';
declare module 'three/examples/jsm/controls/OrbitControls';
// .... 还有什么别的就继续往里面加
初始化场景的效果:
加载模型
首先是获取模型的地址,可以在sketchfab获取一下glb或者gltf文件格式的模型。通过GLTFLoader加载glb模型,glb模型和gltf模型还是有点区别的,glb模型在响应之后需要通过gltf.scene.children[0]
获取的才是模型。因为模型展示的太大了和朝向也不是理想的,这里对模型也进行了一些处理
- 缩放scale
- 旋转rotation
let model: {
scale: { set: (arg0: number, arg1: number, arg2: number) => void; };
rotation: { z: number; };
traverse: (arg0: (item: { material: { name: string; }; }) => void) => void;
position: { z: number; };
};
const addShip = () => {
const gltfLoader = new GLTFLoader();
gltfLoader.load('./src/assets/glb/pirate_ship.glb', (gltf: any) => {
model = gltf.scene.children[0];
const scale = 0.001;
model.scale.set(scale, scale, scale);
model.rotation.z = Math.PI;
scene.add(model);
});
};
渲染场景贴图
因为没有设置场景的背景,这个时候都是黑色的,这个可以直接加在一张HDR图片作为整个场景的外围贴图,这个和上一篇文章的加载HDR是一样的,就不过多赘述了。
纹理常量:映射模式
- UVMapping 是默认值,纹理使用网格的坐标来进行映射
- CubeReflectionMapping 和 CubeRefractionMapping 用于 CubeTexture —— 由6个纹理组合而成,每个纹理都是立方体的一个面。
- EquirectangularReflectionMapping 和 EquirectangularRefractionMapping 用于等距圆柱投影的环境贴图,也被叫做经纬线映射贴图。等距圆柱投影贴图表示沿着其水平中线360°的视角,以及沿着其垂直轴向180°的视角。贴图顶部和底部的边缘分别对应于它所映射的球体的北极和南极。
import {RGBELoader} from 'three/examples/jsm/loaders/RGBELoader';
const rgbLoader = new RGBELoader();
const addHdr = () => {
rgbLoader.loadAsync('./src/assets/hdr/sea_2k.hdr').then((texture: { mapping: any; }) => {
// 图像将如何应用到物体(对象)上。默认值是THREE.UVMapping对象类型, 即UV坐标将被用于纹理映射。
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = texture;
scene.environment = texture;
});
};
光照增强
虽然外围场景也加上了,但是船体还是黑乎乎的一片,原因在于前面设置的光照强度不够,将强度拉上来就可以看到船的颜色细节都渲染出来了。
const ambientLight = new THREE.AmbientLight('white', 50);
ambientLight.position.set(10, 10, 10);
scene.add(ambientLight);
const light = new THREE.DirectionalLight('#fff', 50);
light.position.set(10, 10, 10);
scene.add(light);
船体模型纹理
有些时候我们想给模型设置一下别的纹理上去,那这个船体的纹理怎么设置呢?
全纹理
直接给model模型的material设置一个纹理,通过纹理加载器加载一张图片,并且设置其为EquirectangularRefractionMapping
网格材质 MeshPhongMaterial:该材质使用非物理的Blinn-Phong模型来计算反射率。 可以模拟具有镜面高光的光泽表面(例如涂漆木材)
- envMap:环境贴图
- refractionRatio:空气的折射率(IOR)(约为1)除以材质的折射率。它与环境映射模式CubeRefractionMapping和 EquirectangularRefractionMapping一起使用。
- reflectivity:环境贴图对表面的影响程度
- wireframe:将几何体渲染为线框
const addTexture = () => {
const textureLoader = new THREE.TextureLoader().load('./src/assets/image/bg.jpg');
textureLoader.mapping = THREE.EquirectangularRefractionMapping;
return textureLoader;
};
model.material = new THREE.MeshPhongMaterial({
color: 0xffffff,
envMap: addTexture(),
refractionRatio: 0.75,
reflectivity: 0.99
});
分块纹理
在model模型当中还可以通过traverse
方法去遍历对象或场景中的所有后代对象。拿到item之后我们通常会通过name属性去区分模型的块,比方说现在加载的模型的name取值是:Main、Sail、Mat、Polygon_Reduction_1__0、material这些,对应到模型就是主体、船帆、甲板、发动机、绳索,这里直接添加简单的颜色纹理上去看一下效果
// 加载模型之后通过该方法给模型当中不同的块进行材质的修改
model.traverse((item: { material: { name: string; }; }) => {
// Mat Sail Main Polygon_Reduction_1__0 material
const name = item.material?.name || '';
if (name.includes('Main')) {
item.material = colorMaterial('#e7a23f');
} else if (name.includes('Sail')) {
item.material = colorMaterial('#fff');
} else if (name.includes('Mat')) {
item.material = colorMaterial('#826b48');
} else if (name.includes('Polygon_Reduction_1__0')) {
item.material = colorMaterial('#f40');
} else if (name.includes('material')) {
item.material = colorMaterial('#000');
}
});
const colorMaterial = (color: string) => {
return new THREE.MeshLambertMaterial({
color
});
};
加载水面(Water&Water2)
Water
-
首先是导入Water,在three当中有一个Water和一个Water2,需要注意区分一下
-
平面缓冲几何体(PlaneGeometry)用来作为水面的载体
-
创建water对象,其中属性当中主要是对纹理进行相关配置,主要是wrapS和wrapT
-
wrapS这个值定义了纹理贴图在水平方向上将如何包裹,默认值是THREE.ClampToEdgeWrapping,即纹理边缘将被推到外部边缘的纹素。 其它的两个选项分别是THREE.RepeatWrapping和THREE.MirroredRepeatWrapping。
- ClampToEdgeWrapping 纹理中的最后一个像素将延伸到网格的边缘
- RepeatWrapping 纹理将简单地重复到无穷大
- MirroredRepeatWrapping 纹理将重复到无穷大,在每次重复时将进行镜像
-
wrapT这个值定义了纹理贴图在垂直方向上将如何包裹
-
在最后需要通过water.material.uniforms[‘time’].value += 1.0 / 60.0启动水的运动动画
import {Water} from 'three/examples/jsm/objects/Water';
let water: any;
const addWater = () => {
// 创建水面
const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
water = new Water(
waterGeometry,
{
textureWidth: 512,
textureHeight: 512,
waterNormals: new THREE.TextureLoader().load('./src/Water.jpg', (texture: any) => {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
}),
sunDirection: new THREE.Vector3(),
sunColor: 0xffffff,
waterColor: 0xffffff,
distortionScale: 3.7
}
);
water.rotation.x = -Math.PI / 2;
water.position.y = -0.4;
scene.add(water);
};
const render = () => {
renderer.render(scene, camera);
water.material.uniforms['time'].value += 1.0 / 60.0;
requestAnimationFrame(render);
};
Water2
在使用water2需要注意的是normalMap0和normalMap1配置,没有配的话会报错。这是因为
源码中是:const normalMap0 = options.normalMap0 || textureLoader.load( ‘textures/water/Water_1_M_Normal.jpg’ );
但是three源码并没有这个两个纹理图片。所以可以通过手动给这两个属性赋值。
另一种方式是,直接将这两个图片放在:public/textures/water目录下,因为public下的文件是不会被编译的,这样也能找到这两个图片了。
import {Water} from 'three/examples/jsm/objects/Water2';
const addWater = () => {
// 创建水面
const waterGeometry = new THREE.CircleBufferGeometry(300, 64);
const water = new Water(waterGeometry, {
textureWidth: 1024,
textureHeight: 1024,
// color: 0x0080ff,
color: '#fff',
flowDirection: new THREE.Vector2(1, 1),
scale: 1,
reflectivity: 0.3,
normalMap0: new THREE.TextureLoader().load('./src/Water.jpg'),
normalMap1: new THREE.TextureLoader().load('./src/Water.jpg')
});
water.rotation.x = -Math.PI / 2;
water.position.y = -0.4;
scene.add(water);
};
视口角度限制
在实际开发过程当中会发现,我们不想整个视口是可以360度和无论多远多近都能看到的,视口应该在一个合理的范围当中。这时需要调整一下controls。
OrbitControls (轨道控制器)可以使得相机围绕目标进行轨道运动。
- enableDamping 将其设置为true以启用阻尼(惯性),这将给控制器带来重量感。
- maxPolarAngle 你能够垂直旋转的角度的上限,范围是0到Math.PI,其默认值为Math.PI。
- minDistance 你能够将相机向内移动多少,默认为0。
- maxDistance 你能够将相机向外移动多少,和minDistance仅适用于PerspectiveCamera 透视相机
- update 更新控制器。必须在摄像机的变换发生任何手动改变后调用,
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.maxPolarAngle = Math.PI * 0.45;
controls.minDistance = 5.0;
controls.maxDistance = 15.0;
controls.update();