全网最详Babylon.js教材(4)-材质与纹理的相濡以沫

Q:Babylon.js是什么?🤔️

Babylon.js 是一个强大的、开源的、基于 WebGLWebGPU3D引擎,用于在网页上创建和渲染 3D图形。它提供了一套丰富的 API和功能,包括物理引擎、粒子系统、骨骼动画、碰撞检测、光照和阴影等,可以帮助开发者快速创建复杂的 3D场景和交互。

Q:我为什么要写该系列的教材? 🤔️

因为公司业务的需要因而要在项目中使用到 Babylon.js,虽然官方的文档看起来覆盖面都挺全,且 playgroud(https://playground.babylonjs.com/) 上的案例也都比较多,但一些具体的 API 或者功能属性也都没有特别多详细的介绍,包括很多使用方式的很多坑都得自己去源码中或者论坛上(https://forum.babylonjs.com/)找。在将其琢磨完之后, 决定写一系列关于它的教材来帮助更多 babylon.js的使用者或者是期于学习 Web 3D的开发者。同时也是自己对其的一种巩固。

材质与纹理的相濡以沫

在上一篇《材质与光的交响曲》中说了很多与光相关的材质属性,大多都还是简单的颜色属性。在实际开发中,纹理用的可能会更多。因为有很多物体的细节和视觉复杂性可能是要依赖图片的,这时候就要用到纹理 Texture 了。

纹理 Texture

在 Babylon.js 中,提供了 Texture 类用于创建一个纹理对象,在创建时需要指定纹理的图像文件路径:

const texture = new BABYLON.Texture("textures/brick.jpg", scene);

然后将纹理对象分配给材质的某个属性,这里的某个属性其实就是上一篇提到的 diffuseTexturespecularTextureemissiveTexture 等,例如:

// 创建一个新的标准材质
const standardMat = new BABYLON.StandardMaterial("standardMat", scene);

standardMat.diffuseTexture = texture; // 将纹理分配给材质的漫反射纹理
standardMat.specularTexture = texture; // 将纹理分配给材质的高光反射纹理
standardMat.emissiveTexture = texture; // 将纹理分配给材质的自发光纹理
standardMat.ambientTexture = texture; // 将纹理分配给材质的环境光纹理

实际上 Texture类初始化的可选参数非常多:

/**
* @param url 定义要加载为纹理的图片的 URL 
* @param sceneOrEngine 定义纹理所属的场景或引擎
* @param noMipmapOrOptions 定义纹理是否需要 mip maps,或者创建纹理的所有选项
* @param invertY 定义在加载过程中是否需要在 y 轴上翻转纹理
* @param samplingMode 定义我们从纹理中获取时想要的采样模式(Texture.NEAREST_SAMPLINGMODE...)
* @param onLoad 定义当纹理加载完成时触发的回调
* @param onError 定义在加载会话中发生错误时触发的回调
* @param buffer 定义从缓冲区加载纹理的缓冲区,以防纹理是从缓冲区表示加载的
* @param deleteBuffer 定义我们是否应在加载后删除正在加载纹理的缓冲区
* @param format 定义我们试图加载的纹理的格式(Engine.TEXTUREFORMAT_RGBA...) 
* @param mimeType 定义一个可选的 mime 类型信息
* @param loaderOptions 要传递给加载器的选项
* @param creationFlags 创建纹理时使用的特定标志(例如,存储纹理的 Constants.TEXTURE_CREATIONFLAG_STORAGE)
* @param forcedExtension 定义用于选择正确加载器的扩展名
*/
constructor(
    url: Nullable<string>,
    sceneOrEngine?: Nullable<Scene | ThinEngine>,
    noMipmapOrOptions?: boolean | ITextureCreationOptions,
    invertY?: boolean,
    samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE,
    onLoad: Nullable<() => void> = null,
    onError: Nullable<(message?: string, exception?: any) => void> = null,
    buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null,
    deleteBuffer: boolean = false,
    format?: number,
    mimeType?: string,
    loaderOptions?: any,
    creationFlags?: number,
    forcedExtension?: string
) {}

前期咱还是只需要关注基本的使用,后面的一些参数作用慢慢再来说。

emissiveTexture 自发光纹理

老规矩,还是先从自发光开始看。

类型:BABYLON.Texture

描述:设置材质的自发光纹理,使物体表面看起来像是自己发光。可以用来创建复杂的发光效果。

如果物体设置了自发光纹理的材质,即使场景中没有任何的光源,物体也能显示出自发光的纹理。如下案例:

  • 创建一个带自发光纹理的材质

  • 创建一个小球并设置该材质

const standardMat = new BABYLON.StandardMaterial('standardMat', scene);

standardMat.emissiveTexture = new BABYLON.Texture(
  'https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/0b89446e47cf476c9cce332723bc1430~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTGluRGFpRGFpX-mcluWRhuWRhg==:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzYwMjk1NTEzNDYzOTEyIn0%3D&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1725943743&x-orig-sign=wgs7MHxWQsviDt0Q4oeVyBRx%2BMw%3D?x-oss-process=image%2Fformat%2Cwebp%2Fresize%2Cw_1024%2Climit_0',
  scene
);

const sphere0 = BABYLON.MeshBuilder.CreateSphere('sphere0', {}, scene);
sphere0.material = standardMat;

贴图图片如下:

a8e32d8b0d995b492648dd01d60fe4de.jpeg


案例效果如下:

ca524c54f162debb9a1ca2b2af3cfed5.jpeg


diffuseTexture 漫反射纹理

除了自发光纹理,其他的纹理,如漫反射纹理、环境光纹理,高光纹理,都需要依赖光源。

其中,diffuseTexture 是最常用的纹理类型,用于定义物体在直接光照下的外观。

例如如下案例中,我们场景如果不设置任何的光源的话,球还是会显示成黑色:

const standardMat = new BABYLON.StandardMaterial('standardMat', scene);

standardMat.diffuseTexture = new BABYLON.Texture(
  'https://cdn.nlark.com/yuque/0/2024/png/451257/1721553497235-c398596c-37fc-4a7d-a3db-28da4c684601.png?x-oss-process=image%2Fformat%2Cwebp%2Fresize%2Cw_1024%2Climit_0',
  scene
);

const sphere0 = BABYLON.MeshBuilder.CreateSphere('sphere0', {}, scene);
sphere0.material = standardMat;

8c4b7e93ecd90a69ab171a7a3c400444.jpeg

要解决此问题有几种方案?大家可以先想一想。

----- 手动分割线 -----

OK,通过上面的学习,不难猜到,有以下几种方案:

(一)材质设置自发光颜色 emissiveColor:

// 例如设置为白色
standardMat.emissiveColor = new BABYLON.Color3(1, 1, 1);

效果如下:

84458ce63e8cedc823445c9b608a8ceb.jpeg


(二)材质增加环境光颜色 ambientColor ,并设置场景环境颜色 ambientColor :

scene.ambientColor = new BABYLON.Color3(1, 1, 1);

standardMat.ambientColor = new BABYLON.Color3(1, 1, 1);

效果和第一种方式一样。

(三)环境中增加任意光源照射到物体上即可:

// 例如增加一个半球光源
const light = new BABYLON.HemisphericLight(
  'hemiLight',
  new BABYLON.Vector3(-1, 1, 0),
  scene
);

此时,仅增加光源,且让其能照射到物体上即可,可以不需要指定 diffuseColor,这是因为 standardMaterialdiffuseColor 默认就是白色,能反射所有颜色的光,这样就可以看到贴图效果了,只不过效果是对比前两种会有一个高光和阴影的效果。

8f8a598163bbf9e059291328dc89f913.jpeg


ambientTexture 环境光贴图

环境光贴图的效果和 diffuseTexture 很类似,不过两者在使用场景上有一些区别,具体来说:

  • ambientTexture用于模拟环境光对物体的影响,适合在有全局环境光的场景中使用。

  • diffuseTexture用于定义物体表面的基本颜色和细节,适合显示物体的颜色、图案和纹理。

specularTexture 高光贴图

还有一个比较有意思的是高光贴图,它可以指定材质的高光处显示哪个贴图。

假设我们场景有一个漫反射为红色的半球光,和一个没有指定材质的球体:

// 例如增加一个半球光源
const light = new BABYLON.HemisphericLight(
  'hemiLight',
  new BABYLON.Vector3(-1, 1, 0),
  scene
);

const sphere0 = BABYLON.MeshBuilder.CreateSphere('sphere0', {}, scene);

效果是这样的:

fbf85563bc942698d6de0c1437954e39.jpeg


可以看到高光部分默认是白色的。那么此时我们再来指定一下高光贴图:

const standardMat = new BABYLON.StandardMaterial('standardMat', scene);
standardMat.specularTexture = new BABYLON.Texture(
  'https://cdn.nlark.com/yuque/0/2024/png/451257/1721553497235-c398596c-37fc-4a7d-a3db-28da4c684601.png?x-oss-process=image%2Fformat%2Cwebp%2Fresize%2Cw_1024%2Climit_0',
  scene
);

sphere0.material = standardMat;

效果如下:

0987716fe407f6b00ce10676318ad1d0.jpeg


虽然显示的不多,但还是隐隐能看到高光处有贴图的样子。

hasAlpha 透明纹理

在实际开发中,我们还有一种场景是:贴图可能会有一些透明区域,这部分透明区域要能正常显示。

如下案例,我们创建一个面片(Plane)和一个球,让球在面片的后面,同时给面片添加一张带有透明背景的 png 漫反射贴图。(场景中还要创建一个光源,例如半球光源)

// 例如增加一个半球光源
const light = new BABYLON.HemisphericLight(
  'hemiLight',
  new BABYLON.Vector3(-1, 1, 0),
  scene
);
// 创建一个球并通过改变 z 坐标让其在面片后面
const sphere0 = BABYLON.MeshBuilder.CreateSphere('sphere0', {}, scene);
sphere0.position.z = 2;

// 创建一个面片
const plane = BABYLON.MeshBuilder.CreatePlane('plane', {}, scene);
// 创建一个标准材质并设置漫反射贴图
const standardMat = new BABYLON.StandardMaterial('standardMat', scene);
standardMat.diffuseTexture = new BABYLON.Texture(
  'https://doc.babylonjs.com/img/how_to/Materials/dog.png',
  scene
);
plane.material = standardMat;

效果为:

e43db490f5f0227d3892cb6f97cad3d1.jpeg


这样的效果不是我们期望的,因为小狗图片的背景是透明的,我们不期望它会挡住后面的球。

由于 Babylon.js 并不知道你设置的这张贴图需不需要有透明效果,此时需要手动指定纹理的 hasAlpha 属性为 true才能生效:

standardMat.diffuseTexture.hasAlpha = true;

现在效果就正常了:

078007735a48dbc9f2f895e6ea72b733.jpeg


在线预览地址:https://playground.babylonjs.com/#YDO1F#1327

backFaceCulling 背面剔除

还有一个概念是背面剔除,它在实时渲染中非常常见。指的是通过不渲染那些背对着相机的多边形来减少需要处理的多边形数量,从而提高渲染效率。

例如,一个立方体,六个面的贴图都是狗狗的图片,材质的 backFaceCulling默认是为 true的,也就是开启了背面剔除,效果如下:

01a0dd76b0a9a1f2773456c0b9f52b59.gif


可以注意这个立方体,是看不到它的背面的,即使狗狗图片有些部分是透明的也不可以。

那么如果我们将 backFaceCulling设置为 false的话,效果就是如下了:

b96453a7791a9b17110766061928b050.gif


代码如下:

// 创建灯
var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);

// 创建材质
var mat = new BABYLON.StandardMaterial("dog", scene);
mat.diffuseTexture = new BABYLON.Texture("https://upload.wikimedia.org/wikipedia/commons/8/87/Alaskan_Malamute%2BBlank.png", scene);
mat.diffuseTexture.hasAlpha = true;

// 设置背面剔除为 false
mat.backFaceCulling = false;
var box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
box.material = mat;

在线预览地址:https://playground.babylonjs.com/#YDO1F#1328

cullBackFaces 指定剔除背面或正面

有剔除背面的话,那有没有剔除正面呢?以 Babylon.js 的 niao性(呸,强大)还真有。

材质上的 cullBackFaces属性默认是 true,表示剔除背面,如果你想要剔除正面,只显示背面的话,可以设置它为 false

// 创建灯
var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);

// 创建材质
var mat = new BABYLON.StandardMaterial("dog", scene);
mat.diffuseTexture = new BABYLON.Texture("https://upload.wikimedia.org/wikipedia/commons/8/87/Alaskan_Malamute%2BBlank.png", scene);
mat.diffuseTexture.hasAlpha = true;

// 设置只剔除正面
mat.cullBackFaces = false;
var box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
box.material = mat;

效果如下:

7232c8329b132590c506b912aa842176.gif


(只有背面的狗狗是倒着的,这里可以看出狗狗都是只显示背面的)

注:这里有一点要注意的, cullBackFaces属性只有在 backFaceCullingtrue的时候才生效。

在线预览地址:https://playground.babylonjs.com/#YDO1F#1329

opacityTexture

另外我在官网还发现了一个比较有意思的属性,opacityTexture,不透明度贴图?

咳咳,单从字面上翻译好像还真不知道它是干啥的,简单的摸索后,发现原来它是这么用的。

假设你有一个需求是:一个红色的面片,要求四周都是红色,越靠近中间,越透明。效果类似于这样:

a43d37c9ea7aa0fbf00d2ba78c681d3e.jpeg


(由于场景的背景是暗紫色的,所以面片中间看着是暗紫色,实际上中间是透明的)

运用上面学习到的知识,我们可能很快能给出一个解决方案:

  • 让美工小姐姐切一个方方正正的 png,四周红色,中间越来越透明

  • 把这张图设置到材质的 diffuseTexture上,并设置材质的 hasAlphatrue

咋一看,没啥问题,确实也能实现这个需求。但此时,产品要求的是:这个红色的面片能一直变换着颜色,一会蓝,一会绿,一会紫。这时候你咋搞,总不可能要美工小姐姐把所有颜色的图都做一遍,然后你跟着换吧?

诶~这时候你发现你需要一张能表示透明度的图片:面片具体的颜色由其它属性控制(例如 diffuseColor或者 diffuseTexture),而透明度属性并不是整体一起控制(整个面片全透明或者半透明或者不透明),而是可以非常精细的控制到每个像素。

这就是 opacityTexture的作用了,你可以让美工小姐姐给你切一张类似下面这样的图:

f17ea8e43fbc3e99eab657aafa75fab8.jpeg


用这张图片上的透明度值来表示材质上的透明度值。例如上面的这张图,边缘部分的都是不透明的,然后越靠近中间越透明。所以对应到材质的透明度上,越靠近中间,透明度就越大。

这时候,让我们来重新实现一下产品的需求,代码改为:

// 随便创建一个灯光,并设置漫反射颜色为白色
var light = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(-1, 1, 0), scene);
light.diffuse = new BABYLON.Color3(1, 1, 1);

var mat0 = new BABYLON.StandardMaterial("mat0", scene);
// 设置材质的漫反射颜色为红色
mat0.diffuseColor = new BABYLON.Color3(1, 0, 0);
// 指定材质的透明度由贴图 'https://i.imgur.com/xhXIyKG.png' 来决定
mat0.opacityTexture = new BABYLON.Texture("https://i.imgur.com/xhXIyKG.png", scene);

// 将材质设置给一个面片
var plane = BABYLON.MeshBuilder.CreatePlane("plane", {}, scene);
plane.material = mat0;

// 为了更好的看到效果,我们在面片后面再创建一个球
var sphere1 = BABYLON.MeshBuilder.CreateSphere("sphere1", {}, scene);
sphere1.material = mat1;
sphere1.position.z = 1.5;

效果为:

de495e422d597dfd0931c5115c9e9e03.gif


甚至你可以结合前面狗狗的贴图达到这样的效果:

d19789e79f8b4fa57afc8d9c7f6eae17.gif


狗狗图片的透明度不再受它自身的影响,而是受 opacityTexture的影响,效果就会像上面一样,中间是透明的,四边不透明。

代码为:

var mat0 = new BABYLON.StandardMaterial("mat0", scene);
// mat0.diffuseColor = new BABYLON.Color3(1, 0, 0);
mat0.opacityTexture = new BABYLON.Texture("https://i.imgur.com/xhXIyKG.png", scene);
// 设置漫反射贴图
mat0.diffuseTexture = new BABYLON.Texture("https://doc.babylonjs.com/img/how_to/Materials/dog.png", scene);
mat0.diffuseTexture.hasAlpha = true;

甚至我还有一个大胆的想法:我可以把图片上的狗狗的模样单独抠出来吗?做成一个“剪纸”。

你别说,还真可以:

288ce9be7be1cabd879943e431f1ee7d.gif


只需要把狗狗的这张图片作为 opacityTexture即可:

var mat0 = new BABYLON.StandardMaterial("mat0", scene);

mat0.diffuseColor = new BABYLON.Color3(1, 0, 0);
mat0.opacityTexture = new BABYLON.Texture("https://doc.babylonjs.com/img/how_to/Materials/dog.png", scene);

嘿嘿,这也不难理解,狗狗的图片,“狗的轮廓”是不透明的,而边上是透明的,所以应用到 opacityTexture上就是我们想要的效果了。[偷笑~]

以上三个案例的在线预览地址:https://playground.babylonjs.com/#20OAV9#14616

其他材质

问:上面的材质被称为“标准材质”,那 Babylon.js 中除了标准材质,难道还有其他材质?比如“非标准材质”?

哈哈,非标准没有,但是 BABYLON.js中确实提供了多种材质类型,以支持不同的视觉效果和性能需求。除了

StandardMaterial之外,还包括:

  • PBRMaterial:基于物理的渲染(PBR)材质,提供更真实的视觉效果,模拟真实世界的光照和材料属性。

  • BackgroundMaterial:用于创建全景或天空盒背景的材质。

  • MultiMaterial:允许将多种材质应用到一个网格的不同部分。

  • ShaderMaterial:使用自定义着色器代码创建的材质,为高级用户提供灵活性。

  • NodeMaterial:一个可视化编辑器创建的材质,允许通过拖放节点来构建复杂的着色器。

  • SkyMaterial:用于创建动态天空效果的材质,包括云层和日落效果。

  • WaterMaterial:用于创建水面效果的材质,包括波纹、反射和折射效果。

  • FireMaterial:用于创建火焰效果的材质。

  • FurMaterial:用于创建毛皮效果的材质,可以模拟动物毛皮或草地。

  • LavaMaterial:用于创建岩浆或熔岩效果的材质。

  • TerrainMaterial:专为地形创建的材质,支持混合多种纹理以模拟不同的地面类型。

  • GridMaterial:一个简单的、用于调试目的的网格材质,显示为网格线。

  • TriPlanarMaterial:使用三个平面的纹理映射来创建材质,适用于复杂形状或地形。


20d384d8d90f39eb857dd4098f72d1e5.jpeg

这部分内容我们会在后续再给大家详细的介绍,感兴趣的伙伴也可以先移步官网地址提前学习:https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary

后语

知识无价,支持原创!这篇文章主要是向大家介绍了材质与纹理的关系,然后学习了常见的纹理贴图属性,最后再介绍了透明纹理、背面剔除等功能的用法。至此,标准材质(StandardMaterial)的一些基本用法就和大家介绍的差不多了,实际上,这两篇介绍的内容还只是 Babylon.js 材质相关的冰山一角,还有更多的“宝藏”内容等着我们去挖掘学习,所以请继续保持期待和热情吧~

喜欢霖呆呆的小伙伴还希望可以关注霖呆呆的公众号 LinDaiDai

我会不定时的更新一些前端方面的知识内容以及自己的原创文章🎉。

你的鼓励就是我持续创作的主要动力 😊。

其它相关文章推荐:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值