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

news2024/9/23 15:25:18

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1934005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java——集合(Queue)

1.Queue 接口的常用功能 除了基本的 Collection 操作外&#xff0c;队列还提供其他的插入、提取和检查操作。每个方法都存在 两种形式&#xff1a;一种抛出异常&#xff08;操作失败时&#xff09;&#xff0c;另一种返回一个特殊值&#xff08; null 或 false &#xff…

RPA鼠标按键使用技巧

RPA鼠标按键使用技巧 Mouse.MouseAuto.Action命令出错&#xff0c;调用的目标发生了异常&#xff0c;Exception in Mouse.Action元素不可用怎么解决 出现问题 1.想要实现的效果鼠标移动到录屏工具的小球上2.点击开始按钮开始录屏现象&#xff0c;鼠标没有移动痕迹&#xff0c…

爬虫案例(读书网)(下)

上篇链接&#xff1a; CSDN-读书网https://mp.csdn.net/mp_blog/creation/editor/139306808 可以看见基本的全部信息&#xff1a;如(author、bookname、link.....) 写下代码如下&#xff1a; import requests from bs4 import BeautifulSoup from lxml import etreeheaders{…

SSD实现

一、模型 此模型主要由基础网络组成&#xff0c;其后是几个多尺度特征块。基本网络用于从输入图像中提取特征&#xff0c;因此它可以使用深度卷积神经网络。 单发多框检测选用了在分类层之前截断的VGG&#xff0c;现在也常用ResNet替代&#xff1b;可以设计基础网络&#xff0c…

【LeetCode】162. 寻找峰值

1. 题目 2. 分析 这道题的难点有二&#xff1a;第一&#xff0c;知道用二分法求解&#xff1b;第二&#xff0c;二分判断的标准是什么&#xff1f;传统的题目的二分标注都是跟某个固定的值做比较&#xff0c;但是此题不然。此题的比较对象是相邻的元素。 不要硬凭自己的脑子…

spring是如何解决循环依赖的,为什么不是两级

1. Spring使用三级缓存来解决循环依赖问题 Spring使用三级缓存来解决循环依赖问题&#xff0c;‌而不是使用两级缓存。‌ 在Spring框架中&#xff0c;‌解决循环依赖的关键在于正确地管理Bean的生命周期和依赖关系。‌循环依赖指的是两个或多个Bean相互依赖&#xff0c;‌如果…

FastApi地理坐标数据存取实践

说明&#xff1a; 应用Pydantic Model 验证/出入 数据&#xff0c; SqlAlchemy Model数据实体&#xff0c;Fastapi提供API机制支持。数据表的坐标字段采用Mysql的GEOMETRY类型目前还没成功使用Pydantic的Coordinate类型&#xff0c;待后续改良 要点&#xff1a; 输出的结果是…

多级表头固定列问题

父级的width&#xff0c;是需要固定的列的width的总和 参考&#xff1a; el-table 多级表头下对应列的固定

Android Studio 不再支持windows 7

Android Studio 一打开就报错&#xff1a; 无法找到入口 无法定位程序输入点 CreateAppContainerProfle 于动态链接库USERENV.dII 上。 截图如下&#xff1a; 经调查&#xff0c;是因为系统版本不兼容。 我目前的电脑环境&#xff1a;windows 7,但是现在的Android Studio要…

leetcode145. 二叉树的后序遍历,递归法+迭代法,全过程图解+步步解析,一点点教会你迭代法后序遍历

leetcode145. 二叉树的后序遍历&#xff0c;递归法迭代法 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#…

Java学习高级四

JDK8开始&#xff0c;接口新增了三种形式的方法 接口的多继承 内部类 成员内部类 静态内部类 局部内部类 匿名内部类 import javax.swing.*; import java.awt.event.ActionEvent;public class Test {public static void main(String[] args) {// 扩展 内部类在开发中的真实使用…

H264解码器实现-帧间预测之MV预测

1.前言 本文章所说的MV预测是指计算当前块MV向量的预测值&#xff0c;该值与码流中传输的MV残差值相加即可得到实际的MV向量。请注意&#xff0c;在某些宏块类型的某种情况下是不需要进行MV预测的&#xff0c;他们的MV可以通过其他方法得出&#xff0c;本文只介绍MV预测过程。…

21.x86游戏实战-实现注入器

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

C++:模板编程入门

什么是模板编程 假设我们需要实现对整形、浮点型、双精度型数据的交换函数&#xff0c;一般情况我们需要重载三个函数&#xff0c;但是使用模板&#xff0c;我们使用一个函数就能解决。 函数在编译时进行重载。 #include <iostream>using namespace std;template <t…

leetcode热题100.分割等和子集(动态规划)

分割等和子集 Problem: 416. 分割等和子集 思路 我选择使用动态规划的方法来解题。我们需要判断是否可以将数组分割成两个子集&#xff0c;使得这两个子集的和相等。这个问题可以转化为在数组中找到一个子集&#xff0c;使得其和等于数组总和的一半。 解题过程 首先&#xf…

Linux常见配置

linux 常见配置 一、配置固定IP, 主机名映射二、配置环境变量三、vim配置四、ssh配置 一、配置固定IP, 主机名映射 1、修改主机名 hostnamectl set-hostname xxx2、Centos配置固定IP 使用vim编辑/etc/sysconfig/network-scripts/ifcfg-ens33文件&#xff0c;填入下图信息 …

SpringCloud教程 | 第九篇: 使用API Gateway

1、参考资料 SpringCloud基础篇-10-服务网关-Gateway_springcloud gateway-CSDN博客 2、先学习路由&#xff0c;参考了5.1 2.1、建了一个cloudGatewayDemo&#xff0c;这是用来配置网关的工程&#xff0c;配置如下&#xff1a; http://localhost:18080/aaa/name 该接口代码如…

c++初阶知识——内存管理与c语言内存管理对比

目录 前言&#xff1a; 1.c&#xff0b;&#xff0b;内存管理方式 1.1 new和delete操作自定义类型 2.operator new与operator delete函数 2.1 operator new与operator delete函数 3.new和delete的实现原理 3.1 内置类型 3.2 自定义类型 new的原理 delete的原理 new…

浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用(AI智能体)

浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用 ygluu 卢益贵 关键词&#xff1a;UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂&#xff1a;AI&#xff08;AIGC/ChatGPT&#xff09;与流程式游戏开发》讨论之后就…

ctfshow-web入门-php特性(web127-web131)

目录 1、web127 2、web128 3、web129 4、web130 5、web131 1、web127 代码审计&#xff1a; $ctf_show md5($flag); 将 $flag 变量进行 MD5 哈希运算&#xff0c;并将结果赋值给 $ctf_show。 $url $_SERVER[QUERY_STRING]; 获取当前请求的查询字符串&#xff08;que…