ThreeJs学习笔记【day10】纹理 【1】

253 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情 >>

前言

今天属于是文思如泉涌了,之前的两篇文章探索了threeJs提供的材质相关的类,这一篇就开始进行纹理的学习

纹理与材质的关系是什么?

threeJs的官方教程里没有讲的特别清楚,只是举了很多例子去说明,这里,我尝试进行一下解释,举个栗子,相信看到文章的各位或多或少玩儿过游戏,不管是单机的联机的,氪金的靠肝的,里面大多都能接触到一种道具叫做皮肤,以英雄联盟为例,英雄没变,换了个皮肤,技能特效就变得绚丽,而且越新出的,越贵的,越华丽,这里英雄本身和他皮肤之间的关系,应该就是材质与纹理的关系,纹理是对材质的具体表达与定义

创建纹理

纹理一般是指我们常见的在一些第三方程序中创建的图像,如Photoshop或GIMP。比如我们把这张图片放在立方体上。

在实例化材质的时候map属性设置为纹理

 const loader = new THREE.TextureLoader();

const material = new THREE.MeshBasicMaterial({
    map: loader.load('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1113%2F011220113I3%2F200112113I3-7-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1662271043&t=9da19d7e7c68544b9776ec8b3f345663'),
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

image.png 需要注意的是,TextureLoader是一个异步的过程,只是threeJs会在纹理加载完成前,显示为透明纹理,直到纹理加载完成后再重新渲染,这样不会阻塞代码继续执行,TextureLoader也支持传入回调的方式进行处理,回调会在纹理加载完成后触发,但是一般来说,等待加载与不阻塞加载没有明显的差异

多纹理阻塞加载

假如想要在多张纹理图加载完成后再进行渲染,则需要借助LoadingManager

  const loadManager = new THREE.LoadingManager();
  const loader = new THREE.TextureLoader(loadManager);
  const materials = [
      new THREE.MeshBasicMaterial({map: loader.load('https://threejs.org/manual/examples/resources/images/flower-1.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('https://threejs.org/manual/examples/resources/images/flower-2.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('https://threejs.org/manual/examples/resources/images/flower-3.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('https://threejs.org/manual/examples/resources/images/flower-4.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('https://threejs.org/manual/examples/resources/images/flower-5.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('https://threejs.org/manual/examples/resources/images/flower-6.jpg')}),
    ];
loadManager.onLoad = () => {
const cube = new THREE.Mesh(geometry, materials);
scene.add(cube);
cubes.push(cube);  // 添加到我们要旋转的立方体数组中
};

image.png LoadingManager和Promise.all的问题是一样的,Promise.all(arr)假如有一个请求失败了,则会整体失败,LoadingManager不一样,假如loader下载的资源有一个资源下载出错,它内部会做一个优化,按照材质与纹理不一一对应进行处理,会放上重复的纹理,当然,这个耗时会比较久

多纹理的其他实现

在其他3D引擎中,如果你想在一个几何体上使用多个图像,使用 纹理图集(Texture Atlas) 更为常见,性能也更高。纹理图集是将多个图像放在一个单一的纹理中,然后使用几何体顶点上的纹理坐标来选择在几何体的每个三角形上使用纹理的哪些部分。 什么是纹理坐标?它们是添加到一块几何体的每个顶点上的数据,用于指定该顶点对应的纹理的哪个部分。

看见这个纹理图集的描述,是不是感觉似曾相似?对咯,这他喵不就是3D版的雪碧图?spriteImg,页面请求优化技术之一,原理是将多张图片集合到一张上,通过background-position属性传入具体的x,y坐标,也就是top,left值,来展示不同的图像,以减少http请求,降低流量。纹理图集就是多了一个Z轴坐标罢了(愚人浅见,仅代表当下的个人观点)

纹理的内存管理

一般来说,纹理会占用 宽度 * 高度 * 4 * 1.33 字节的内存

假设一张图,大小为157K,分辨率为3024 * 3761 那么这张图占用的内存就是

3024 * 3761 * 4 * 1.33 = 60505764.5

也就是60M,要是多来几个,那你内存一会儿就用完了,所以,纹理一定要小,不只是大小小,而且要分辨率小!