Three.js 实战【2】—— 船模型海上场景渲染

停止了好久没有更新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();

相关推荐

  1. Three CSS2D 渲染器 月球绕地球旋转

    2024-07-18 12:14:03       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 12:14:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 12:14:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 12:14:03       58 阅读
  4. Python语言-面向对象

    2024-07-18 12:14:03       69 阅读

热门阅读

  1. 支付平台系统遭遇黑客攻击怎么办

    2024-07-18 12:14:03       23 阅读
  2. MySQL 实现模糊匹配

    2024-07-18 12:14:03       24 阅读
  3. Linux 驱动开发

    2024-07-18 12:14:03       25 阅读
  4. LeetCode 227. 基本计算器 II

    2024-07-18 12:14:03       22 阅读
  5. 如何实现MySQL的高可用

    2024-07-18 12:14:03       23 阅读
  6. docker安装指导

    2024-07-18 12:14:03       20 阅读
  7. 使用lxml库提取HTML中a标签的href和文本内容

    2024-07-18 12:14:03       23 阅读
  8. 一些数据库专家称,最新的 MySQL 版本令人失望

    2024-07-18 12:14:03       26 阅读
  9. NLP篇8 自然语言处理 使用注意力模型

    2024-07-18 12:14:03       20 阅读
  10. 测试用例设计方法

    2024-07-18 12:14:03       16 阅读
  11. Mongodb使用索引进行查询优化

    2024-07-18 12:14:03       22 阅读
  12. 计算机视觉——OpenCV C++实现凸包

    2024-07-18 12:14:03       22 阅读