问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Three.js实战:Vue3项目中的海上场景渲染

创作时间:
2025-01-21 17:49:27
作者:
@小白创作中心

Three.js实战:Vue3项目中的海上场景渲染

本文将手把手教你使用Three.js在Vue3项目中创建一个逼真的海上场景,包括船模、水面效果和光照设置。如果你对Three.js和Vue3有一定了解,那么这篇文章将帮助你掌握更高级的场景渲染技巧。

初始化场景

首先,我们需要在Vue3项目中安装Three.js:

npm i three

然后,我们可以在Vue3组件中初始化场景、相机和渲染器:

<template>
  <div id="home" class="w-full h-full"></div>
</template>

<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;

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>

如果在项目中引入Three.js时遇到类型声明问题,可以在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.scene.children[0]获取模型实例。由于模型的大小和朝向可能不符合预期,我们需要对模型进行缩放和旋转处理:

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图片作为环境贴图。Three.js提供了多种纹理映射模式,其中EquirectangularReflectionMapping适用于全景环境贴图:

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; }) => {
    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);

增强光照后的效果如下:

船体模型纹理

全纹理

我们可以通过设置模型的材质来改变其外观。这里使用MeshPhongMaterial材质,并为其添加环境贴图:

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: { material: { name: string; }; }) => {
  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

Three.js提供了两种水面实现方式:WaterWater2。这里我们先介绍Water的使用方法:

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时需要注意配置normalMap0normalMap1属性:

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: '#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);
};

视口角度限制

为了限制相机的视角范围,我们可以调整OrbitControls的参数:

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();

调整后的视口效果如下:

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号