实现模型贴图的移动缩放旋转

技术:threejs+canvas+fabric

效果图:

原理:threejs中没有局部贴图的效果,只能通过map 的方式贴到模型上,所以说换一种方式来实现,通过canvas+fabric来实现图片的移动缩放旋转,然后将整个画布以map 的形式放到模型材质上,实现局部贴图的效果

直接上代码:

<template><div id="c-left"><input type="file" @change="handleFileChange" accept=".png" /><div id="container"></div></div><div id="c-right"><canvas id="canvas" width="512" height="512"></canvas></div>
</template><script>
import { fabric } from 'fabric'
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';// oss上传相关配置
let OSS = require('ali-oss')
let client = new OSS({region: 'oss-cn-beijing',accessKeyId: 'xxxxx',accessKeySecret: 'xxxxx',bucket: 'xxxxx'
})// 设置场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xfffff0);
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight( 0xffffff, 2.5); 
dirLight1.position.set( 0, 0.5, 1 );
scene.add( dirLight1 );const dirLight2 = new THREE.DirectionalLight( 0xffffff, 2.5); 
dirLight2.position.set( 0, 0.5, -1 );
scene.add( dirLight2 );const dirLight3 = new THREE.DirectionalLight( 0xffffff, 2.5 );
dirLight3.position.set( 0, -0.5, 0 );
scene.add( dirLight3 );const n = 2
// 设置视角
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/n / window.innerHeight,0.1,1000
);
camera.position.set(0, 5, 10);
// 随机名称
function generateRandomFileName() {const date = new Date().toISOString().replace(/[-:.TZ]/g, '');const randomPart = Math.random().toString(36).substr(2, 6);return `${date}-${randomPart}`;
}let selectedImage = null
export default {data(){return {canvas_s:null,image_url:null,}},methods:{async handleFileChange(event) {const file = event.target.files[0];if (!file || file.type!== 'image/png') {alert('请选择 PNG 格式的图片!');return;}const fileName = generateRandomFileName();await client.put(`m2_photos/${fileName}`, file);const url = client.signatureUrl(`m2_photos/${fileName}`);console.log("url为: ", url);this.image_url = url},init(){let flag = {x:false}; // 创建渲染器const renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true,antialias: true,});const container = document.getElementById("container");container.appendChild(renderer.domElement);var s = new fabric.Canvas('canvas');s.backgroundColor = 'rgb(100, 255, 255)'; // 设置画布背景this.canvas_s = s// 创建轨道控制器const controls = new OrbitControls(camera, renderer.domElement);renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.PCFSoftShadowMap;renderer.outputEncoding = THREE.sRGBEncoding;// 开启场景中的阴影贴图renderer.shadowMap.enabled = true;// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。controls.enableDamping = true;renderer.setSize(window.innerWidth/n, window.innerHeight);// 添加坐标系const axesHelper = new THREE.AxesHelper(10);scene.add(axesHelper);// 异步添加图片,能够实现图片的任意交互fabric.Image.fromURL('xxxxxxx', (oImg)=> {oImg.scale(0.1);var canvasWidth = s.width;var canvasHeight = s.height;// 计算图片放置在正中间的位置var left = canvasWidth / 2 ;var top = canvasHeight / 2 ;oImg.set({left: left - 80,  top: top -40  });console.log("oImg : ",oImg);s.add(oImg);}, {crossOrigin: 'anonymous'});// 定时任务setInterval(()=>{if (this.image_url) {fabric.Image.fromURL(this.image_url, (oImg)=> {oImg.scale(0.1);var canvasWidth = s.width;var canvasHeight = s.height;// 计算图片放置在正中间的位置var left = canvasWidth / 2 ;var top = canvasHeight / 2 ;oImg.set({left: left - 80,  top: top -40  });console.log("oImg : ",oImg);s.add(oImg);}, {crossOrigin: 'anonymous'});this.image_url = null}},1000)var texture = new THREE.Texture(document.getElementById("canvas"));texture.anisotropy = renderer.capabilities.getMaxAnisotropy();const mapTexture = new THREE.TextureLoader().load('/statisc/fabric004.png')const loader = new OBJLoader();loader.load('模型的位置', (object) => {object.traverse((child) => {child.material = new THREE.MeshLambertMaterial({ color:0xffffff,side:THREE.DoubleSide,// transparent:false,// opacity:1,bumpMap:mapTexture,// alphaMap:mapTexture,bumpScale:1,// emissive:0x404040});child.material.map = texture;child.material.map.minFilter = THREE.LinearFilterchild.material.map.colorSpace = 'srgb'console.log("map",child.material.map);});object.scale.set(0.1, 0.1, 0.1); // 变小一点object.position.set(0, -10, 0)scene.add(object);// 新增:为模型添加点击事件监听renderer.domElement.addEventListener('click', onModelClick);}, () => {}, () => {});// 按键设置document.addEventListener('keydown',function (event) {if (flag.x) {if (event.key === 's') {selectedImage.top += 5;}else if(event.key === 'a'){selectedImage.left -= 5;}else if( event.key === 'd'){selectedImage.left += 5;}else if(event.key === 'w'){selectedImage.top -= 5;}else if(event.key === 'q'){selectedImage.angle -= 5}else if(event.key === 'e'){selectedImage.angle += 5}else if(event.key === '6'){selectedImage.scaleX += 0.01}else if(event.key === '4'){selectedImage.scaleX -= 0.01}else if(event.key === '2'){selectedImage.scaleY += 0.01}else if(event.key === '8'){selectedImage.scaleY -= 0.01}else if(event.key === '3'){selectedImage.scaleY += 0.01selectedImage.scaleX += 0.01}else if(event.key === '7'){selectedImage.scaleY -= 0.01selectedImage.scaleX -= 0.01}else if(event.key === 'Backspace'){s.remove(selectedImage)}else if(event.key === 'ArrowUp'){s.bringForward(selectedImage)}else if(event.key === 'ArrowDown'){s.sendBackwards(selectedImage)}s.renderAll();}})const geometry = new THREE.BoxGeometry(1, 1, 1);const material = new THREE.MeshBasicMaterial({ map:texture });const cube = new THREE.Mesh(geometry, material);scene.add(cube);function render() {controls.update();texture.needsUpdate = truerenderer.render(scene, camera);// 渲染下一帧的时候就会调用render函数requestAnimationFrame(render);}render();var raycaster = new THREE.Raycaster();var mouse = new THREE.Vector2();// 鼠标点击事件function onModelClick(event) {  flag.x = falseevent.preventDefault();// pos 在场景图像上的位置var pos = [event.clientX,event.clientY]var rect = container.getBoundingClientRect();mouse.x = ((pos[0] - rect.left) / rect.width) *2-1mouse.y = -((pos[1] - rect.top) / rect.height) *2+1raycaster.setFromCamera(mouse, camera);// 通过射线获得场景中的对象var intersects = raycaster.intersectObjects(scene.children);if (intersects.length > 0 && intersects[0].uv) {var uv = intersects[0].uv;intersects[0].object.material.map.transformUv(uv)// 512表示画布的宽和高都是512var x = Math.round(uv.x * rect.width/(1+0.002*(rect.width-512))); var y = Math.round(uv.y * rect.height/(1+0.002*(rect.height-512)));const positionOnScene = {x,y}selectCanvas(positionOnScene,flag)}if (!flag.x) {s.discardActiveObject();s.renderAll();}}// 选中模型中的图片function selectCanvas(point,flag) {const objects = s.getObjects();for (let i = objects.length - 1; i >= 0; i--) {const obj = objects[i];if (obj.containsPoint(point)) {s.setActiveObject(obj);  // 设置图形为选中状态flag.x = true;  // 标记有图形被选中selectedImage = objs.renderAll();break; }}}}},mounted() {this.init();},
}</script><style>
#c-left, #c-right {
position: relative;
display: inline-block;
height: 100%;
width: 50%;
}#c-right {
float: right;
/* display: none; */
}
</style>

我是使用的vue3,同时还包含了oss的图片上传功能以及threejs 的反射效果,当点击模型上的图片时,即可选中图片,并通过wasd移动图片位置,qe旋转,123456789各个位置的缩放,还是很有趣的~

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

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

相关文章

【STM32】在标准库中使用DMA

1.MDA简介 DMA全称Direct Memory Access,直接存储区访问。 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作&#xff0c;传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输&#xff0c;也没有中断处理方式那样保留现场和…

【踩坑】探究PyTorch中创建稀疏矩阵的内存占用过大的问题

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 问题复现 原因分析 解决方案 碎碎念 问题复现 创建一个COO格式的稀疏矩阵&#xff0c;根据计算公式&#xff0c;他应该只占用约5120MB的内存&…

go zero入门

一、goctl安装 goctl 是 go-zero 的内置脚手架&#xff0c;可以一键生成代码、文档、部署 k8s yaml、dockerfile 等。 # Go 1.16 及以后版本 go install github.com/zeromicro/go-zero/tools/goctllatest检查是否安装成功 $ goctl -v goctl version 1.6.6 darwin/amd64vscod…

通过SDK使用百度智能云的图像生成模型SDXL

登录进入百度智能云控制台&#xff0c;在模型广场按照图像生成类别进行筛选&#xff0c;可以找到Stable-Diffusion-XL模型。点击Stable-Diffusion-XL模型的API文档后在弹出的新页面下拉可以找到SDK调用的说明。 import qianfandef sdxl(file: str, prompt: str, steps: int 2…

C语言_练习题

求最小公倍数 思路&#xff1a;假设两个数&#xff0c;5和7&#xff0c;那么最小至少也要7吧&#xff0c;所以先假定最小公倍数是两个数之间较大的&#xff0c;然后看7能不能同时整除5和7&#xff0c;不能就加1继续除 int GetLCM(int _num1, int _num2) {int max _num1>_n…

堆叠的作用

一、为什么要堆叠 传统的园区网络采用设备和链路冗余来保证高可靠性&#xff0c;但其链路利用率低、网络维护成本高&#xff0c;堆叠技术将多台交换机虚拟成一台交换机&#xff0c;达到简化网络部署和降低网络维护工作量的目的。 二、堆叠优势 1、提高可靠性 堆叠系统多台成…

25款404网页源码(下)

25款404网页源码&#xff08;下&#xff09; 13部分源码 14部分源码 15部分源码 16部分源码 17部分源码 18部分源码 19部分源码 20部分源码 21部分源码 22部分源码 23部分源码 24部分源码 25部分源码 领取完整源码下期更新 13 部分源码 .rail {position: absolute;width: 100%…

Node.js-path 模块

path 模块 path 模块提供了 操作路径 的功能&#xff0c;如下是几个较为常用的几个 API&#xff1a; 代码实例&#xff1a; const path require(path);//获取路径分隔符 console.log(path.sep);//拼接绝对路径 console.log(path.resolve(__dirname, test));//解析路径 let pa…

Docker搭建MySQL双主复制详细教程

在此之前需要提前安装好Docker和 Docker Compose 。 一、创建目录 首先创建一个本地数据挂载目录。 mkdir -p master1-data master2-data二、编写docker-compose.yml version: 3.7services:mysql-master1:image: mysql:5.7.36container_name: mysql-master1environment:MYSQL_…

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

FRP反向隧道代理打CFS三层

目录 攻击机 查看服务端frps.ini配置文件 开启服务端frps 蚁剑打目标机 上传客户端frp到目标机 ​frpc.ini文件配置成 客户端打开代理frpc vps显示成功客户端frpc打开 访问成功192.168.22.22的第二层内网主机 省去前面漏洞利用的rce过程&#xff0c;直接蚁剑开搞隧道…

如何使用VScode创建和上传Arduino项目

Visual Studio Code &#xff08;VS Code&#xff09; 是一种非常流行的通用集成开发环境 &#xff08;IDE&#xff09;。IDE 是一种将文本编辑器、编程界面、调试视图和项目管理集成在一个地方的软件。这个开源项目由微软领导&#xff0c;可以在所有操作系统上运行。使 VS Cod…

深度解析Ubuntu版本升级:LTS版本升级指南

深度解析Ubuntu版本升级&#xff1a;Ubuntu版本生命周期及LTS版本升级指南 Ubuntu是全球最受欢迎的Linux发行版之一&#xff0c;其版本升级与维护策略直接影响了无数用户的开发和生产环境。Canonical公司为Ubuntu制定了明确的生命周期和发布节奏&#xff0c;使得社区、企业和开…

宿舍报修小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;论坛管理&#xff0c;故障上报管理&#xff0c;新闻信息管理&#xff0c;维修人员管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;新闻信息…

C++ 视觉开发 六.特征值匹配

以图片识别匹配的案例来分析特征值检测与匹配方法。 目录 一.感知哈希算法(Perceptual Hash Algorithm) 二.特征值检测步骤 1.减小尺寸 2.简化色彩 3.计算像素点均值 4.构造感知哈希位信息 5.构造一维感知哈希值 三.实现程序 1.感知哈希值计算函数 2.计算距离函数 3…

CTF入门知识点

CTF知识点 md5函数 <?php$a 123;echo md5($a,true); ?> 括号中true显示输出二进制 替换成false显示输出十六进制绕过 ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c&#xff0c;这个字符串前几位刚好是 or 6 而 Mysql 刚好又会把 …

分支与循环

目录 1. if语句 1&#xff09;if 2) else 3&#xff09;分支中包含多条语句 4&#xff09;if嵌套 2.关系操作符 3.条件操作符 4.逻辑操作符&#xff1a;&& || ! 1) 逻辑取反运算符 !​编辑 2 与运算符​编辑 3) 或运算符​编辑 4) 闰年的判断 5) 短路 …

一行代码用git新建分支

1.在本地创建分支 dev git branch dev2.切换分支 git checkout devwebstorm操作如下&#xff1a; 3.推送新分支到远程 git push --set-upstream origin 分支名webstorm操作如下&#xff1a;提交代码的时候会自动推送到远程 4.到git上面可以看看刚刚推送的内容 dev多推送…

MacOS miniconda安装方法

打开macos “终端” 应用 执行命令 mkdir -p ~/miniconda3curl https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o ~/miniconda3/miniconda.shbash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3rm -rf ~/miniconda3/mini…

Java项目:基于SSM框架实现的智慧城市实验室管理系统分前后台【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的智慧城市实验室管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单…