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

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部