介绍
three.js 之 Texture 纹理
# 纹理贴图导入 TextureLoader()
纹理一般是指我们常见的在一些第三方程序中创建的图像,如PNG和JPG类型的图。我们把这张图片放在立方体上。(我通常称为
贴图
)。我们需要做的就是创建一个TextureLoader() (opens new window)。调用它的load方法,同时传入图像的URL,并将材质的 map 属性设置为该方法的返回值TextureLoader()
通常用来加载一张图片可以返回一个纹理对象Texture (opens new window) 作为一个表面,或者作为反射/折射贴图- 通过材质方法map (opens new window) 加载纹理贴图
TextureLoader()
也可以制作序列帧动画注意: three.js r84遗弃了TextureLoader进度事件。对于支持进度事件的TextureLoader
# 网格模型使用加载的纹理贴图
- 配合基础网格材质MeshBasicMaterial() (opens new window)的.map (opens new window) 把加载好纹理变成网格模型的贴图
// 导入纹理图片 作为贴图
import logo from '@/assets/logo.svg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建一个在网格模型中展示的几何体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1) // 默认就是1,1,1 宽高深度
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({ map: texture }) // 通过map使用纹理材质
# 纹理贴图的常用操作
- 以下纹理的属性 基于PBR标准网格材质(MeshStandardMaterial) (opens new window)进行设置 其他材质需要看文档 是否支持
# 设置偏移量 X Y
- 通过.offset (opens new window) 可以改变纹理的偏移量 参数是一个二维向量(Vector2) (opens new window)支持的修改方式:
- 直接修改
x
或y
- 通过Vector2的
.set(x,y)
批量修改 - 创建一个新的二维向量Vector2 进行修改
- 直接修改
// 1.直接修改
texture.offset.x = 0.5
texture.offset.y = 0.5
// 2.用set(x,y)进行批量修改
texture.offset.set(0.5, 0.5)
// 3.创建一个新的Vector2 进行修改
texture.offset = new THREE.Vector2(0.5, 0.5)
# 设置旋转弧度
- .rotation (opens new window) 可以设置纹理将围绕中心点旋转多少度 单位为弧度(rad)。正值为逆时针方向旋转,默认值为0。
- PI在数学方法中为π,而此时的π在角度里为180°,
Math.PI/180
就为1° - 弧度= 角度 *
Math.PI / 180
- PI在数学方法中为π,而此时的π在角度里为180°,
// 计算出弧度
const radians = Math.PI / 180
// 设置纹理的旋转
texture.rotation = 45 * radians
# 修改旋转中心点
- .center (opens new window) 可以设置旋转中心点 , (0.5, 0.5)对应纹理的正中心。默认值为(0,0),即左下角。通常配合
.rotation
进行使用 参数是一个二维向量(Vector2) (opens new window)
// 1.直接修改
texture.center.x = 0.5
texture.center.y = 0.5
// 2.用set(x,y)进行批量修改
texture.center.set(0.5, 0.5)
// 配合rotation旋转弧度使用
// 计算出弧度
const radians = Math.PI / 180
texture.rotation = 45 * radians
// 修改旋转中心点
texture.center.set(0.5, 0.5)
# 设置贴图重复和阵列效果
.repeat (opens new window) 设置贴图重复, 参数为二维向量(Vector2) (opens new window),代表x轴y轴对应的重复次数, 默认是(1, 1)
- 默认的贴图阵列效果是 THREE.ClampToEdgeWrapping (opens new window)将贴图推至到边远, 而非重复. 实现贴图重复阵列需要将贴图的
.wrapS
水平和.wrapT
垂直的阵列效果设置为: THREE.RepeatWrapping (opens new window)- .wrapS (opens new window) 定义了纹理贴图在水平方向上将如何阵列
- .wrapT (opens new window) 定义了纹理贴图在垂直方向上将如何阵列
// 设置贴图重复
// 设置水平方向上阵列
texture.wrapS = THREE.RepeatWrapping
// 设置垂直方向阵列
texture.wrapT = THREE.RepeatWrapping
// 1.设置重复次数
texture.repeat.set(3, 2) // 设置 x y轴的重复次数
// 2.直接xy轴的重复次数
texture.repeat.x = 2
texture.repeat.y = 3
设置重复后的纹理常量
THREE.ClampToEdgeWrapping
将贴图推至边缘 不进行重复操作
THREE.RepeatWrapping
将贴图按照重复次数 进行重复包裹
THREE.MirroredRepeatWrapping
将贴图按照重复次数 进行镜像方式的重复包裹
# 设置纹理采样
- 定义当一个纹理单元 覆盖多个像素点或者不足以覆盖对个像素点的时候 如何设置采样效果
- .magFilter (opens new window)当一个纹素覆盖大于一个像素时,贴图将如何采样。( 放大滤镜 )
- 默认的纹理采样是
THREE.LinearFilter
表示获取4个最近的纹理单元执行双向线性插值计算显示效果好
- 另外的选项是
THREE.NearestFilter
, 表示使用最近的texel性能优
- 默认的纹理采样是
- .minFilter (opens new window) 当一个纹素覆盖小于一个像素时,贴图将如何采样。( 缩小滤镜 )
THREE.NearestFilter
:最近滤镜。在纹理基层上执行最邻近过滤。性能优
THREE.NearestMipMapNearestFilter
:选择最临近的mip层,并执行最临近的过滤。THREE.NearestMipMapLinearFilter
:在mip层之间执行线性插补,并执行最临近的过滤。THREE.LinearFilter
:在纹理基层上执行线性过滤。显示效果好
THREE.LinearMipMapNearestFilter
:选择最临近的mip层,并执行线性过滤。THREE.LinearMipMapLinearFilter
:在mip层之间执行线性插补,并执行线性过滤。
- .magFilter (opens new window)当一个纹素覆盖大于一个像素时,贴图将如何采样。( 放大滤镜 )
简单的进行一个预览效果
采用一个 36x36的小图片 看看设置THREE.NearestFilter
和 THREE.LinearFilter
的显示差别
使用
THREE.LinearFilter
可以看出 小图片被模糊化了 three.js帮你优化了图片的展示效果 这种展示效果有点像一些调低画质的游戏THREE.LinearFilter
是默认纹理采样无需设置
// 通常不需要手动设置 贴图默认就是这种纹理采样 texture.minFilter = THREE.LinearFilter texture.magFilter = THREE.LinearFilter
使用
THREE.NearestFilter
可以很明细的看出来马赛克效果( 因为图片本来就很小 ) 不会对贴图进行一些平滑处理 这种展示效果很像我的世界或一些像素游戏THREE.NearestFilter
需要你手动设置这种纹理采样
texture.minFilter = THREE.NearestFilter texture.magFilter = THREE.NearestFilter
# 设置灰度/透明纹理
- .alphaMap (opens new window) 灰度/透明度纹理,用于控制整个表面的不透明度。(偏黑色:越透明;偏白色:越不透明)
- 使用
.alphaMap
灰度纹理的时候 需要把开启纹理的.transparent (opens new window) 是否透明设置为true
透明
- 使用
灰度纹理的作用
- 灰度纹理是用来实现 纹理局部透明 就跟栅栏一样 空隙的部分希望透明 不被遮挡
比如说这个门 只想要中间部分
就可能需要 灰度纹理进行处理
- 设置灰度纹理前的效果
- 设置灰度纹理后的效果 很明显看到 灰度纹理为黑色的地方变成了透明色 白色为显示的内容
// 导入纹理
import logo from '@/assets/door/color.jpg'
// 导入灰度纹理
import logoGray from '@/assets/door/alpha.jpg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建灰度纹理
const textureGray = textureLoader.load(logoGray)
// 创建一个在网格模型中展示的几何体
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3)
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置纹理贴图
map: texture,
// 设置灰度纹理贴图
alphaMap: textureGray,
// 设置透明度 一定要把透明度设置为true
transparent: true,
})
# 设置环境遮挡贴图
- .aoMap (opens new window) 可以设置环境遮挡贴图 设置后需要第二组UV:
uv2
坐标属性uv2
: 就是把第一组uv
坐标的值赋值给第二组uv
坐标
- .aoMapIntensity (opens new window) 可以设置环境遮挡的强度 默认是1 0是取消环境遮蔽效果
- 网格模型(mesh) 中的
.geometry
属性 实际上就是BufferGeometry (opens new window) 缓冲几何( 是面片、线或点几何体的有效表述。包括顶点位置,面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值 ) 通过其.setAttribute (opens new window)方法 给其设置一个属性 比如: 环境遮挡题图需要的uv2
坐标属性
环境遮挡设置后的效果
- 环境遮挡的贴图
- 设置环境遮挡后 可以看出 物体有明显的阴影效果 黑色的就是遮挡 白色的就是非遮挡
- 设置环境遮挡的代码
// 导入纹理
import logo from '@/assets/door/color.jpg'
// 导入环境遮挡贴图
import logoEnv from '@/assets/door/ambientOcclusion.jpg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建环境遮挡贴图
const textureEnv = textureLoader.load(logoEnv)
// 创建一个在网格模型中展示的几何体
// 参数为长宽高 以及长宽高的分段数 横截面,利于变形使用,段数越多越柔和,则段数越少越生硬。
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3)
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置纹理贴图
map: texture,
// 设置环境遮挡贴图
aoMap: textureEnv,
// 设置环境遮挡贴图强度
aoMapIntensity: 1, // 默认为1 最小值为0 最大值为1
})
// 创建一个网格模型 放入创建的几何体和其自身材质
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial) // Mesh(几何体, 纹理材质)
// 设置环境遮挡贴图第二组uv坐标 (就是把第一组uv坐标的值赋值给第二组uv坐标)
cube.geometry.setAttribute(
'uv2',
new THREE.Float32BufferAttribute(cube.geometry.attributes.uv.array, 2)
)
// 将几何体添加到场景中
scene.add(cube)
# 设置位移贴图(凹凸效果)
- .displacementScale (opens new window) 设置位移贴图(凹凸纹理) 需要设置网格模型的分段数(默认是1)和.displacementScale (opens new window) 设置位移贴图影响程度(凹凸的深度 最大1 默认值1)
- 分段数是横截面,利于变形使用,段数越多越柔和,则段数越少越生硬。
// 导入纹理
import logo from '@/assets/door/color.jpg'
// 导入置换纹理
import displacementMap from '@/assets/door/height.jpg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建置换纹理
const textureDisplacementMap = textureLoader.load(displacementMap)
// 创建一个在网格模型中展示的几何体
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3, 200, 200, 200) // 参数为长宽高 以及长宽高的分段数 分段数需要单独设置 默认是1
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置纹理贴图
map: texture,
// 使用置换纹理
displacementMap: textureDisplacementMap,
// 设置置换纹理强度
displacementScale: 0.1, // 默认为1 最小值为0 最大值为1
})
# 设置粗糙/光滑度
- .roughness (opens new window) 设置纹理贴图的整体粗糙度 默认为1 最小值为0 最大值为1 例如: 镜子反光明显他的的粗糙度就是0
- 粗糙度是物体表面反光的一种表现 越光滑的物体 反光越明显 相反越粗糙的物体反光就比较受限
- .roughnessMap (opens new window) 可以设置粗糙度贴图 也就是局部粗糙度
- 如果同时设置
.roughness
和.roughnessMap
那么两个值将会相乘 效果更明显
// 导入纹理
import logo from '@/assets/door/color.jpg'
// 导入粗糙度贴图
import roughness from '@/assets/door/roughness.jpg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建粗糙度贴图
const textureRoughness = textureLoader.load(roughness)
// 创建一个在网格模型中展示的几何体
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3)
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置纹理贴图
map: texture,
// 设置粗糙度贴图
roughnessMap: textureRoughness
// 设置粗糙度
roughness: 0.5 // 默认为0.5 最小值为0 最大值为1
})
# 设置金属度
.metalness (opens new window) 设置纹理贴图的整体金属度 **默认为0 最小值为0 最大值为1 **
- 金属度代表了有多少光子是直接被反射出去, 有多少光子在进入体内,后成了漫反射
- 金属度等于0, 或者很低的情况下, 直接反射会变得非常弱, 只有漫反射
- 金属度如果等于1的情况下, 所有的光子都会被反射出去, 形成镜面反射效果
.metalnessMap (opens new window) 可以设置金属度贴图 也就是局部金属度
- 如果同时设置
.metalness
和.metalnessMap
那么两个值将会相乘 效果更明显
// 导入纹理
import logo from '@/assets/door/color.jpg'
// 导入金属贴图
import metalness from '@/assets/door/metalness.jpg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建金属贴图
const textureMetalness = textureLoader.load(metalness)
// 创建一个在网格模型中展示的几何体
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3)
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置纹理贴图
map: texture,
// 设置金属贴图
metalnessMap: textureMetalness,
// 设置金属度
metalness: 0.5, // 默认为0.5 最小值为0 最大值为1
})
# 开启两面可见(共有属性)
- .side (opens new window) 设置渲染面行为 默认是
THREE.FrontSide
只渲染正面 这是一个共有属性(所有材质都存在)THREE.FrontSide
正面。THREE.BackSide
背面THREE.DoubleSide
双面/两面。
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置两面可见
side: THREE.DoubleSide
})
# 开启透明度(共有属性)
通过材质的透明度属性.opacity (opens new window)可以设置材质的透明程度,.opacity
属性值的范围是0.0~1.0,0.0值表示完全透明, 1.0表示完全不透明,.opacity
默认值1.0。这是一个共有属性
- 当设置
.opacity
属性值的时候,需要设置材质属性transparent
值为true
- 在构造函数参数中设置
transparent
和.opacity
的属性值
const material = new THREE.MeshPhongMaterial({
color: 0x220000,
// transparent设置为true,开启透明,否则opacity不起作用
transparent: true,
// 设置材质透明度
opacity: 0.4,
})
通过访问材质对象属性形式设置transparent
和.opacity
的属性值
// transparent设置为true,开启透明,否则opacity不起作用
material.transparent = true;
// 设置材质透明度
material.opacity = 0.4;
# 设置法线贴图
.normalMap (opens new window) 设置法线贴图
- 用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。可以作为物体的凹凸效果的贴图
- 法线贴图可以让模型从精模到简模, 把一些顶点几何数据转化为贴图法线数据, 这样通过法线贴图来完善模型的细节, 从而减少模型的大小, 并且不影响过多的模型显示效果
- 法线贴图的颜色代表着反射(方向)的向量 从而规定光源的反射效果 实现光照物体的层次感
- 记得设置灯光
import logo from '@/assets/door/color.jpg'
// 导入法线贴图
import normal from '@/assets/door/normal.jpg'
// 设置一个统一的纹理加载器
const textureLoader = new THREE.TextureLoader()
// 创建纹理
const texture = textureLoader.load(logo)
// 创建法线贴图
const textureNormal = textureLoader.load(normal)
// 创建一个在网格模型中展示的几何体
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3)
// 设置该集合体的纹理材质
const cubeMaterial = new THREE.MeshBasicMaterial({
// 设置纹理贴图
map: texture,
// 导入法线贴图
normalMap: textureNormal,
})
# 放大法线贴图的影响
法线贴图对材质的影响程度。Vector2
类型, 默认值为(1,1)。
material.normalMap = doorNormalTexture
material.normalScale.set(2, 2)
// 或者直接赋值二维向量
material.normalScale = new THREE.Vector2(2, 2)
# 设置深度写入
- .depthWrite (opens new window)设贴图的深度行为 默认为
true
覆盖的 也就是两张贴图重叠在一起 默认最上层(离相机)的贴图 会被默认覆盖- 通常贴图的深度写入 需要先设置 .alphaMap (opens new window) 灰度/透明度纹理并且 开启纹理的.transparent (opens new window) 是否透明设置为
true
透明
- 通常贴图的深度写入 需要先设置 .alphaMap (opens new window) 灰度/透明度纹理并且 开启纹理的.transparent (opens new window) 是否透明设置为
// 创建深度写入点材质
const pmaterial = new THREE.PointsMaterial({
color: '#ff3040',
size: 0.2,
transparent: true, // 开启透明度
map: texture, // 设置贴图
alphaMap: texture, // 设置透明贴图
depthWrite: false, // 关闭深度写入(防止点被遮挡)
})
# 设置叠加混合模式
- .blending (opens new window) 可以设置贴图的叠加混合模式 当两张贴图叠加在一起 可以设置其混合模式
THREE.NoBlending
不混合THREE.NormalBlending
正常混合(默认值)- z-buffer值较大的像素将会遮挡z-buffer值较小的像素,没有纹理融合效果,设置纹理透明度无效。
THREE.AdditiveBlending
相加混合- 此混合模式只是将一个图层的像素值添加到另一个图层。如果值大于1(在RGB的情况下),则显示白色。线性减淡颜色值。由于它总是产生与输入相同或更浅的颜色,因此它也被称为“加亮”。
THREE.SubtractiveBlending
相减混合- 此混合模式将一个图层的像素值减去另一个图层像素值。如果为负值,则显示黑色。
THREE.MultiplyBlending
相乘混合- 颜色混合,源图像RGB分量与目标图像RGB分量的相乘。
THREE.CustomBlending
自定义混合
- 通常需要设置.depthWrite (opens new window)设贴图的深度行为为
false
// 创建点材质
const pmaterial = new THREE.PointsMaterial({
color: '#ff3040',
size: 0.2,
transparent: true, // 开启透明度
map: texture, // 设置贴图
alphaMap: texture, // 设置透明贴图
depthWrite: false, // 关闭深度写入(防止点被遮挡)
blending: THREE.NormalBlending, // 设置混合模式 (AdditiveBlending为叠加)
})
# 设置顶点着色(默认颜色)
- .vertexColors (opens new window) 可以设置材质/材质的顶点颜色 (默认为
false
) 顶点颜色是指每个顶点都有一个颜色值(默认色值) 默认顶点颜色的优先级高于材质颜色(通过.color设置的颜色)- 顶点颜色的值是一个0-1的值 0表示黑色 1表示白色
- 部分材质(例如 点材质(PointsMaterial) (opens new window) 修改材质的
.color
时候 就需要设置其顶点着色为true
const starMaterial = new THREE.PointsMaterial({
size: 0.3,
map: material,
alphaMap: material,
transparent: true, // 开启透明度
depthWrite: false, // 关闭深度写入(防止点被遮挡),
vertexColors: true, // 开启顶点颜色 (默认为false) 顶点颜色是指每个顶点都有一个颜色值(默认色值) 顶点颜色的优先级高于材质颜色(通过.color设置的颜色) 顶点颜色的值是一个0-1的值 0表示黑色 1表示白色
})
# 设置环境贴图的强度
MeshStandardMaterial (opens new window)PBR材质 和 MeshPhysicalMaterial (opens new window) 物理网格材质(高阶PBR) 提供了环境贴图强度设置 .envMapIntensity (opens new window),通过乘以环境贴图的颜色来缩放环境贴图的效果。数值越大环境贴图的强度越大
envMapIntensity
的默认值是1
const material = new THREE.MeshStandardMaterial({
color: '#ffffff',
metalness: 1.0,
roughness: 0.0,
envMapIntensity: 1.0,
})
# 环境贴图 (cube几何贴图加载器)
- CubeTextureLoader (opens new window) cube几何贴图加载器 类似于几何体的加载 对应的是几个体的每个面
- 通过几何贴图加载器 加载环境贴图 贴在指定的几何体面上 再设置物体的粗糙度和金属度(让其反光有镜面的效果) 就可以实现环境贴图的效果
# 环境贴图的示例
- 比如说BoxGeometry (opens new window)或者SphereGeometry (opens new window) 立方体有六个面 或者 球体(也是六面个和立方体一样) 使用几何贴图加载器分别对应其每个面的贴图
- 物体的.roughness (opens new window) 粗糙度设置低一些(0.1左右)光滑一些 默认粗糙度(默认为1) 是没有反光效果的
- 物体的.metalness (opens new window) 金属度要搞一些(0.7左右)镜面效果强一些 默认金属度(默认为0)是没有镜面效果的
- 注意: 一定要加上灯光 推荐DirectionalLight 平行光 (opens new window) 和 AmbientLight 环境光 (opens new window) 一起用
- 代码展示案例
// 创建场景
const scene = new THREE.Scene()
// 设置一个cube加载器
const envMapLoader = new THREE.CubeTextureLoader()
// 加载环境贴图
const envMapT = envMapLoader.load([
'px.png',
'nx.png',
'py.png',
'ny.png',
'pz.png',
'nz.png',
])
// 声明一个球体
const sphere = new THREE.SphereGeometry(1, 20, 20)
// 声明一个标准材质
const mmaterial = new THREE.MeshStandardMaterial({
// 设置金属度
metalness: 0.7,
// 设置光滑度
roughness: 0.1,
// 设置环境贴图
envMap: envMapT,
})
// 创建网格模型
const mesh = new THREE.Mesh(sphere, mmaterial)
// 添加到场景
scene.add(mesh)
// 环境光
const light = new THREE.AmbientLight(0xffffff, 0.5) // soft white light
scene.add(light)
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight.position.set(0, 0, 10)
scene.add(directionalLight)
- 环境贴图的展示效果
# 环境贴图使用场景
通常环境贴图使用的场景分为两种
- 第一种是作用在材质上的比如MeshStandardMaterial (opens new window) PBR材质中的.envMap (opens new window)设置环境贴图, 比如物体通过粗糙度金属度在表面反射的效果, 如果金属度粗糙度允许, 环境贴图会存在一些光照效果(类似于反光的效果), 通常环境光可以配合环境贴图, 实现一个反射的光照效果
- .envMapIntensity (opens new window)环境贴图效果的强度, 默认是
1
, 用来表现出环境贴图对物体的影响(类似以光照的强度)
- .envMapIntensity (opens new window)环境贴图效果的强度, 默认是
import { getAssetsFile } from '@/utils/getAssetsFile'
// 设置一个环境贴图加载器
const envMapLoader = new THREE.CubeTextureLoader()
// 这里是three.js的环境贴图加载器 引用多张图片进行加载
const envMap = envMapLoader.loadAsync([
'px.png',
'nx.png',
'py.png',
'ny.png',
'pz.png',
'nz.png',
] as any)
// 声明一个标准材质
const mmaterial = new THREE.MeshStandardMaterial({
// 设置金属度
metalness: 0.7,
// 设置光滑度
roughness: 0.1,
// 设置环境贴图
envMap,
})
- 第二种是作为Scene (opens new window)场景的.background (opens new window)背景图, 可以配合物体的环境贴图, 在场景中模拟外界效果
import { getAssetsFile } from '@/utils/getAssetsFile'
// 设置一个环境贴图加载器
const envMapLoader = new THREE.CubeTextureLoader()
// 这里是three.js的环境贴图加载器 引用多张图片进行加载
const envMap = envMapLoader.loadAsync([
'px.png',
'nx.png',
'py.png',
'ny.png',
'pz.png',
'nz.png',
] as any)
// 声明一个标准材质
const mmaterial = new THREE.MeshStandardMaterial({
// 设置金属度
metalness: 0.7,
// 设置光滑度
roughness: 0.1,
// 给材质添加环境贴图
envMap,
})
// 给场景添加背景
scene.background = envMap
# HDR贴图的应用
得益于HDR出色的色彩效果 虽HDR的资源消耗巨大 (vscode安装HDR插件后 预览一次就会崩溃) 但是也可以在webgl中使用
HDR
可以作为Sence
背景 也可以作为物体的环境贴图HDR
加载需要使用three.js的RGBELoader (opens new window)HDR
文件加载器控件 (控件需要单独导入)HDR
加载建议使用.loadAsync (opens new window) 异步加载HDR
一张图往往10m以上 适合异步加载- 通过
RGBELoader
加载的HDR
需要设置 .mapping (opens new window)贴图的环绕方式 设置为EquirectangularReflectionMapping (opens new window)等距圆柱投影的环境贴图也被叫做经纬线映射贴图
包裹在一起形成Scene
的背景效果
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'
// 1. 创建three.js场景
const scene = new THREE.Scene()
// 添加HDR贴图
const HDRloader = new RGBELoader()
// 异步加载HDR贴图
HDRloader.loadAsync('hdr/002.hdr').then((HDRtexture) => {
// 设置HDR贴图的贴图环绕方式
HDRtexture.mapping = THREE.EquirectangularReflectionMapping
// 给场景设置HDR背景图
scene.background = HDRtexture
// 给场景内所有的物体添加默认的环境贴图 (如果物体不单独设置环境贴图 默认使用这个环境贴图)
scene.environment = HDRtexture
})
# 纹理贴图反转
.flipY (opens new window) GPU中纹理贴图Y轴反转, 默认为true
, 纹理贴图会在Y轴上翻转, 设置为false
纹理贴图在Y轴不会翻转
- 通常纹理贴图导入后, 该属性默认是
true
, 也就是翻转效果 - 是否要取消Y轴翻转, 取决于模型文件导出时候的设置
// 导入色彩贴图
const map = this.textureLoader.load(getAssetsFile('iphone/basecolor.png'))
// 取消贴图的反转
map.flipY = false
# 为什么需要纹理贴图反转
- 如果glft/glb模型文件不包含纹理, 通过three.js提供的
TextureLoader
纹理加载器从外部手动添加纹理的时候, 就需要取消纹理反转
// 导入色彩贴图
const map = this.textureLoader.load(getAssetsFile('iphone/basecolor.png'))
// 取消贴图的反转
map.flipY = false
- 如果glft/glb的模型文件自带纹理, 通过
GLTFLoader
加载后, 就可以正确地自动配置从glft/glb文件中引用的纹理, 不需要取消纹理反转
// 导入gltf加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
// 异步获得加载的模型
const gltf = await loader.loadAsync(car)
# 纹理贴图二次修改和ts类型问题
会有这样的一个场景, 颜色贴图envMap
可以进行二次修改, 这里会出现一个问题, 在mesh
模型中是存在material
这个属性, 但是在ts中可能因为某些原因不存在material
类型, 所以我们需要通过断言进行二次修改
- 在ts中(@types/three版本0.149.0)直接引用
Mesh
网格中的material
材质会出现类型不存在报错, 但本身其实是Mesh
类型, 可以通过断言给其设置为Mesh<THREE.BufferGeometry, 使用材质的类型>
// 先设置一次设置PBR材质
iphoneMap.material = new THREE.MeshStandardMaterial({
// 设置颜色贴图
map,
})
// 给Mesh网格物体 设置Mesh类型泛型为BufferGeometry和MeshStandardMaterial(依据使用材质的类型)
const iphoneMapMaterial = this.iphoneMap.material as THREE.Mesh<THREE.BufferGeometry, THREE.MeshStandardMaterial>
// 标记为需要更新
iphoneMapMaterial.needsUpdate = true
// 更新贴图
iphoneMapMaterial.map = map2
不建议通过二次声明的方式进行修改, 因为这样会造成物体材质的二次声明, 消耗性能并且页面会进行卡顿, 如下所示
// 先设置一次设置材质
iphoneMap.material = new THREE.MeshStandardMaterial({
// 设置环境贴图
map,
})
// 二次修改材质
iphoneMap.material = new THREE.MeshStandardMaterial({
map2,
})
# 贴图缓存更新
three.js里的很多对象都有一个.needsUpdate
属性, 纹理贴图也有该属性 (opens new window), 告诉renderer
这一帧我该更新缓存了, 这个属性经常会作用到切换不同的贴图时的缓存操作
- 每次
map
, 或者envMap
等各种贴图改变**真值true
**的时候都需要更新材质, 首次对材质进行贴图赋值的时候不需要 - 在ts中(@types/three版本0.149.0)
.getObjectByName()
获取到的模型是一个Object3D
格式的数据, 但本身其实是Mesh
类型, 可以通过断言给其设置为Mesh<THREE.BufferGeometry, 使用材质的类型>
// 查找模型, 给其设置Mesh类型泛型为BufferGeometry和MeshStandardMaterial(依据使用材质的类型)
const iphoneMap = this.iphone.getObjectByName('手机') as THREE.Mesh<THREE.BufferGeometry, THREE.MeshStandardMaterial>
// 先设置一次设置PBR材质
iphoneMap.material = new THREE.MeshStandardMaterial({
// 设置颜色贴图
map,
})
// 二次修改材质
const iphoneMapMaterial = this.iphoneMap.material
// 标记为需要更新
iphoneMapMaterial.needsUpdate = true
// 更新贴图
iphoneMapMaterial.map = map2
关于纹理贴图的缓存机制
材质在three.js中是通过THREE.Material
来描述的,其实材质并没有什么数据要传输,但是为什么还要搞一个.needsUpdate
呢,这里还要说一下shader这个东西,shader直译过来是着色器,提供了在gpu中编程处理顶点和像素的可能性,在绘画中有个shading的术语来表示绘画的明暗法,GPU中的shading也类似,通过程序计算光照的明暗来表现物体的材质,ok, 既然shader是一段跑在GPU上的程序,那么像所有程序一样都需要进行一次编译链接的操作, WebGL中是在运行时对shader程序进行编译的,这当然需要消耗时间,因此也是最好能够一次编译就运行到程序结束。所以three.js中就在material初始化的时候就编译链接了shader程序并且缓存了编译链接后得到的program对象。一般一个material是不需要再去重新编译整个shader了,材质的调整只需要修改shader的uniform参数就行了。但是如果是替换了整个材质,比如将原来phong的shader替换成了一个lambert的shader,就需要将material.needsUpdate
设置成true去重新做一次编译。
# 纹理贴图的编码
three.js中默认的编码格式是LinearEncoding
线性编码, 编码在以下内容使用
- 纹理贴图:
Texture
通过 .encoding (opens new window) 属性可以查看编码, 通过three.js创建的纹理贴图是线性编码 - 场景渲染器:
WebGLRenderer
通过 .outputEncoding (opens new window) 修改渲染编码,WebGLRenderer
场景渲染器默认是线性渲染
常用的两种编码格式sRGBEncoding
和LinearEncoding
, 两者在.encoding
对应的编码值
LinearEncoding
的编码值为 3000 , three.js默认的编码sRGBEncoding
的编码值为 3001, gltf/glb模型文件的编码其他编码格式在这里查看 (opens new window)
通过gltf/glb创建的模型自带的纹理贴图是sRGB编码
# gltf/glb的编码设置问题
gltf/glb模型的编码是sRGBEncoding
, 通过GLTFLoader (opens new window)加载器加载到three.js中, 会出现一些色差
这时候WebGLRenderer
场景渲染器默认的渲染是线性渲染(默认), 那么如果模型中包含一些自带的纹理贴图就会出现色差问题(自带贴图是sRGB编码), 需要 WebGLRenderer (opens new window) 通过 .outputEncoding (opens new window) 修改渲染编码为sRGB编码
renderer.outputEncoding = THREE.sRGBEncoding // 解决gltf/glb模型加载后出现色差问题
总的来说WebGLRenderer
场景渲染的.outputEncoding
编码渲染方式, 必须和纹理贴图的.encoding
编码方式一致, 否则会出现一些色差问题