ThreeJS-圣诞节表白3D贺卡(三十)
素材分享:
链接: https://pan.baidu.com/s/1l0mZWfkiLaXJfdvZ7XoY8w
提取码: i69h
提前预知:
向下滚动鼠标滑轮切换视角
关键代码:
//初始化渲染器
const render = new THREE.WebGLRenderer({
//设置抗锯齿,防失真
antialis: true,
//对数深度缓冲区,防止模型闪烁
logarithmicdepthbuffer: true,
});
/*设置场景渲染编码threejs将贴图的编码都默认设置为THREE.LinearEncoding,
*导致图片色彩失真(色彩不像正常那么鲜艳,会灰蒙蒙的),所以务必将场景中的所有贴图的编码都调整为THREE.sRGBEncoding
*/
render.outputEncoding = THREE.sRGBEncoding;
//设置曝光类型(电影类型、文本类型、游戏类型),电影类型
render.toneMapping = THREE.ACESFilmicToneMapping;
//曝光强度
render.toneMappingExposure = 0.5;
//开启物理灯光
render.physicallyCorrectLights = true;
//创建轨道控制器,可以拖动,控制的是摄像头
const controls = new OrbitControls(camera, render.domElement);
//控制器焦点,需要随着相机的更新而更新
controls.target.set(-8, 2, 0);
//设置控制阻尼,让控制器有更真实的效果
controls.enableDamping = true;
完整代码:
<template>
<div id="three_div"></div>
</template>
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { Water } from "three/examples/jsm/objects/Water2";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import gsap from "gsap";
export default {
name: "HOME",
components: {
// vueQr,
// glHome,
},
data() {
return {};
},
mounted() {
//使用控制器控制3D拖动旋转OrbitControls
//控制3D物体移动
//1.创建场景
const scene = new THREE.Scene();
//2.创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
20000
);
//设置相机位置
camera.position.set(0, 0, 10);
//将相机添加到场景
scene.add(camera);
//初始化渲染器
const render = new THREE.WebGLRenderer({
//设置抗锯齿,防失真
antialis: true,
//对数深度缓冲区,防止模型闪烁
logarithmicdepthbuffer: true,
});
/*设置场景渲染编码threejs将贴图的编码都默认设置为THREE.LinearEncoding,
*导致图片色彩失真(色彩不像正常那么鲜艳,会灰蒙蒙的),所以务必将场景中的所有贴图的编码都调整为THREE.sRGBEncoding
*/
render.outputEncoding = THREE.sRGBEncoding;
//设置渲染器的尺寸
render.setSize(window.innerWidth, window.innerHeight);
//清除默认设置颜色
render.setClearColor("#000");
//设置曝光类型(电影类型、文本类型、游戏类型),电影类型
render.toneMapping = THREE.ACESFilmicToneMapping;
//曝光强度
render.toneMappingExposure = 0.5;
//开启物理灯光
render.physicallyCorrectLights = true;
//创建轨道控制器,可以拖动,控制的是摄像头
const controls = new OrbitControls(camera, render.domElement);
//控制器焦点,需要随着相机的更新而更新
controls.target.set(-8, 2, 0);
//设置控制阻尼,让控制器有更真实的效果
controls.enableDamping = true;
//将webgl渲染的canvas内容添加到body上
document.getElementById("three_div").appendChild(render.domElement);
//渲染下一帧的时候就会调用回调函数
let renderFun = () => {
//更新阻尼数据
controls.update();
//需要重新绘制canvas画布
render.render(scene, camera);
//监听屏幕刷新(60HZ,120HZ),每次刷新触发一次requestAnimationFrame回调函数
//但是requestAnimationFrame的回调函数注册生命只有一次,因此需要循环注册,才能达到一直调用的效果
window.requestAnimationFrame(renderFun);
};
// window.requestAnimationFrame(renderFun);
renderFun();
//添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1);
scene.add(light);
//加载模型
const sceneLoader = new GLTFLoader().setPath("three/glb/");
//创建解码器
const dencoderLoader = new DRACOLoader().setDecoderPath(
"three/draco/gltf/"
);
sceneLoader.setDRACOLoader(dencoderLoader);
sceneLoader.load("christmas .glb", (loader) => {
console.log(loader);
const model = loader.scene;
model.traverse((child) => {
//隐藏模型水面
if (child.name == "Plane") {
child.visible = false;
}
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(model);
});
//加入自己的水面
const waterPlane = new THREE.CircleBufferGeometry(400, 100);
const textureLoader = new THREE.TextureLoader();
const waterMesh = new Water(waterPlane, {
textureWidth: 1024,
textureHeight: 1024,
color: 0xeeeeff,
flowDirection: new THREE.Vector2(1, 1),
scale: 100,
flowMap: textureLoader.load("three/textures/water/Water_1_M_Flow.webp"),
normalMap1: textureLoader.load(
"three/textures/water/Water_1_M_Normal.webp"
),
normalMap2: textureLoader.load(
"three/textures/water/Water_2_M_Normal.webp"
),
});
waterMesh.rotation.x = -Math.PI / 2;
waterMesh.position.y = -0.7;
scene.add(waterMesh);
//添加场景背景
const rgbeLoader = new RGBELoader();
rgbeLoader.loadAsync("three/christmas-sky.hdr").then((load) => {
//将用于等距圆柱投影的环境贴图,也被叫做经纬线映射贴图。
//等距圆柱投影贴图表示沿着其水平中线360°的视角,以及沿着其垂直轴向180°的视角。
//贴图顶部和底部的边缘分别对应于它所映射的球体的北极和南极。
load.mapping = THREE.EquirectangularReflectionMapping;
scene.background = load;
scene.environment = load;
});
//屋子创建灯光
const hotelLight = new THREE.PointLight(0xffffff, 1);
hotelLight.position.set(0, 2.4, 0);
scene.add(hotelLight);
render.shadowMap.enabled = true;
//开启灯光动态投影
hotelLight.castShadow = true;
//创建灯光组
const lightGroup = new THREE.Group();
lightGroup.position.set(-8, 2.5, -1.5);
for (let index = 0; index < 3; index++) {
const geometry = new THREE.SphereBufferGeometry(0.2, 50, 5);
const material = new THREE.MeshBasicMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveintensity: 10,
});
const mesh = new THREE.Mesh(geometry, material);
const light = new THREE.PointLight(0xffff, 1);
mesh.add(light);
mesh.position.set(
Math.cos(Math.random()) * 3,
Math.cos(Math.random()) * 1,
Math.sin(Math.random()) * 3
);
lightGroup.add(mesh);
}
console.log("组", lightGroup);
scene.add(lightGroup);
let options = {
angle: 0,
};
gsap.to(options, {
angle: Math.PI * 2,
duration: 10,
repeat: -1, //无线循环
ease: "linear",
onUpdate: () => {
lightGroup.children.forEach((elem, index) => {
elem.position.set(
Math.cos(options.angle + (Math.PI / 2) * index) * 3,
Math.cos(3 * options.angle) * 1,
Math.sin(options.angle + (Math.PI / 2) * index) * 3
);
});
},
});
// 使用补间动画移动相机
let timeLine1 = gsap.timeline();
let timeline2 = gsap.timeline();
// 定义相机移动函数
function translateCamera(position, target) {
timeLine1.to(camera.position, {
x: position.x,
y: position.y,
z: position.z,
duration: 1,
ease: "power2.inOut",
});
timeline2.to(controls.target, {
x: target.x,
y: target.y,
z: target.z,
duration: 1,
ease: "power2.inOut",
});
}
// 实例化创建漫天星星
let starsInstance = new THREE.InstancedMesh(
new THREE.SphereGeometry(0.1, 32, 32),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 10,
}),
100
);
// 星星随机到天上
let starsArr = [];
let endArr = [];
for (let i = 0; i < 100; i++) {
let x = Math.random() * 100 - 50;
let y = Math.random() * 100 - 50;
let z = Math.random() * 100 - 50;
starsArr.push(new THREE.Vector3(x, y, z));
let matrix = new THREE.Matrix4();
matrix.setPosition(x, y, z);
starsInstance.setMatrixAt(i, matrix);
}
scene.add(starsInstance);
// 创建爱心路径
let heartShape = new THREE.Shape();
heartShape.moveTo(25, 25);
heartShape.bezierCurveTo(25, 25, 20, 0, 0, 0);
heartShape.bezierCurveTo(-30, 0, -30, 35, -30, 35);
heartShape.bezierCurveTo(-30, 55, -10, 77, 25, 95);
heartShape.bezierCurveTo(60, 77, 80, 55, 80, 35);
heartShape.bezierCurveTo(80, 35, 80, 0, 50, 0);
heartShape.bezierCurveTo(35, 0, 25, 25, 25, 25);
// 根据爱心路径获取点
let center = new THREE.Vector3(0, 2, 10);
for (let i = 0; i < 100; i++) {
let point = heartShape.getPoint(i / 100);
endArr.push(
new THREE.Vector3(
point.x * 0.1 + center.x,
point.y * 0.1 + center.y,
center.z
)
);
}
// 创建爱心动画
function makeHeart() {
let params = {
time: 0,
};
gsap.to(params, {
time: 1,
duration: 1,
onUpdate: () => {
for (let i = 0; i < 100; i++) {
let x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.time;
let y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.time;
let z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.time;
let matrix = new THREE.Matrix4();
matrix.setPosition(x, y, z);
starsInstance.setMatrixAt(i, matrix);
}
starsInstance.instanceMatrix.needsUpdate = true;
},
});
}
function restoreHeart() {
let params = {
time: 0,
};
gsap.to(params, {
time: 1,
duration: 1,
onUpdate: () => {
for (let i = 0; i < 100; i++) {
let x = endArr[i].x + (starsArr[i].x - endArr[i].x) * params.time;
let y = endArr[i].y + (starsArr[i].y - endArr[i].y) * params.time;
let z = endArr[i].z + (starsArr[i].z - endArr[i].z) * params.time;
let matrix = new THREE.Matrix4();
matrix.setPosition(x, y, z);
starsInstance.setMatrixAt(i, matrix);
}
starsInstance.instanceMatrix.needsUpdate = true;
},
});
}
let scenes = [
{
text: "圣诞快乐",
callback: () => {
// 执行函数切换位置
translateCamera(
new THREE.Vector3(-3.23, 3, 4.06),
new THREE.Vector3(-8, 2, 0)
);
},
},
{
text: "感谢在这么大的世界里遇见了你",
callback: () => {
// 执行函数切
translateCamera(
new THREE.Vector3(7, 0, 23),
new THREE.Vector3(0, 0, 0)
);
},
},
{
text: "愿与你探寻世界的每一个角落",
callback: () => {
// 执行函数切
translateCamera(
new THREE.Vector3(10, 3, 0),
new THREE.Vector3(5, 2, 0)
);
},
},
{
text: "愿将天上的星星送给你",
callback: () => {
// 执行函数切
translateCamera(
new THREE.Vector3(7, 0, 23),
new THREE.Vector3(0, 0, 0)
);
makeHeart();
},
},
{
text: "愿疫情结束,大家健康快乐!",
callback: () => {
// 执行函数切
translateCamera(
new THREE.Vector3(-20, 1.3, 6.6),
new THREE.Vector3(5, 2, 0)
);
},
},
];
let index = 0;
let isAnimate = false;
//创建滑轮滚动切换摄像头位置
window,
addEventListener("wheel", (e) => {
if (isAnimate) return;
isAnimate = true;
if (e.deltaY > 0) {
index++;
index %= scenes.length;
if(index == 0){
restoreHeart();
}
console.log(index);
}
scenes[index].callback();
setTimeout(() => {
isAnimate = false;
}, 1000);
});
//画布全屏
window.addEventListener("dblclick", () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
//document.documentElement.requestFullscreen();
render.domElement.requestFullscreen();
}
});
//监听画面变化,更新渲染画面,(自适应的大小)
window.addEventListener("resize", () => {
//更新摄像机的宽高比
camera.aspect = window.innerWidth / window.innerHeight;
//更新摄像机的投影矩阵
camera.updateProjectionMatrix();
//更新渲染器宽度和高度
render.setSize(window.innerWidth, window.innerHeight);
//设置渲染器的像素比
render.setPixelRatio(window.devicePixelRatio);
});
},
methods: {},
};
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.home-content {
position: fixed;
top: 0;
right: 20px;
}
.select-item-color {
width: 50px;
height: 50px;
border: 1px solid #ccc;
margin: 10px;
display: inline-block;
cursor: pointer;
border-radius: 10px;
}
.select {
display: flex;
}
</style>
效果图:
相关文章:

ThreeJS-圣诞节表白3D贺卡(三十)
素材分享: 链接: https://pan.baidu.com/s/1l0mZWfkiLaXJfdvZ7XoY8w 提取码: i69h 提前预知: 向下滚动鼠标滑轮切换视角 关键代码: //初始化渲染器 const render new THREE.WebGLRenderer({ //设置抗锯齿,防失真 antialis: …...

040:cesium加载World Terrain地形图
第040个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中加载世界地形图。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共64行)相关API参考:专栏目标示例效果 配置方式 1)查看基础设置:https://xiaozh…...

逻辑运算和位移指令
逻辑运算指令 AND OR NOT XOR TEST 逻辑位移指令 SHL SHR 算术位移指令 SAL SAR 小循环位移指令 ROL ROR 大循环位移指令 RCL RCR AND 逻辑与指令 汇编格式:AND 目的操作数,源操作数 执行操作:(目的操作数)&…...

大家现在都去做Linux运维了吗?
运维自互联网出现以来,都是以基础技术部门的形式出现在各个互联网公司或者其他需要网络设备的公司里面,职位由来已久,也是多次徘徊在被淘汰的边缘。很多运维人都是靠着自己良好乐观的心态坚持到现在,接受新技术并学习新技术&#…...
Webpack的编译流程是怎么样的?webpack是如何工作的?
Webpack是一款非常流行的前端构建工具,用于将多个模块打包成一个或多个静态资源。它的工作原理是将模块的依赖关系图转化为最终的静态资源。Webpack的编译流程是一个非常复杂的过程,本文将从四个方面详细介绍Webpack的编译流程,分别是入口点分…...
【ZOJ 1151】Word Reversal 题解(字符串+模拟)
问题描述 对于每个单词列表,在不改变单词顺序的情况下,将每个单词反转输出一行。 此问题包含多个测试用例! 多重输入的第一行是整数N,然后是空行,后面跟着N个输入块。每个输入块 采用问题描述中所示的格式。输入块之间…...

Dart语言操作符?和!的用法
一.基本使用 1. ? 操作符跟在类型后面,表示当前变量可为null。 int a null; //这句代码在有空安全时,编译会提示错误如果想给一个变量赋值null要如何处理呢?只需要在类型 后面添加操作符?即可,eg: int? a null…...

聚类 kmeans | 机器学习
聚类 刘建平 1、算法原理: 是一种无监督学习算法,其主要目的是将数据点分为k个簇,距离近的样本具有更高的相似度,距离近的划分为一个簇,一共划分k个簇,**让簇内距离小,簇间距离大。**距离是样…...

求职咨询Job Information
前言 加油 原文 求职咨询常用会话 ❶ I want to apply for a job which enables me to use my major. 我想要申请一个能用到我的专业知识的职业。 ❷ I have the capability of operating the computer. 我有操作电脑的能力。 ❸ My dream is to be an excellent interpret…...

怎么去除pdf文件的水印?好用软件说明
怎么去除pdf文件的水印?在某些情况下,PDF 文件的水印可能会影响文件的可读性和美观度。为了解决这个问题,您可以考虑使用其他方法来标记文档,例如添加页眉或页脚。另一种选择是使用透明度更低的水印,这样它就不会太过分…...

1-ELK+ Elasticsearch+head+kibana、企业内部日志分析系统
ELK:日志收集平台 ELK由ElasticSearch、Logstash和Kibana三个开源工具组成: 概念图 组件介绍 1、Elasticsearch: ElasticSearch是一个基于Lucene的开源分布式搜索服务。只搜索和分析日志 特点:分布式,零配置,自…...

ctfshow愚人杯web复现
easy_signin 题目url base64解码是face.png,尝试flag.txt和flag.php,base64加密后传入都不对,用index.php加密后传入,看源码 将后面的base64解密得到flag 被遗忘的反序列化 源码 <?php# 当前目录中有一个txt文件哦 error_r…...

商品推荐Promoting Products
目录 前言原文内容:推荐常用会话商品推荐常用会话商品推荐常用会话前言 加油 原文内容: ❶ I promise that our product is superior. 我承诺我们的产品比别的家的好。 ❷ Our product is very attractive to young people. 我们的产品很吸引年轻人。 ❸ I want to buy th…...

整懵了,蚂蚁金服4面成功拿下测开offer,涨薪6k,突然觉得跳槽也不是那么难
蚂蚁的面试挺独特的,每轮面试都没有HR约时间,一般是晚上8点左右面试官来一个电话,问是否能面试,能的话开始面,不能就约一个其他时间。 全程4面,前四面技术面,电话面试,最后一面是HR面…...

《扬帆优配》个人养老金投资最新成绩出炉 七成养老FOF跑输基准
自去年底落地以来,个人养老金制度运转已有4个多月。运转以来,设置Y比例的个人养老FOF(基金中的基金)、个人养老金理财、个人养老储蓄、个人养老金稳妥四大产品继续扩容,形成了个人养老金初期的业态样貌。并且历经一季度…...

用Qt编写STM32烧录软件(ISP模式)代码
1.前言 之前写了一篇【用Qt编写STM32烧录软件(ISP模式)】,但是在文中没有具体的实现代码。 现在补上,各位有兴趣的同学可以参考以下。但是代码里面还有很多没有完善的,必定会存在一些bug,目前只是堪堪能用…...

Excel技能之美观排版
一个普通的Excel文件,想要变得好看,除了要掌握相关技能,还要用心。 美观排版,离不开的技能有字体、字体大小、字体颜色、背景色,等等。了解不同的效果用在什么样的场景,才能得心应手,融会贯通&…...

兆芯最新X86 CPU曝光:性能与英特尔/AMD相比,没落后10年
众所周知,在PC领域,X86完全是处于垄断地全的,至少占了90%以上的份额。其它的像MIPS、ARM、RISC-V等等,都不是X86的对手。 这与X86是复杂指令集有关,更与X86绑定了windows操作系统,有坚固的intel联盟有关&am…...
【Go自学】一文搞懂Go的strconv模块
一、 strconv包 strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi()、Itoa()、parse系列、format系列、append系列。 其中需要注意的是Atoi、parse系列的返回值是两个,分别是result和error,造成这种现…...

SpringBoot整合Admin服务监控(图文详细)
SpringBoot整合Admin服务监控组件 1 SpringBootAdmin 简介 1.1 概述 SpringBootAdmin 是一个非常好用的监控和管理的开源组件,该组件能够将 Actuator 中的信息进行界面化的展示,也可以监控所有 Spring Boot 应用的健康状况,提供实时警报功…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...