webgl_decals

ThreeJS 官方案例学习(webgl_decals)

1.效果图

在这里插入图片描述

2.源码

<template><div><div id="container"></div></div>
</template>
<script>
// 光线投射相关代码 https://threejs.org/docs/index.html#api/zh/core/Raycaster
import * as THREE from 'three';
// 导入控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// 引入房间环境,创建一个室内环境
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
// 导入性能监视器
import Stats from 'three/examples/jsm/libs/stats.module.js';
// 导入gltf载入库、模型加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
// 引入模型解压器
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
// 引入贴花几何体
import { DecalGeometry } from 'three/examples/jsm/geometries/DecalGeometry.js';
//GUI界面
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import gsap from 'gsap';
export default {data() {return {container: null, //界面需要渲染的容器scene: null,	// 场景对象camera: null, //相机对象renderer: null, //渲染器对象controller: null,	// 相机控件对象stats: null,// 性能监听器mixer: null,//动画混合器mesh: null,//导入的模型gui: null,//GUI界面clock: new THREE.Clock(),// 创建一个clock对象,用于跟踪时间raycaster: new THREE.Raycaster(),//光线投射,用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)params: null,intersection: {intersects: false,//是否存在相交的点point: new THREE.Vector3(),//相交部分的点(世界坐标)的坐标normal: new THREE.Vector3()//相交的面},mouse: new THREE.Vector2(),//创建鼠标位置。创建一个二维向量为后面 Raycaster 实例调用 .setFromCamera 方法做准备intersects: [],//物体和射线的焦点(光线投射与鼠标拾取时相交的物体数组)mouseHelper: null,//鼠标定位帮助器line: null,//鼠标定位帮助线条position: new THREE.Vector3(),//贴花投影器的位置orientation: new THREE.Euler(),//贴花投影器的朝向size: new THREE.Vector3(10, 10, 10),// 贴花投影器的尺寸decals: [],//点击时创建的贴花几何体集合};},mounted() {// gui参数(贴花参数)this.params = {minScale: 10,//最大缩放值maxScale: 20,//最小缩放值rotate: true,//贴花是否旋转clear: () => {this.removeDecals();}}this.init()this.animate()  //如果引入了模型并存在动画,可在模型引入成功后加载动画window.addEventListener("resize", this.onWindowSize)let moved = false;//判断是否移动状态// controller 控制器存在操作时(物体处于移动中时),moved置为true,不进行相关操作this.controller.addEventListener('change', () => { moved = true; });window.addEventListener("pointerdown", () => { moved = false })window.addEventListener("pointerup", (event) => {if (moved === false) {this.checkIntersection(event.clientX, event.clientY)if (this.intersection.intersects) this.shoot()}})window.addEventListener('pointermove', this.onPointerMove);},beforeUnmount() {console.log('beforeUnmount===============');// 组件销毁时置空this.container = nullthis.scene = nullthis.camera = nullthis.renderer = nullthis.controller = nullthis.stats = nullthis.mixer = nullthis.model = null//导入的模型},methods: {/*** @description 初始化*/init() {this.container = document.getElementById('container')this.setScene()this.setCamera()this.setRenderer()this.setController()this.addHelper()// this.setPMREMGenerator()this.setLight()this.setGltfLoader()this.addStatus()},/*** @description 创建场景*/setScene() {// 创建场景对象Scenethis.scene = new THREE.Scene()// 设置场景背景// this.scene.background = new THREE.Color(0xbfe3dd);},/*** @description 创建相机*/setCamera() {// 第二参数就是 长度和宽度比 默认采用浏览器  返回以像素为单位的窗口的内部宽度和高度this.camera = new THREE.PerspectiveCamera(60, this.container.clientWidth / this.container.clientHeight, 1, 1000)// 设置相机位置this.camera.position.z = 120// 设置摄像头宽高比例this.camera.aspect = this.container.clientWidth / this.container.clientHeight;// 设置摄像头投影矩阵this.camera.updateProjectionMatrix();// 设置相机视线方向this.camera.lookAt(new THREE.Vector3(0, 0, 0))// 0, 0, 0 this.scene.position// 将相机加入场景this.scene.add(this.camera)},/*** @description 创建渲染器*/setRenderer() {// 初始化渲染器this.renderer = new THREE.WebGLRenderer({antialias: true,// 设置抗锯齿// logarithmicDepthBuffer: true,  // 是否使用对数深度缓存})// 设置渲染器宽高this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);// 设置渲染器的像素比this.renderer.setPixelRatio(window.devicePixelRatio);// 是否需要对对象排序this.renderer.sortObjects = false;// 将渲染器添加到页面this.container.appendChild(this.renderer.domElement);},/*** @description 添加创建控制器*/setController() {this.controller = new OrbitControls(this.camera, this.renderer.domElement);// 控制缩放范围this.controller.minDistance = 50;this.controller.maxDistance = 200;//是否开启右键拖拽// this.controller.enablePan = false;// 阻尼(惯性)// this.controller.enableDamping = true; //启用阻尼(惯性)// this.controller.dampingFactor = 0.04; //阻尼惯性有多大// this.controller.autoRotate = true; //自动围绕目标旋转// this.controller.minAzimuthAngle = -Math.PI / 3; //能够水平旋转的角度下限。如果设置,其有效值范围为[-2 * Math.PI,2 * Math.PI],且旋转角度的上限和下限差值小于2 * Math.PI。默认值为无穷大。// this.controller.maxAzimuthAngle = Math.PI / 3;//水平旋转的角度上限,其有效值范围为[-2 * Math.PI,2 * Math.PI],默认值为无穷大// this.controller.minPolarAngle = 1; //能够垂直旋转的角度的下限,范围是0到Math.PI,其默认值为0。// this.controller.maxPolarAngle = Math.PI - 0.1; //能够垂直旋转的角度的上限,范围是0到Math.PI,其默认值为Math.PI。// 修改相机的lookAt是不会影响THREE.OrbitControls的target的// 由于设置了控制器,因此只能改变控制器的target以改变相机的lookAt方向// this.controller.target.set(0, 0.5, 0); //控制器的焦点},/*** @description 创建辅助坐标轴*/addHelper() {// 模拟相机视锥体的辅助对象let helper = new THREE.CameraHelper(this.camera);// this.scene.add(helper);//创建辅助坐标轴、轴辅助 (每一个轴的长度)let axisHelper = new THREE.AxesHelper(150);  // 红线是X轴,绿线是Y轴,蓝线是Z轴// this.scene.add(axisHelper)// 坐标格辅助对象let gridHelper = new THREE.GridHelper(100, 30, 0x2C2C2C, 0x888888);// this.scene.add(gridHelper);this.mouseHelper = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 10), new THREE.MeshNormalMaterial());this.mouseHelper.visible = false;this.scene.add(this.mouseHelper);},/*** @description 给场景添加环境光效果*/setPMREMGenerator() {// 预过滤的Mipmapped辐射环境贴图const pmremGenerator = new THREE.PMREMGenerator(this.renderer);this.scene.environment = pmremGenerator.fromScene(new RoomEnvironment(this.renderer), 0.04).texture;},/*** @description 设置光源*/setLight() {// 环境光const ambientLight = new THREE.AmbientLight(0x666666);this.scene.add(ambientLight);// 平行光const dirLight1 = new THREE.DirectionalLight(0xffddcc, 3);dirLight1.position.set(1, 0.75, 0.5);this.scene.add(dirLight1);const dirLight2 = new THREE.DirectionalLight(0xccccff, 3);dirLight2.position.set(- 1, 0.75, - 0.5);this.scene.add(dirLight2);},/*** @description 创建性能监听器*/addStatus() {// 创建一个性能监听器this.stats = new Stats();// 将性能监听器添加到容器中this.container.appendChild(this.stats.dom);},/*** @description 添加创建模型*/setGltfLoader() {let that = this// 实例化gltf载入库const loader = new GLTFLoader();// 实例化draco载入库const dracoLoader = new DRACOLoader();// 添加draco载入库dracoLoader.setDecoderPath("/draco/gltf/");// 添加draco载入库loader.setDRACOLoader(dracoLoader);//创建鼠标定位帮助线条const geometry = new THREE.BufferGeometry();geometry.setFromPoints([new THREE.Vector3(), new THREE.Vector3()]);this.line = new THREE.Line(geometry, new THREE.LineBasicMaterial());this.scene.add(this.line);// 添加模型const textureLoader = new THREE.TextureLoader()//纹理贴图加载器const map = textureLoader.load(require("../../../public/model/gltf/LeePerrySmith/Map-COL.jpg"));map.colorSpace = THREE.SRGBColorSpace;const specularMap = textureLoader.load('../../../public/models/gltf/LeePerrySmith/Map-SPEC.jpg');const normalMap = textureLoader.load('../../../public/models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg');loader.load('/model/gltf/LeePerrySmith/LeePerrySmith.glb', (gltf) => {that.mesh = gltf.scene.children[0];that.mesh.material = new THREE.MeshPhongMaterial({specular: 0x111111,//材质的高光颜色map: map,//颜色贴图specularMap: specularMap,//镜面反射贴图// normalMap: normalMap,//法线贴图的纹理shininess: 25,//高亮的程度});that.scene.add(that.mesh)that.mesh.scale.set(10, 10, 10)}, undefined, (err => {console.error(err)}))const gui = new GUI();gui.add(this.params, 'minScale', 1, 30);gui.add(this.params, 'maxScale', 1, 30);gui.add(this.params, 'rotate');gui.add(this.params, 'clear');gui.open();},/*** @description pointermove 窗口事件*/onPointerMove(event) {if (event.isPrimary) {this.checkIntersection(event.clientX, event.clientY);}},/*** @description 计算物体和射线之间的焦点*/// 光线投射相关代码 https://threejs.org/docs/index.html#api/zh/core/RaycastercheckIntersection(x, y) {if (this.mesh === undefined) return// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)this.mouse.x = (x / window.innerWidth) * 2 - 1;this.mouse.y = - (y / window.innerHeight) * 2 + 1;// 通过摄像机和鼠标位置更新射线this.raycaster.setFromCamera(this.mouse, this.camera);// 检测所有在射线与物体之间,包括或不包括后代的相交部分。返回结果时,相交部分将按距离进行排序,最近的位于第一个。// intersectObjec()第三个参数 - 结果的目标数组, 如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)this.raycaster.intersectObject(this.mesh, false, this.intersects);// 如果存在相交点if (this.intersects.length > 0) {const p = this.intersects[0].point//相交部分的点(世界坐标)this.mouseHelper.position.copy(p);this.intersection.point.copy(p);const n = this.intersects[0].face.normal.clone();//相交的面n.transformDirection(this.mesh.matrixWorld);n.multiplyScalar(10);n.add(this.intersects[0].point);this.intersection.normal.copy(this.intersects[0].face.normal);this.mouseHelper.lookAt(n);// 设置line焦点const positions = this.line.geometry.attributes.position;positions.setXYZ(0, p.x, p.y, p.z);positions.setXYZ(1, n.x, n.y, n.z);positions.needsUpdate = true;this.intersection.intersects = true;//intersectObjec() 清空数组this.intersects.length = 0;} else {this.intersection.intersects = false;}},/*** @description 创建贴花几何体*/shoot() {// 设置贴花几何体的位置、朝向、尺寸this.position.copy(this.intersection.point);this.orientation.copy(this.mouseHelper.rotation);if (this.params.rotate) this.orientation.z = Math.random() * 2 * Math.PI;const scale = this.params.minScale + Math.random() * (this.params.maxScale - this.params.minScale);this.size.set(scale, scale, scale);// 加载纹理贴图const textureLoader = new THREE.TextureLoader()//纹理贴图加载器const decalDiffuse = textureLoader.load('/textures/decal/decal-diffuse.png');decalDiffuse.colorSpace = THREE.SRGBColorSpace;const decalNormal = textureLoader.load('/textures/decal/decal-normal.jpg');// 设置贴花几何体材质const decalMaterial = new THREE.MeshPhongMaterial({specular: 0x444444,//材质的高光颜色map: decalDiffuse,//颜色贴图normalMap: decalNormal,//法线贴图的纹理normalScale: new THREE.Vector2(1, 1),//法线贴图对材质的影响程度shininess: 30,//高亮的程度transparent: true,//材质是否透明,存在map时为truedepthTest: true,//是否在渲染此材质时启用深度测试depthWrite: false,//渲染此材质是否对深度缓冲区有任何影响polygonOffset: true,//是否使用多边形偏移polygonOffsetFactor: - 4,//多边形偏移系数wireframe: false,//渲染为平面多边形});const material = decalMaterial.clone();//设置随机颜色material.color.setHex(Math.random() * 0xffffff);// 引入贴花物体 DecalGeometry - 贴花几何体const m = new THREE.Mesh(new DecalGeometry(this.mesh, this.position, this.orientation, this.size), material);// 创建贴花几何体集合数组this.decals.push(m);// 在场景中添加贴花this.scene.add(m);},/*** @description 清除贴花几何体集合*/removeDecals() {this.decals.forEach((d) => {this.scene.remove(d);});this.decals.length = 0;},/*** @description 监听屏幕的大小改变,修改渲染器的宽高,相机的比例*/// 窗口变化onWindowSize() {// 更新摄像头this.camera.aspect = this.container.clientWidth / this.container.clientHeight;// 更新摄像机的投影矩阵this.camera.updateProjectionMatrix();//更新渲染器this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);// 设置渲染器的像素比this.renderer.setPixelRatio(window.devicePixelRatio)},/*** @description 动画执行函数*/animate() {const delta = this.clock.getDelta();// 引擎自动更新渲染器requestAnimationFrame(this.animate);//update()函数内会执行camera.lookAt(x, y, z)this.controller.update(delta);// 更新性能监听器this.stats.update();// 重新渲染场景this.renderer.render(this.scene, this.camera);},},
};
</script>
<style>
#container {position: absolute;width: 100%;height: 100%;
}
</style>

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

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

相关文章

干货满满!Stable Diffusion 从入门到精通之提示词手册,免费分享,自学转行,零基础首选!

Stable Diffusion 技术把 AI 图像生成提高到了一个全新高度&#xff0c;文生图 Text to image 生成质量很大程度上取决于你的提示词 Prompt 好不好。本文从“如何写好提示词”出发&#xff0c;从提示词构成、调整规则和 AIGC辅助工具等角度&#xff0c;对文生图的提示词输入进行…

模式识别涉及的常用算法

一、线性回归 1.算法执行流程&#xff1a; 算法的执行流程可以简述如下&#xff1a; 导入必要的库&#xff1a; 导入NumPy库&#xff0c;用于数值计算。导入Matplotlib库&#xff0c;用于数据可视化。导入Pandas库&#xff0c;用于数据处理&#xff08;尽管在这个例子中&#…

开关电源基本原理1

目录 内容概述 关于电感 认识电感 电感充电 电感储能 电感充电 电感参数 电感放电 利用电感升压 电感电流波形 伏秒法则 电流纹波率 电感电流三种导电模式 电流纹波率与频率的关系 电流纹波率与电感值的关系 电感值与电感体积 电路纹波率r的最优值 电感值与电…

Flutter开发效率提升1000%,Flutter Quick教程之定义Api(四)

现在我们来讲讲&#xff0c;如何建立Api 响应数据的变量。 这个变量&#xff0c;本质上就是对根据json数据生成model的引用。 这个name就是引用名。 这个path&#xff0c;就是引用的Model Data里面的具体字段&#xff0c;在实际操作过程中&#xff0c;校验是由右边的json数据…

从运维故障中你都学到了什么?

一阵急促尖锐的铃声响起&#xff0c;王一搏忐忑不安地接起电话&#xff0c;被告知系统有20台服务器批量重启。 20 台&#xff01;批量重启&#xff01;意识到问题的严重性&#xff0c;王一搏迅速调整好状态&#xff0c;准备投身一场激烈的救火工作中。 然而事件的走向却远远超…

JVM的内存结构

JVM 内存结构 方法区: 方法区主要用于存储虚拟机加载的类信息、常量、静态变量&#xff0c;以及编译器编译后的代码等数据。 程序计数器 由于在JVM中&#xff0c;多线程是通过线程轮流切换来获得CPU执行时间的&#xff0c;因此&#xff0c;在任一具体时刻&#xff0c;一个CP…

kali配置静态ip

kali配置静态ip 因为一些环境需要&#xff0c;本地linux主机需要搭建一个桥接模式的网络&#xff0c;那么直接就在kali中配置了&#xff0c; 打开vim /etc/network/interfaces 这里就需要自己配置一下ip&#xff0c;网关&#xff0c;路由等内容 这里参考&#xff1a;参考链接 …

排序方法——《选择排序》

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

关于文件上传失败问题的排查思路

问题场景&#xff1a; 最近公司的app有很多用户反馈上传文件失败了。业务路径就是简单的app前端调用后端文件上传接口&#xff0c;所以发生上传失败的可能因素可能是&#xff1a;1、文件大小/文件类型等是否有问题&#xff0c;公司用的是七牛的文件服务器&#xff0c;对文件上…

我成功创建了一个Electron应用程序

1.创建electron项目命令&#xff1a; npm create quick-start/electron electron-memo 2选择&#xff1a;√ Select a framework: vue √ Add TypeScript? ... No √ Add Electron updater plugin? ... Yes √ Enable Electron download mirror proxy? ... Yes 3.命令&am…

保护关键业务资产的四个步骤

提到 “关键资产 ”&#xff0c;相信大家并不陌生&#xff0c;它是企业 IT 基础设施中对组织运作至关重要的技术资产。如果这些资产&#xff08;如应用服务器、数据库或特权身份&#xff09;出现问题&#xff0c;势必会对企业安全态势造成严重影响。 但每项技术资产都被视为关…

【UML用户指南】-01-UML基本元素的介绍(一)

目录 1、UML的词汇表 2、UML的4种事物 2.1、结构事物 1&#xff09;类 2&#xff09;接口 3&#xff09;协作 4&#xff09;用例&#xff08;use case&#xff09; 5&#xff09;主动类&#xff08;active class&#xff09; 6&#xff09;构件&#xff08;component&a…

揭秘c语言储存类别

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文将整理c语言的储存类型的知识点 储存类型概念 描述:用于解决内存开辟与解放的时间的问题。跟作用域没啥关系。 但是呢&#xff0c;他也是能影响到程序的运行的&#xff0c;所以是很关键的。 类型: auto :自…

idea mac快捷键

Mac快捷键 快捷键 说明 ⌘ F 在当前窗口查找 ⌘ ⇧ F 在全工程查找 ⌘ ⇧ ⌥ N 查找类中的方法或变量 F3 / ⇧ F3 移动到搜索结果的下/上一匹配处 ⌘ R 在当前窗口替换 ⌘ ⇧ R 在全工程替换 ⌘ ⇧ V 可以将最近使用的剪贴板内容选择插入到文本 ⌥…

负压实验室设计建设方案

随着全球公共卫生事件的频发&#xff0c;负压实验室的设计和建设在医疗机构中的重要性日益凸显。负压实验室&#xff0c;特别是负压隔离病房&#xff0c;主要用于控制传染性疾病的扩散&#xff0c;保护医护人员和周围环境的安全。广州实验室装修公司中壹联凭借丰富的实验室装修…

MQTT.FX的使用

背景 在如今物联网的时代下&#xff0c;诞生了许多的物联网产品&#xff0c;这些产品通过BLE、WIFI、4G等各种各样的通信方式讲数据传输到各种各样的平台。 除了各个公司私有的云平台外&#xff0c;更多的初学者会接触到腾讯云、阿里云之类的平台。设备接入方式也有着多种多样…

Spring自带定时任务@Scheduled注解

文章目录 1. cron表达式生成器2. 简单定时任务代码示例&#xff1a;每隔两秒打印一次字符3. Scheduled注解的参数3.1 cron3.2 fixedDelay3.3 fixedRate3.4 initialDelay3.5 fixedDelayString、fixedRateString、initialDelayString等是String类型&#xff0c;支持占位符3.6 tim…

GD32F407ZGT6/GD32F450ZGT6(3)外部中断实验

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 向上代码兼容GD32F450ZGT6中使用 后续项目主要在下面该专栏中发布&#xff1a; https://blog.csdn.net/qq_62316532/category_12608431.html?spm1001.2014.3001.5482 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转…

用幻灯片讲解C++手动内存管理

用幻灯片讲解C手动内存管理 1.栈内存的基本元素 2.栈内存的聚合对象 3.手动分配内存和释放内存 注意&#xff1a;手动分配内存&#xff0c;指的是在堆内存中。 除非实现自己的数据结构&#xff0c;否则永远不要手动分配内存! 即使这样&#xff0c;您也应该通过std::allocator…

进入新公司有焦虑感怎么办?

前因 前两天技术交流群里有童鞋问了一个很有意思的问题&#xff0c;他问如何克服进入新公司的焦虑感&#xff1f;很多热心的童鞋都纷纷支招&#xff0c;比如 “主动干活”、“专注干活”、“让时间冲淡焦虑感”、……等等&#xff0c;这些都很有道理&#xff0c;不过&#xff…