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

技术: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.mzph.cn/diannao/41848.shtml

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

相关文章

数据集 | 人脸公开数据集的介绍及下载地址

本文介绍了人脸相关算法的数据集。 1.人脸数据集详情 1.1.Labeled Faces in the Wild (LFW) 论文 下载地址&#xff1a;LFW Face Database : Main (umass.edu) 是目前人脸识别的常用测试集&#xff0c;其中提供的人脸图片均来源于生活中的自然场景&#xff0c;因此识别难度会…

DDR的拓扑与仿真

T型拓扑 vs Fly-by 由于T型拓扑在地址、命令和时钟都是同时到达每个DDR芯片&#xff0c;所以同步的切换噪声会叠加在一起&#xff0c;DDR越多这个信号上叠加的噪声越大&#xff0c;T型拓扑的优点是地址、命令和时钟都是同时到达&#xff0c;所以不需要做写均衡Write leveling。…

Node.js 生成vue组件

在项目根目录下创建 create.js /*** 脚本生成vue组件* 主要是利用node自带的fs模块操作文件的写入* ===========================================* 准备步骤:* 1.输入作者名* 2.输入文件名* 3.输入菜单名* 4.输入文件地址* ============================================* 操…

【3D->2D转换(1)】LSS(提升,投放,捕捉)

Lift, Splat, Shoot 这是一个端到端架构&#xff0c;直接从任意数量的摄像头数据提取给定图像场景的鸟瞰图表示。将每个图像分别“提升&#xff08;lift&#xff09;”到每个摄像头的视锥&#xff08;frustum&#xff09;&#xff0c;然后将所有视锥“投放&#xff08;splat&a…

AI助手崛起:开发者的新伙伴还是未来替代者?

你好&#xff0c;我是三桥君。 自从 ChatGPT 问市以来&#xff0c;AI 将取代开发者的声音不绝于耳&#xff0c;至今还是互联网异常火热的问题。 在软件开发领域&#xff0c;生成式人工智能&#xff08;AIGC&#xff09;正在改变开发者的工作方式。无论是代码生成、错误检测还是…

【JavaWeb程序设计】JSP编程

目录 一、编写JSP页面&#xff0c;在界面上显示1-9&#xff0c;9个链接&#xff0c;单击每个链接&#xff0c;能够在另一个页面打印该数字的平方。 1. 运行截图 2. 第一个jsp页面&#xff08;index.jsp&#xff09; 3. 第二个jsp页面&#xff08;square.jsp&#xff09; 二…

Arc for Windows 无法使用?一篇文章教会你!

&#x1f44b; 大家好&#xff0c;我是 Beast Cheng &#x1f4eb; 联系我&#xff1a;458290771qq.com &#x1f331; 接合作、推广…… 什么是Arc浏览器&#xff1f; Arc浏览器是The Browser Conpany使用Swift语言开发的一款浏览器&#xff0c;Arc浏览器由其漂亮的侧边栏闻名…

Python 异步编程介绍与代码示例

Python 异步编程介绍与代码示例 一、异步编程概述 异步编程是一种编程范式&#xff0c;它旨在处理那些需要等待I/O操作完成或执行耗时任务的情况。在传统的同步编程中&#xff0c;代码会按照顺序逐行执行&#xff0c;直到遇到一个耗时操作&#xff0c;它会阻塞程序的执行直到…

Codeforces Round 903 (Div. 3)A~F

A.Dont Try to Count 输入样例&#xff1a; 12 1 5 a aaaaa 5 5 eforc force 2 5 ab ababa 3 5 aba ababa 4 3 babb bbb 5 1 aaaaa a 4 2 aabb ba 2 8 bk kbkbkbkb 12 2 fjdgmujlcont tf 2 2 aa aa 3 5 abb babba 1 19 m mmmmmmmmmmmmmmmmmmm输出样例&#xff1a; 3 1 2 -1 1 0…

1999-2022年企业持续绿色创新水平数据

企业持续绿色创新水平数据为研究者提供了评估企业在绿色技术领域创新持续性和能力的重要视角。以下是对企业持续绿色创新水平数据的介绍&#xff1a; 数据简介 定义&#xff1a;企业持续绿色创新水平反映了企业在一定时期内绿色专利申请的持续性和创新能力。计算方法&#xf…

初识STM32:开发方式及环境

STM32的编程模型 假如使用C语言的方式写了一段程序&#xff0c;这段程序首先会被烧录到芯片当中&#xff08;Flash存储器中&#xff09;&#xff0c;Flash存储器中的程序会逐条的进入CPU里面去执行。 CPU相当于人的一个大脑&#xff0c;虽然能执行运算和执行指令&#xff0c;…

通信协议:常见的芯片内通信协议

相关阅读 通信协议https://blog.csdn.net/weixin_45791458/category_12452508.html?spm1001.2014.3001.5482 本文将简单介绍一些常见的芯片间通信协议&#xff0c;但不会涉及到协议的具体细节。 一、AMBA&#xff08;Advanced Microcontroller Bus Architecture&#xff09;…

MySQL之备份与恢复(七)

备份与恢复 文件系统快照 规划LVM备份 LVM快照备份也是有开销的。服务器写到原始卷的越多&#xff0c;引发的额外开销也越多。当服务器随机修改许多不同块时&#xff0c;磁头需要需要自写时复制空间来来回回寻址&#xff0c;并且将数据的老版本写到写时复制空间。从快照中读…

刷题之多数元素(leetcode)

多数元素 哈希表解法&#xff1a; class Solution { public:/*int majorityElement(vector<int>& nums) {//map记录元素出现的次数&#xff0c;遍历map&#xff0c;求出出现次数最多的元素unordered_map<int,int>map;for(int i0;i<nums.size();i){map[nu…

最适合mysql5.6安装的linux版本-实战

文章目录 一, 适合安装mysql5.6的linu版本1. CentOS 72. Ubuntu 14.04 LTS (Trusty Tahr)3. Debian 8 (Jessie)4. Red Hat Enterprise Linux (RHEL) 7 二, 具体以Ubuntu 14.04 LTS (Trusty Tahr)为例安装虚拟机安装Ubuntu 14.04 LTS (Trusty Tahr) 自己弄安装ssh(便于远程访问,…

前端八股文 对$nextTick的理解

$nexttick是什么? 获取更新后的dom内容 为什么会有$nexttick ? vue的异步更新策略 (这也是vue的优化之一 要不然一修改数据就更新dom 会造成大量的dom更新 浪费性能) 这是因为 message &#xff08;data&#xff09;数据在发现变化的时候&#xff0c;vue 并不会立刻去更…

240705_昇思学习打卡-Day17-基于 MindSpore 实现 BERT 对话情绪识别

240705_昇思学习打卡-Day17-基于 MindSpore 实现 BERT对话情绪识别 近期确实太忙&#xff0c;此处仅作简单记录&#xff1a; 模型简介 BERT全称是来自变换器的双向编码器表征量&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;&#xff0c…

【wordpress教程】wordpress博客网站添加非法关键词拦截

有的网站经常被恶意搜索&#xff0c;站长们不胜其烦。那我们如何屏蔽恶意搜索关键词呢&#xff1f;下面就随小编一起来解决这个问题吧。 后台设置预览图&#xff1a; 设置教程&#xff1a; 1、把以下代码添加至当前主题的 functions.php 文件中&#xff1a; add_action(admi…

CVPR 2024最佳论文分享:通过解释方法比较Transformers和CNNs的决策机制

CVPR&#xff08;Conference on Computer Vision and Pattern Recognition&#xff09;是计算机视觉领域最有影响力的会议之一&#xff0c;主要方向包括图像和视频处理、目标检测与识别、三维视觉等。近期&#xff0c;CVPR 2024 公布了最佳论文。共有10篇论文获奖&#xff0c;其…

计算组的妙用!!页面权限控制

需求描述&#xff1a; 某些特殊的场景下&#xff0c;针对某页看板&#xff0c;需要进行数据权限卡控&#xff0c;但是又不能对全部的数据进行RLS处理&#xff0c;这种情况下可以利用计算组来解决这个需求。 实际场景 事实表包含产品维度和销售维度 两个维度属于同一公司下面的…