当前位置: 首页 > news >正文

08-three.js Textures

Three.js Journey — Learn WebGL with Three.jsThe ultimate Three.js course whether you are a beginner or a more advanced developerhttps://threejs-journey.com/?c=p3

使用原生 JavaScript

首先是静态页面的放置位置,如果使用Vite模版配置,可以直接放在 /static/ 文件夹中,引用参考:

const imageSource = '/files/image.png'console.log(imageSource)

简单带过一下使用原生JavaScript的步骤:

  1. 创建 Image 实例
  2. 监听 image 的 load 事件
  3. 将texture变量提升为全局变量,并使用 Texture 类创建纹理
  4. 设置 image.src 属性加载图片,再将texture变量提升为全局变量
  5. 将texture应用到material中
const image = new Image()
const texture = new THREE.Texture(image)
texture.colorSpace = THREE.SRGBColorSpace  // 修正 `sRGB` 颜色空间造成的纹理色值偏差
image.addEventListener('load', () =>
{texture.needsUpdate = true  // 更新在函数外部创建的纹理
})
image.src = '/textures/door/color.jpg'// ...const material = new THREE.MeshBasicMaterial({ map: texture })

用于材质的 map 或 matcap 属性的纹理应该以 sRGB 编码,需要将 colorSpace 设置为 THREE.sRGBColorSpace,但仅限于这些纹理。

总结关键点:

1.  texture.colorSpace = THREE.SRGBColorSpace

2. texture.needsUpdate = true

【效果图】

使用 TextureLoader

将上面原生JavaScript的引入改为 THREE.TextureLoader() 引入:

const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('/textures/door/color.jpg')
texture.colorSpace = THREE.SRGBColorSpace

可以使用一个 TextureLoader 实例加载任意多的纹理(一对多)。如果现在有多个图片需要加载,并且希望共享事件(比如在所有图片加载完成后接收通知),可以使用THREE.LoadingManager() 与THREE.TextureLoader() 配合:

const loadingManager = new THREE.LoadingManager()
const textureLoader = new THREE.TextureLoader(loadingManager)

如果想要显示加载器并在所有资源加载完成后隐藏它,LoadingManager 对这点非常有用,可以通过将以下属性替换为自己的函数来监听各种事件。

// LoadingManager的使用
const loadingManager = new THREE.loadingManager()
// 可以通过将以下属性替换为自己的函数来监听各种事件
loadingManager.onStart = ()=> {console.log('loading started!');    
}
loadingManager.onLoad = ()=> {console.log('loading finished!');    
}
loadingManager.onProgress = ()=> {console.log('loading progressing!');    
}
loadingManager.onError = ()=> {console.log('loading error!');    
}
const TextureLoader = new THREE.TextureLoader(loadingManager)

【扩展】ExrLoader

这次下载的素材中有以 .exr 后缀结尾的文件,经查需要通过 ExrLoader 进行导入,直接用

TextureLoader() 会报错,二者可以一起与LoadingManager 使用,如下:

/*** TextureLoader*/
// LoadingManager的使用
const loadingManager = new THREE.LoadingManager()
// 可以通过将以下属性替换为自己的函数来监听各种事件
loadingManager.onStart = () => {console.log('loading started!')
}
loadingManager.onLoad = () => {console.log('loading finished!')
}
loadingManager.onProgress = () => {console.log('loading progressing!')
}
loadingManager.onError = () => {console.log('loading error!')
}
const TextureLoader = new THREE.TextureLoader(loadingManager)/*** 【扩展】EXRLoader*/
// 使用同一个loadingManager创建加载器
const ExrLoader = new EXRLoader(loadingManager);// 加载所有需要的图片
const textures = {diffTexture: TextureLoader.load('/textures/metal_plate_diff_1k.jpg'),dispTexture: TextureLoader.load('/textures/metal_plate_disp_1k.png'),metalTexture: ExrLoader.load('/textures/metal_plate_metal_1k.exr'),norTexture: ExrLoader.load('/textures/metal_plate_nor_gl_1k.exr'),roughTexture: ExrLoader.load('/textures/metal_plate_rough_1k.exr'),
}textures.diffTexture.colorSpace = THREE.SRGBColorSpace

repeat

textures.diffTexture.colorSpace = THREE.SRGBColorSpace
textures.diffTexture.repeat.x = 2
textures.diffTexture.repeat.y = 3

上面的纹理没有重复,反而显得更小,最后一个像素似乎被拉伸了,因为默认情况下纹理没有设置为重复。通过使用 THREE.RepeatWrapping 常量更新 wrapSwrapT 属性,如下:

textures.diffTexture.colorSpace = THREE.SRGBColorSpace
textures.diffTexture.repeat.x = 2
textures.diffTexture.repeat.y = 3
textures.diffTexture.wrapS = THREE.RepeatWrapping  // `wrapS` 用于 `x` 轴重复
textures.diffTexture.wrapT = THREE.RepeatWrapping  // `wrapT` 用于 `y` 轴重复

可以使用 THREE.MirroredRepeatWrapping 更改方向(镜像):

textures.diffTexture.colorSpace = THREE.SRGBColorSpace
textures.diffTexture.repeat.x = 2
textures.diffTexture.repeat.y = 3
// textures.diffTexture.wrapS = THREE.RepeatWrapping  // `wrapS` 用于 `x` 轴重复
// textures.diffTexture.wrapT = THREE.RepeatWrapping  // `wrapT` 用于 `y` 轴重复
textures.diffTexture.wrapS = THREE.MirroredRepeatWrapping
textures.diffTexture.wrapT = THREE.MirroredRepeatWrapping

offset

可以使用 offset 属性来偏移纹理,该属性也是一个 Vector2(具有 x 和 y 属性),更改这些值将简单地偏移 UV 坐标:

textures.diffTexture.offset.x = 0.5
textures.diffTexture.offset.y = 0.5

rotation

旋转基准点见下方旋转基准点说明。

textures.diffTexture.rotation = Math.PI * 0.25

UV展开

纹理以不同的方式被拉伸或挤压以覆盖几何形状,叫做 UV 展开,可以想象成像展开折纸或糖果纸一样将其展平。每个顶点将在一个平面(通常是正方形)上有一个 2D 坐标,可以在 geometry.attributes.uv 属性中看到这些 UV 2D 坐标:

console.log(geometry.attributes.uv)

旋转基准点

`0, 0` UV 坐标在立方体面的左下角,如果想改变旋转时的基准点,可以使用 center 属性,将以立方体中心为旋转点:

textures.diffTexture.rotation = Math.PI * 0.25
textures.diffTexture.center.x = 0.5
textures.diffTexture.center.y = 0.5

过滤和 mip 映射

缩小滤波器

当纹理的像素小于渲染像素时会发生压缩滤镜。换句话说,纹理对于它覆盖的表面来说太大了。

可以通过minFilter属性来更改纹理的压缩滤镜,有6种可能的值:

  • THREE.NearestFilter

  • THREE.LinearFilter

  • THREE.NearestMipmapNearestFilter

  • THREE.NearestMipmapLinearFilter

  • THREE.LinearMipmapNearestFilter

  • THREE.LinearMipmapLinearFilter 【默认值】

放大滤镜

当纹理的像素大于渲染像素时,换句话说,纹理对于它覆盖的表面来说太小了。

可以使用 magFilter 属性来更改纹理的放大滤镜,有2种可能的值:

  • THREE.NearestFilter

  • THREE.LinearFilter【默认值】

【注意】仅在 minFilter 属性中使用 mipmaps。如果使用的是 THREE.NearestFilter,则不需要 mipmaps,并可以通过 colorTexture.generateMipmaps = false 关闭它们,这将稍微减轻 GPU 的负担:

colorTexture.generateMipmaps = false
colorTexture.minFilter = THREE.NearestFilter

工具网站

压缩图片工具网站

https://tinypng.com/

纹理素材网站

poliigon.com

3dtextures.me

arroway-textures.ch

此次使用到的素材

Metal Plate Texture • Poly Haven

练习

练习,通过按钮切换 material 的 texture

练习,通过按钮切换几何体

【完整代码】

// Texture Link: https://polyhaven.com/a/metal_plateimport * as THREE from 'three'
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import gsap from 'gsap'
import GUI from 'lil-gui'
// console.log(GUI);
// console.log(EXRLoader);/*** TextureLoader*/
// 使用 TextureLoader
// const TextureLoader = new THREE.TextureLoader()
// 可以在路径后发送3个函数。这些函数将在以下事件中被调用,依次分别是
// 1.load 当图像加载成功时  2.progress 当加载进行中时  3.error 如果发生错误 
// const texture = TextureLoader.load(
//     '/textures/metal_plate_diff_1k.jpg',
//     () => {
//         console.log('loading finished');//     },
//     () => {
//         console.log('loading progressing');//     },
//     () => {
//         console.log('loading error');//     },
// )/*** TextureLoader*/
// LoadingManager的使用
const loadingManager = new THREE.LoadingManager()
// 可以通过将以下属性替换为自己的函数来监听各种事件
loadingManager.onStart = () => {console.log('loading started!')
}
loadingManager.onLoad = () => {console.log('loading finished!')
}
loadingManager.onProgress = () => {console.log('loading progressing!')
}
loadingManager.onError = () => {console.log('loading error!')
}
const TextureLoader = new THREE.TextureLoader(loadingManager)/*** 【扩展】EXRLoader*/
// 使用同一个loadingManager创建加载器
const ExrLoader = new EXRLoader(loadingManager);// 加载所有需要的图片
const textures = {diffTexture: TextureLoader.load('/textures/metal_plate_diff_1k.jpg'),dispTexture: TextureLoader.load('/textures/metal_plate_disp_1k.png'),metalTexture: ExrLoader.load('/textures/metal_plate_metal_1k.exr'),norTexture: ExrLoader.load('/textures/metal_plate_nor_gl_1k.exr'),roughTexture: ExrLoader.load('/textures/metal_plate_rough_1k.exr'),
}textures.diffTexture.colorSpace = THREE.SRGBColorSpace
// textures.diffTexture.repeat.x = 2
// textures.diffTexture.repeat.y = 3
textures.diffTexture.wrapS = THREE.RepeatWrapping  // `wrapS` 用于 `x` 轴重复
textures.diffTexture.wrapT = THREE.RepeatWrapping  // `wrapT` 用于 `y` 轴重复
// textures.diffTexture.wrapS = THREE.MirroredRepeatWrapping
// textures.diffTexture.wrapT = THREE.MirroredRepeatWrapping// const diffTexture = TextureLoader.load('/textures/metal_plate_diff_1k.jpg')
// const dispTexture = TextureLoader.load('/textures/metal_plate_disp_1k.png')
// const metalTexture = ExrLoader.load('/textures/metal_plate_metal_1k.exr')
// const norTexture = ExrLoader.load('/textures/metal_plate_nor_gl_1k.exr')
// const roughTexture = ExrLoader.load('/textures/metal_plate_rough_1k.exr')// Canvas
const canvas = document.querySelector('canvas.webgl')// Scene
const scene = new THREE.Scene()/*** Object*/
const geometries = {BoxGeometry: new THREE.BoxGeometry(1, 1, 1),SphereGeometry: new THREE.SphereGeometry(1, 32, 32),ConeGeometry: new THREE.ConeGeometry(1, 2, 32),TorusGeometry: new THREE.TorusGeometry(1, 0.4, 16, 100)
};
const geometry = geometries.BoxGeometry
// 使用纹理 texture
const material = new THREE.MeshBasicMaterial({ map: textures.diffTexture })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)mesh.position.x = 0/*** Sizes*/
const sizes = {width: window.innerWidth,height: window.innerHeight
}/*** Camera*/// 自定义控制
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 1000)
camera.position.z = 3
scene.add(camera)/*** Controls*/
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true/*** Renderer*/
const renderer = new THREE.WebGLRenderer({canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.render(scene, camera)const tick = () => {// Update controlscontrols.update()renderer.render(scene, camera)window.requestAnimationFrame(tick)
}
tick()window.addEventListener('resize', () => {// 1. 更新 sizessizes.width = window.innerWidthsizes.height = window.innerHeight// 2.1 更新 camera aspect 纵横比camera.aspect = sizes.width / sizes.height// 2.2 更新 aspect 时要配合更新投影矩阵 updateProjectionMatrixcamera.updateProjectionMatrix()// 3. 更新 rendererrenderer.setSize(sizes.width, sizes.height)renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))})window.addEventListener('dblclick', () => {// 注释原来的双击全屏if (!document.fullscreenElement) {canvas.requestFullscreen();} else {document.exitFullscreen();}})// 练习// 生成按钮,控制切换
// const buttonLabels = ['diffTexture', 'dispTexture', 'metalTexture', 'norTexture', 'roughTexture']
// const buttonLabels = ['BoxGeometry', 'SphereGeometry', 'ConeGeometry', 'TorusGeometry']
// const buttonLabels = ['偏移', '复原']
const buttonLabels = ['旋转', '复原']
const buttonsHTML = `
<div class="action-buttons" style="position: absolute;">${buttonLabels.map(label =>`<button class="action-btn" style="width: 110px; margin: 16px; cursor: pointer;">${label}</button>`
).join('')}
</div>
`;
// 添加到文档
document.body.insertAdjacentHTML('beforeend', buttonsHTML);// 生成按钮,控制切换 ENDlet currentMesh = mesh; // 保存当前网格对象// 为按钮添加事件
document.querySelectorAll('.action-btn').forEach(btn => {btn.addEventListener('click', function () {// 切换纹理// const textureType = this.textContent// material.map = textures[textureType]// material.needsUpdate = true;// 切换纹理 END// 切换几何体// if (currentMesh && scene.children.includes(currentMesh)) {//     scene.remove(currentMesh);//     // 可选:释放几何体内存//     currentMesh.geometry.dispose();// }// // 创建新的几何体网格// const geometryType = this.textContent;// const geometry = geometries[geometryType];// if (!geometry) {//     console.error('未找到几何体:', geometryType);//     return;// }// currentMesh = new THREE.Mesh(geometry, material);// scene.add(currentMesh);// // 3. 调试输出// // console.log('当前场景对象:', scene.children);// console.log(geometry.attributes.uv)// 切换几何体 END// 偏移与复原// if (this.textContent == '偏移') {//     textures.diffTexture.offset.x = 0.5//     textures.diffTexture.offset.y = 0.5// } else {//     textures.diffTexture.offset.x = 0//     textures.diffTexture.offset.y = 0// }// 旋转与复原if (this.textContent == '旋转') {textures.diffTexture.rotation = Math.PI * 0.25} else {textures.diffTexture.rotation = 0}});
});


项目创建参考

01-three.js vite基础示例_three.js示例-CSDN博客文章浏览阅读392次。three.js 基本示例代码_three.js示例 https://blog.csdn.net/gaowxx/article/details/147954918?spm=1001.2014.3001.5501

http://www.dtcms.com/a/265903.html

相关文章:

  • day15——Java常用API(二):常见算法、正则表达式与异常处理详解
  • 【机器学习深度学习】AI 项目开发流程:从需求到部署的五大阶段
  • Springboot3整合ehcache3缓存--XML配置和编程式配置
  • 移除 Java 列表中的所有空值
  • 一天两道力扣(1)
  • Linux多线程(十二)之【生产者消费者模型】
  • “Payload document size is larger than maximum of 16793600.“问题解决(MongoDB)
  • Kettle数据抽取(十一)作业-邮件
  • 什么是码率?剪映中如何选择适合的视频码率
  • C++(std::sort)
  • js-cookie详细介绍
  • Node.js与Webpack
  • 2025年6月:技术探索与生活平衡的协奏曲
  • 目标检测:从基础原理到前沿技术全面解析
  • 架构师的“降维打击”:用桥接模式,把 N*M 的问题变成 N+M
  • Matplotlib 安装使用教程
  • 【Git】同时在本地使用多个github账号进行github仓库管理
  • C++ 网络编程(14) asio多线程模型IOThreadPool
  • 【数据结构】树的基本操作
  • 阿里云服务网格ASM实践
  • 抗辐照芯片在核电厂火灾探测器中的应用优势与性能解析
  • springMvc的简单使用:要求在浏览器发起请求,由springMVC接受请求并响应,将个人简历信息展示到浏览器
  • Java 原生 HTTP Client
  • https如何利用工具ssl证书;使用自己生成的证书
  • http、SSL、TLS、https、证书
  • 【交互设计】UI 与 UX 简介:从核心概念到行业实践
  • 微算法科技(NASDAQ MLGO)基于量子图像处理的边缘检测算法:开拓图像分析新视野
  • [2025CVPR]SEEN-DA:基于语义熵引导的领域感知注意力机制
  • 通过观看数百个外科手术视频讲座来学习多模态表征|文献速递-最新论文分享
  • 【数据结构】哈希——闭散列/开散列模拟实现(C++)