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;尽管在这个例子中&#…

MySQL数据库主从配置

MySQL主从配置 1. 修改数据库my.cnf文件 修改数据库my.cnf文件&#xff0c;在文件中添加如下内容&#xff0c;其中主数据库的server-id必须要比从库的更小。 # 注册集群id server-id101 # 开启二进制日志文件 log-binmysql-bin # 设置日志格式 binlog-formatrow # 开启中继日…

开关电源基本原理1

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

MySql和Oracle表分区

MySql和Oracle表分区 MySQL和Oracle都支持表分区&#xff0c;这是一种数据库管理技术&#xff0c;用于将大型表、索引或索引组织表分解为更小、更易于管理的片段&#xff0c;称为分区。以下是关于MySQL和Oracle分区的详细对比和说明&#xff1a; 分区基本概念以及类型 MySQL…

利用多线程打印出1 2 3

阿里控股_爱橙科技_笔试 题目&#xff1a;利用多线程打印出1 2 3&#xff0c;请给出尽可能多的实现方案。 其中&#xff0c;悲观锁可以使用Synchronized、Reentrantlock实现&#xff0c;乐观锁可以使用AtomicInteger实现&#xff0c;底层是CAS实现乐观锁。Semaphore 是一种计数…

spring管理bean

大家好&#xff0c;这里是教授.F 管理bean方面&#xff1a; 获取bean: /** * 通过类型来获取容器的 bean 对象 */ Test public void getMonsterByType() {ApplicationContext ioc new ClassPathXmlApplicationContext("beans.xml");Monster monster ioc.getBean(…

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;势必会对企业安全态势造成严重影响。 但每项技术资产都被视为关…

IT项目常用考核指标

在IT项目管理中&#xff0c;考核指标是用来评估项目进展和成果的重要依据。以下是一些常用的IT项目考核指标&#xff0c;包括具体的指标名称、计算公式、基准达标值以及常见问题&#xff1a; 1 项目进度准时率 项目的生命线是什么&#xff1f;没错&#xff0c;是时间&#xf…

【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 :自…

拉取代码编辑器中报错`Delete ␍ prettier/prettier` 问题的解决方案

当您在使用 git clone 下载Web前端代码仓库后&#xff0c;可能会遇到 Delete ␍ prettier/prettier 的提示时&#xff0c;这通常意味着您的代码中存在不兼容的换行符问题。 问题产生的原因 在不同的操作系统中&#xff0c;文本文件的换行符是不同的。Windows系统通常使用回车…

Pytorch常用函数用法归纳:Tensor张量之间的计算

1.torch.add() (1)函数原型: torch.add(input, other, alpha, out) (2)参数说明: 参数名称参数类型参数说明inputtorch.Tensor表示参与运算的第一个输入Tensor张量othertorch.Tensor或者Number表示参与运算的第二个输入Tensor张量或标量alphaNumber, optional一个可选的缩放…