0
点赞
收藏
分享

微信扫一扫

ThreeJs制作模型图片

乱世小白 03-14 09:45 阅读 3

 这个标题名字可能有歧义,只是不知道如何更好的表达,总之就是将图片的像素转换成3D场景的模型,并设置这个模型的颜色,放到像素点对应的位置从而拼接成一个图片,起因是上文中用js分解了音乐,实现了模型跳动效果,既然音频可以分解,那图片应该也可以,所以就有个这篇博客。

        大概得实现逻辑是这样的,先找一个图片,像素要小,越小越好,要有花纹,然后用canvas将图片的每个像素拆解出来,拆解后可以获得这个图片每个像素的位置,颜色,用集合保存每个像素的信息,在3D场景中循环,有了位置和颜色后,在循环中创建一个个正方体,将正方体的位置设置为像素的位置,y轴方向为1,创建贴图,并将贴图的颜色改为像素点的颜色,全部循环后就得到一副用正方体拼接出来的图片了。但是如果你的图片分辨率高,那么拆解出来的像素点很多,就需要筛选掉一些,否则浏览器会卡死,所以强调用分辨率低的图片。

这里先找一副图片:

ThreeJs制作模型图片_ThreeJs

下面开始代码:

首先创建场景,相机,灯光,渲染器等:

 initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
    initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
    },

然后封装一个用canvas分解图片的方法

getImagePixels(image) {
      return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;
        ctx.drawImage(image, 0, 0);
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const data = imageData.data;
        const pixels = [];
        for (let i = 0; i < data.length; i += 4) {
          const x = (i / 4) % canvas.width;
          const y = Math.floor((i / 4) / canvas.width);
          const r = data[i];
          const g = data[i + 1];
          const b = data[i + 2];
          const a = data[i + 3];
          pixels.push({ x, y, r, g, b, a });
        }
        resolve(pixels); // 返回所有像素的数据数组
      });
    },

然后调用这个方法获取到像素点集合信息,再循环这个集合,这里为了不卡顿,选择每40个像素点才生成一个模型,也就是下面的i%40===0的判断,

initBox(){
      const img = new Image();
      img.src = '/static/images/image.jpg';
      let geometry = new THREE.BoxGeometry(1, 1, 1);
      img.onload = async () => {
        let boxModel = []
        try {
          const allPixels = await this.getImagePixels(img);
          for (let i = 0; i < allPixels.length; i++) {
            if(i%40 === 0) {
              let r = allPixels[i].r;
              let g = allPixels[i].g;
              let b = allPixels[i].b;
              let x = allPixels[i].x;
              let y = allPixels[i].y;
              let cubeMaterial = new THREE.MeshPhysicalMaterial({color: 'rgb(' + r + ', ' + g + ', ' + b + ')'});
              this.boxMaterial.push(cubeMaterial)
              let mesh = new THREE.Mesh(geometry.clone(), cubeMaterial);
              mesh.position.set(x, 1, y);
              mesh.updateMatrix() // 更新投影矩阵,不更新各mesh位置会不正确
              boxModel.push(mesh.geometry.applyMatrix4(mesh.matrix));
            }
          }
          const boxGeometry = mergeGeometries(boxModel,true)
          let result = new THREE.Mesh(boxGeometry, this.boxMaterial)
          scene.add(result);
          console.log("執行完畢")
        } catch (error) {
          console.error('Error getting image pixels:', error);
        }
      };
    },

最终得到一副比较虚幻的图片

ThreeJs制作模型图片_ThreeJs_02

因为每个模型之间距离比较远,所以图片比较阴暗和虚幻,为了提高图片效果,可以将模型的宽和高改为5,

let geometry = new THREE.BoxGeometry(5, 5, 5);

ThreeJs制作模型图片_ThreeJs_03

这样就真实点了,可以根据电脑性能来调整去选取的像素点个数,如果电脑足够好,也可以根据上一篇音乐的效果,给这个图片添加音乐效果的跳动。

完整代码如下:

<template>
  <div style="width:100px;height:100px;">
    <div id="container"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import {OrbitControls} from "three/addons/controls/OrbitControls";
import {mergeGeometries} from "three/addons/utils/BufferGeometryUtils";

let scene;
export default {
  name: "agv-single",
  data() {
    return{
      camera:null,
      cameraCurve:null,
      renderer:null,
      container:null,
      controls:null,
      imageData:[],
      boxMaterial:[],
    }
  },
  methods:{
    initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
    initBox(){
      const img = new Image();
      img.src = '/static/images/image.jpg';
      let geometry = new THREE.BoxGeometry(5, 5, 5);
      img.onload = async () => {
        let boxModel = []
        try {
          const allPixels = await this.getImagePixels(img);
          for (let i = 0; i < allPixels.length; i++) {
            if(i%40 === 0) {
              let r = allPixels[i].r;
              let g = allPixels[i].g;
              let b = allPixels[i].b;
              let x = allPixels[i].x;
              let y = allPixels[i].y;
              let cubeMaterial = new THREE.MeshPhysicalMaterial({color: 'rgb(' + r + ', ' + g + ', ' + b + ')'});
              this.boxMaterial.push(cubeMaterial)
              let mesh = new THREE.Mesh(geometry.clone(), cubeMaterial);
              mesh.position.set(x, 1, y);
              mesh.updateMatrix() // 更新投影矩阵,不更新各mesh位置会不正确
              boxModel.push(mesh.geometry.applyMatrix4(mesh.matrix));
            }
          }
          const boxGeometry = mergeGeometries(boxModel,true)
          let result = new THREE.Mesh(boxGeometry, this.boxMaterial)
          scene.add(result);
          console.log("執行完畢")
        } catch (error) {
          console.error('Error getting image pixels:', error);
        }
      };
    },
    getImagePixels(image) {
      return new Promise((resolve) => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;
        ctx.drawImage(image, 0, 0);
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const data = imageData.data;
        const pixels = [];
        for (let i = 0; i < data.length; i += 4) {
          const x = (i / 4) % canvas.width;
          const y = Math.floor((i / 4) / canvas.width);
          const r = data[i];
          const g = data[i + 1];
          const b = data[i + 2];
          const a = data[i + 3];
          pixels.push({ x, y, r, g, b, a });
        }
        resolve(pixels); // 返回所有像素的数据数组
      });
    },
    initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
    initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
    },
    initPage(){
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initBox();
      this.initRenderer();
      this.initControl();

      this.initAnimate();
    }
  },
  mounted() {
    this.initPage()
  }
}
</script>

<style scoped>
#container{
  position: absolute;
  width:100%;
  height:100%;
  overflow: hidden;
}

</style>

举报

相关推荐

0 条评论