實現模型貼圖的移動縮放旋轉

技術: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.pswp.cn/diannao/41848.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/41848.shtml
英文地址,請注明出處:http://en.pswp.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.輸入文件地址* ============================================* 操…

【路徑規劃】基于A星算法實現機器人柵格地圖徑規劃附Matlab代碼

% 機器人柵格地圖路徑規劃(A*算法) % 假設你已經有了柵格地圖數據和起點終點坐標 % 柵格地圖數據 grid_map = your_grid_map_data; % 柵格地圖數據,0表示可行區域,1表示障礙物區域 % 起點和終點坐標 start = your_start_coordinates; % 起點坐標,格式為[x, y] goal = yo…

【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; 二…

RedHat運維-Linux存儲管理基礎1-添加分區、文件系統、持續性掛載

1. 假如當前系統上ls -alh /dev | grep ^b的結果如下所示&#xff0c;那么&#xff1a; [rhcerhel9 ~]$ ls -alh /dev | grep ^b brw-rw----. 1 root disk 253, 0 Jun 7 19:46 dm-0 brw-rw----. 1 root disk 253, 1 Jun 7 19:46 dm-1 brw-rw----. 1 root disk …

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…