ThreeJs制作模型图片

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

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

这里先找一副图片:

        下面开始代码:

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

 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);}};},

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

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

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

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

完整代码如下:

<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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/535351.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何开发SDK项目,详细带领理解实践

❤ 作者主页&#xff1a;李奕赫揍小邰的博客 ❀ 个人介绍&#xff1a;大家好&#xff0c;我是李奕赫&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 记得点赞、收藏、评论⭐️⭐️⭐️ &#x1f4e3; 认真学习!!!&#x1f389;&#x1f389; 文章目录 SDK-Starter …

前端面试题汇总

基础面试题 1.new 操作符做了那些事 function Fun(name){this.name name } Fun.prototype.sayHi funtion(){console.log(this.name) }function mockCreate(fn, ...args){let obj Object.create({}) // 创建一个空对象Object.setPrototypeOf(obj, fn.prototype) // 空对象…

记OnlyOffice的两个大坑

开发版&#xff0c;容器部署&#xff0c;试用许可已安装。 word&#xff0c;ppt&#xff0c;excel均能正常浏览。 自带的下载菜单按钮能用。 但config里自定义的downloadAs方法却不一而足。 word能正常下载&#xff0c;excel和ppt都不行。 仔细比对调试了代码。发现app.js…

供应链金融翻译,英译中如何翻译比较好

在供应链金融领域&#xff0c;英文翻译是一项至关重要的任务。由于供应链金融涉及多个行业、多方参与者和错综复杂的金融操作&#xff0c;翻译人员需要具备深厚的专业知识和精湛的翻译技巧。那么&#xff0c;供应链金融英文怎么翻译成中文 &#xff0c;北京哪个翻译公司比较专业…

手把手写深度学习(23):视频扩散模型之Video DataLoader

手把手写深度学习(0)&#xff1a;专栏文章导航 前言&#xff1a;训练自己的视频扩散模型的第一步就是准备数据集&#xff0c;而且这个数据集是text-video或者image-video的多模态数据集&#xff0c;这篇博客手把手教读者如何写一个这样扩散模型的的Video DataLoader。 目录 准…

一文告诉你服务器为什么要托管?

IDC的全称是Internet Data Center&#xff0c;即“互联网数据中心”&#xff0c;现在大家都称作“IDC数据中心” 。 什么是IDC服务器托管服务&#xff1f; 服务器托管是企业用户为提高公司效益、压缩成本&#xff0c;将自身企业的服务器及相关设备放到专业IDC服务商所建设的数…

考研C语言复习进阶(1)

目录 1. 数据类型介绍 1.1 类型的基本归类&#xff1a; 2. 整形在内存中的存储 2.1 原码、反码、补码 2.2 大小端介绍 3. 浮点型在内存中的存储 ​编辑 1. 数据类型介绍 前面我们已经学习了基本的内置类型&#xff1a; char //字符数据类型 short //短整型 int /…

10个调研分析模板,轻松搞定市场调查与分析!

市场调查与分析&#xff0c;对于任何一家企业来说&#xff0c;都是不可或缺的一环。对进入市场开展业务的企业而言&#xff0c;不管处于哪个阶段——初创公司&#xff0c;抑或是已经稳定运营的企业&#xff0c;了解市场动态和客户需求总是至关重要的。 但必须承认的是&#xf…

探索C++中的动态数组:实现自己的Vector容器

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

mybatis基础操作(三)

动态sql 通过动态sql实现多条件查询&#xff0c;这里以查询为例&#xff0c;实现动态sql的书写。 创建members表 创建表并插入数据&#xff1a; create table members (member_id int (11),member_nick varchar (60),member_gender char (15),member_age int (11),member_c…

什么时候去检测大数据信用风险比较合适?

什么时候去检测大数据信用风险比较合适?在当今这个数据驱动的时代&#xff0c;大数据信用风险检测已经成为个人的一项重要需求。本文将从贷前检测、信息泄露检测和定期检测三个方面&#xff0c;阐述何时进行大数据信用风险检测较为合适。 一、贷前检测 大数据信用风险检测在贷…

指针【理论知识速成】(3)

一.指针的使用和传值调用&#xff1a; 在了解指针的传址调用前&#xff0c;先来额外了解一下 “传值调用” 1.传值调用&#xff1a; 对于来看这个帖子的你相信代码展示胜过千言万语 #include <stdio.h> #include<assert.h> int convert(int a, int b) {int c 0…