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

技术: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各个位置的缩放,还是很有趣的~

相关推荐
qianbo_insist8 天前
ue 材质贴图Tiling repeat
材质·贴图
哈市雪花9 天前
3ds Max导出fbx贴图问题简单记录
3dsmax·贴图·fbx·fbxsdk·fbx2gltf
Yuulily12 天前
vface贴图使用说明
贴图·vface·maya置换贴图·arnold置换
霜晨月c17 天前
贴图法美化Button按钮
笔记·学习·mfc·贴图
Mac分享吧20 天前
iCopy for Mac 剪切板 粘贴工具 历史记录 安装(保姆级教程,新手小白轻松上手)
经验分享·笔记·macos·mac·学习方法·软件需求·贴图
一叶飘零晋24 天前
Threejs-09、贴图的加载与环境遮蔽强度设置
vue.js·3d·贴图
岩岩很哇塞!1 个月前
three.js使用环境贴图或者加载hdr图
javascript·vue.js·three.js·贴图
月巴月巴白勺合鸟月半1 个月前
C# 绘图及古诗填字
前端·数据分析·c#·贴图
陈言必行1 个月前
Unity 之 代码修改材质球贴图
unity·材质·贴图