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

技术: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.LinearFilter
              child.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.01
            selectedImage.scaleX += 0.01
          }else if(event.key === '7'){
            selectedImage.scaleY -= 0.01
            selectedImage.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 = true
          renderer.render(scene, camera);
          // 渲染下一帧的时候就会调用render函数
          requestAnimationFrame(render);
      }
      
      render();
      
      var raycaster = new THREE.Raycaster();
      var mouse = new THREE.Vector2();

      // 鼠标点击事件
      function onModelClick(event) {  
        flag.x = false
        event.preventDefault();
        // pos 在场景图像上的位置
        var pos = [event.clientX,event.clientY]
        var rect = container.getBoundingClientRect();
        mouse.x = ((pos[0] - rect.left) / rect.width) *2-1
        mouse.y = -((pos[1] - rect.top) / rect.height) *2+1
        raycaster.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表示画布的宽和高都是512
          var 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 = obj
            s.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.mfbz.cn/a/776602.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【智能算法应用】麻雀搜索算法SSA优化Kmeans图像分割

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】麻雀搜索算法&#xff08;SSA&#xff09;原理及实现 2.数学模型 Kmeans是一种无监督的聚类算法,由于参数简洁,时间复杂度低已成功应用于图像分割,取得了良好的分割效果。但传统的 K 均值聚…

Go-知识测试-性能测试

Go-知识测试-性能测试 1. 定义2. 例子3. testing.common 测试基础数据4. testing.TB 接口5. 关键函数5.1 testing.runBenchmarks5.2 testing.B.runN5.3 testing.B.StartTimer5.4 testing.B.StopTimer5.5 testing.B.ResetTimer5.6 testing.B.Run5.7 testing.B.run15.8 testing.B…

无人机便携式侦测干扰设备(定全向)技术详解

无人机便携式侦测干扰设备&#xff08;定全向&#xff09;是一种专门针对无人机进行侦测和干扰的设备。它具备定向和全向两种工作模式&#xff0c;能够覆盖较宽的频率范围&#xff0c;有效侦测并干扰无人机与遥控器之间的通信信号&#xff0c;从而达到控制或驱离无人机的目的。…

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

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

收银系统源码-营销活动-积分商城

1. 功能描述 营运抽奖&#xff1a;智慧新零售收银系统&#xff0c;线上商城的营销插件&#xff0c;由商户运营&#xff0c;用户通过多种渠道可以获取积分&#xff0c;不仅支持在收银端抵用&#xff0c;还可以在积分商城内兑换优惠券或者真实商品&#xff0c;提升会员活跃度&am…

计算机图形学入门24:材质与外观

1.前言 想要得到一个漂亮准确的场景渲染效果&#xff0c;不只需要物理正确的全局照明算法&#xff0c;也要了解现实中各种物体的表面外观和在图形学中的模拟方式。而物体的外观和材质其实就是同一个意思&#xff0c;不同的材质在光照下就会表现出不同的外观&#xff0c;所以外观…

CH09_JS的循环控制语句

第9章&#xff1a;Javascript循环控制语句 本章目标 掌握break关键字的使用掌握continue关键字的使用 课程回顾 for循环的特点和语法while循环的特点和语法do-while循环的特点和语法三个循环的区别 讲解内容 1. break关键字 为什么要使用break关键字 生活中&#xff0c;描…

MongoDB集群搭建-最简单

目录 前言 一、分片概念 二、搭建集群的步骤 总结 前言 MongoDB分片&#xff08;Sharding&#xff09;是一种水平扩展数据库的方法&#xff0c;它允许将数据分散存储在多个服务器上&#xff0c;从而提高数据库的存储容量和处理能力。分片是MongoDB为了应对大数据量和高吞吐量需…

13 - Python网络编程入门

网络编程入门 计算机网络基础 计算机网络是独立自主的计算机互联而成的系统的总称&#xff0c;组建计算机网络最主要的目的是实现多台计算机之间的通信和资源共享。今天计算机网络中的设备和计算机网络的用户已经多得不可计数&#xff0c;而计算机网络也可以称得上是一个“复…

idea MarketPlace插件找不到

一、背景 好久没用idea了&#xff0c;打开项目后没有lombok&#xff0c;安装lombok插件时发现idea MarketPlace插件市场找不到&#xff0c;需要重新配置代理源&#xff0c;在外网访问时通过代理服务进行连接 二、操作 ### File-->setting 快捷键 Ctrl Alt S 远端源地…

uni-app 使用Pinia进行全局状态管理并持久化数据

1.引言 最近在学习移动端的开发&#xff0c;使用uni-app前端应用框架&#xff0c;通过学习B站的视频以及找了一个开发模板&#xff0c;终于是有了一些心得体会。 B站视频1&#xff1a;Day1-01-uni-app小兔鲜儿导学视频_哔哩哔哩_bilibili B站视频2&#xff1a;01-课程和uni的…

hdu物联网硬件实验1 小灯闪烁

物联网硬件基础实验报告 学院 班级 学号 姓名 日期 成绩 实验题目 配置环境小灯 实验目的 配置环境以及小灯闪烁 硬件原理 无 关键代码及注释 /* Blink The basic Energia example. Turns on an LED on for one second, then off for one sec…

01 Web基础与HTTP协议

1.1 Web 基础 本章将介绍 Web 基础知识&#xff0c;包括域名的概念、DNS 原理、静态网页和动态网页的相关知识。 1.1.1.域名概述 1.域名的概念 ip地址不易记忆 2.早期使用host文件解析域名 主机名重复主机维护困难 3.DNS 分布式层次式 4.域名空间结构 根域顶级域 组…

在原有的iconfont.css文件中加入新的字体图标

前言&#xff1a;在阿里图标库中&#xff0c;如果你没有这个字体图标的线上项目&#xff0c;那么你怎么在本地项目中的原始图标文件中添加新的图标呢&#xff1f; 背景&#xff1a;现有一个vue项目&#xff0c;下面是这个前端项目的字体图标文件。现在需要新开发功能页&#x…

使用POI实现Excel文件的读取(超详细)

目录 一 导入poi相关的maven坐标 二 实现创建并且写入文件 2.1实现步骤 2.2实现代码 2.3效果展示 ​编辑 2.4注意 三 实现从Excel文件中读取数据 3.1实现步骤 3.2实现代码 3.3结果展示 一 导入poi相关的maven坐标 <!-- Apache poi --><dependency><gro…

【JVM-04】线上CPU100%

【JVM-04】线上CPU100% 1. 如何排查2. 再举一个例子 1. 如何排查 ⼀般CPU100%疯狂GC&#xff0c;都是死循环的锅&#xff0c;那怎么排查呢&#xff1f;先进服务器&#xff0c;⽤top -c 命令找出当前进程的运⾏列表按⼀下 P 可以按照CPU使⽤率进⾏排序显示Java进程 PID 为 2609…

LeetCode题练习与总结:排序链表--148

一、题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5]示例 3&am…

HTTP与HTTPS的主要区别

HTTP&#xff08;超文本传输协议&#xff09;与HTTPS&#xff08;超文本传输安全协议&#xff09;的主要区别在于安全性、数据传输方式、默认使用的端口以及对网站的影响。 一、安全性&#xff1a; HTTP是一种无加密的协议&#xff0c;数据在传输过程中以明文形式发送&#x…

2024年APMCM亚太杯中文赛A题——飞行器外形的优化问题

飞行器外形的优化问题 解题思路问题一第一问结果第一问代码 完整答案 本篇文章为大家分享2024年APMCM亚太杯中文赛A题——飞行器外形的优化问题的解题思路以及第一问的完整求解代码与结果&#xff0c;四问的完整解答请看文章最后&#xff01; 解题思路 飞行器是在大气层内或大…