> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/materials.md).

# 材质

了解如何为具有基本几何形状的实体添加材质和纹理。

## 材质

可以通过添加一个 `材质` 组件。此组件包含多个字段，可让你配置材质属性、添加纹理等。

*glTF* 模型包含其自己的材质，这些材质会与模型一起被隐式导入到场景中。要修改或覆盖这些材质，请使用 `GltfNodeModifiers` 组件遍历所有玩家。参见 [修改 glTF 材质](#modify-gltf-materials) 了解更多详情。

在导入带有自身材质的 3D 模型时，请记住 Decentraland 引擎并不支持所有着色器。仅支持标准材质和 PBR（基于物理的渲染）材质。参见 [外部 3D 模型注意事项](https://github.com/decentraland/docs-creator/blob/main/creator/3d-modeling/materials/README.md) 了解更多详情。

支持的材质有不同类型：

* PBR（基于物理的渲染）：Decentraland 中最常见的材质类型。它支持纯色或纹理，以及金属度、自发光、透明度等不同属性。阅读更多关于 [PBR](https://en.wikipedia.org/wiki/Physically_based_rendering).
* 基础材质：它们不会响应光照和阴影，因此非常适合显示公告板图像。

## 在 Creator Hub 中使用 Scene Editor

给实体添加材质的最简单方法是使用场景编辑器。你可以向你的实体添加一个 **材质** 组件，然后在场景编辑器 UI 上配置所有可用字段。参见 [添加组件](/creator/content-creator-zh/chang-jing-bian-ji-qi/gou-jian/components.md#add-components).

## 添加材质

下面的示例创建了一个 PBR 材质，并将其部分字段设置为红色和金属属性。此材质被添加到一个同时具有盒子形状的实体，因此它会使用此材质为盒子着色。

```ts
//创建实体并分配形状
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
	position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity)

//创建材质并配置其字段
Material.setPbrMaterial(meshEntity, {
	albedoColor: Color4.Red(),
	metallic: 0.8,
	roughness: 0.1,
})
```

要更改已经具有一个 `材质` 组件的实体的材质，请运行 `Material.setPbrMaterial()` 或其他任一辅助函数，它就会覆盖原始材质。无需移除原始的 `材质` ，也无需使用高级语法。

```ts
//创建实体并分配形状
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
	position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity)

//创建材质并配置其字段
Material.setPbrMaterial(meshEntity, {
	albedoColor: Color4.Red(),
})

//使用新的材质组件覆盖
Material.setPbrMaterial(meshEntity, {
	albedoColor: Color4.Blue(),
})
```

{% hint style="warning" %}
**📔 注意**： `材质` 组件必须通过以下方式导入：

> `import { Material } from "@dcl/sdk/ecs"`

参见 [导入](/creator/content-creator-zh/chang-jing-sdk7/ru-men/coding-scenes.md#imports) 了解如何轻松处理这些。
{% endhint %}

## 材质颜色

给材质设置纯色。在 PBR 材质中，你设置 `albedoColor` 字段。Albedo 颜色会响应光照，并且可以包含阴影。

颜色值的类型是 `Color4`，由 *r*, *g* 和 *b* 个值组成（红、绿和蓝）。这三个值都取 0 到 1 之间的值。通过为它们设置不同的值，你可以组合出任何可见颜色。黑色时，将三个值都设为 0。白色时，全部设为 1。

{% hint style="warning" %}
**📔 注意**：如果你在 `albedoColor` 中将任何颜色设为高于 *1*的值，它将显示为 *发光*，且值越高，强度越强。例如， `{r: 15, g: 0, b: 0}` 会产生非常明亮的红色辉光。
{% endhint %}

参见 [颜色类型](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/color-types.md) 了解如何设置颜色的更多详情。

你还可以在 PBR 材质中编辑以下字段，以微调其颜色的感知效果：

* *emissiveColor*：材质发出的颜色。
* *reflectivityColor*：也称为 *镜面颜色* ，在其他命名法中。

要创建一个不受环境光照和阴影影响的纯色材质，请创建基础材质而不是 PBR 材质。

```ts
Material.setBasicMaterial(myEntity, {
	diffuseColor: Color4.Black(),
})
```

## 使用纹理

通过设置 `纹理` 参数，将图像文件设为材质上的纹理。

```ts
//创建实体并分配形状
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
	position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity)

//创建材质并配置其字段
Material.setPbrMaterial(meshEntity, {
	texture: Material.Texture.Common({
		src: 'assets/materials/wood.png',
	}),
})
```

在上面的示例中，材质的图像位于一个 `assets/materials` 文件夹中，该文件夹位于场景项目文件夹的根级别。

{% hint style="info" %}
**💡 提示**中：我们建议将纹理图像文件保存在 `/assets` 文件夹里。
{% endhint %}

在创建纹理时，你还可以传递其他参数：

* `filterMode`：决定纹理中的像素在渲染时如何被拉伸或压缩。此项取值来自 `TextureFilterMode` 枚举。参见 [纹理缩放](#texture-scaling).
* `wrapMode`：决定纹理如何平铺到对象上。此项取值来自 `TextureWrapMode` 枚举。参见 [纹理平铺](#texture-wrapping).

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'assets/materials/wood.png',
		filterMode: TextureFilterMode.TFM_BILINEAR,
		wrapMode: TextureWrapMode.TWM_CLAMP,
	}),
})
```

要创建一个不受环境光照和阴影影响的纹理，请创建基础材质而不是 PBR 材质。

```ts
Material.setBasicMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'assets/materials/wood.png',
	}),
})
```

### 来自外部 URL 的纹理

你可以将材质的纹理指向一个外部 URL，而不是场景项目中的内部路径。

```ts
Material.setBasicMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'https://wearable-api.decentraland.org/v2/collections/community_contest/wearables/cw_tuxedo_tshirt_upper_body/thumbnail',
	}),
})
```

URL 必须以 `https`, `http` 开头。URL 不受支持。

### 纹理平铺

你可以设置纹理与表面的对齐方式。默认情况下，纹理会被拉伸以一次覆盖整个表面，但你可以对其进行缩放和偏移。

以下字段适用于所有纹理：

* `offset`：移动纹理以改变其对齐方式。该值是一个 Vector2，两个轴都从 0 到 1，其中 1 表示纹理的完整宽度或高度。
* `tiling`：缩放纹理。默认值是 Vector 2 `[1, 1]`，这会让图像重复一次并覆盖整个表面。
* `TextureWrapMode`：决定如果图像平铺不足以覆盖整个表面时会发生什么。此属性的取值来自 `TextureWrapMode` 枚举，它允许以下值：
  * `TextureWrapMode.TWM_CLAMP`：纹理只会以指定大小显示一次。网格表面的其余部分保持透明。 `tiling` 将被忽略。
  * `TextureWrapMode.TWM_REPEAT`：纹理会按指定大小，在网格中尽可能多次重复。
  * `TextureWrapMode.TWM_MIRROR`：与平铺类似，纹理会按可容纳的次数重复，但这些重复的方向会镜像。

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'assets/materials/wood.png',
		wrapMode: TextureWrapMode.TWM_REPEAT,
		offset: Vector2.create(0, 0.2),
		tiling: Vector2.create(1, 1),
	}),
})
```

{% hint style="warning" %}
**📔 注意**： `offset` 和 `tiling` 属性仅在 DCL 2.0 桌面客户端中受支持。
{% endhint %}

使用此功能可以用平铺图案覆盖大片表面。例如，重复以下图像：

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-e0324d6080a0e815b172eafd56b5ece426ca68ca%2Ftiles.png?alt=media)

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'assets/materials/wood.png',
		wrapMode: TextureWrapMode.TWM_REPEAT,
		tiling: Vector2.create(8, 8),
	}),
})
```

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-6e66e1936998029bf4cd65f497509ae21896809a%2Ftiles-in-scene.png?alt=media)

在下面的示例中，纹理使用了一个 *镜像* 平铺模式，并且纹理的每次重复只占表面的 1/4。这意味着我们会看到 4 张图像副本，它们在两个轴上彼此镜像。

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/atlas.png',
		wrapMode: TextureWrapMode.TWM_MIRROR,
		tiling: Vector2.create(0.25, 0.25),
	}),
})
```

### 纹理补间

通过使用一个 `补间` 组件，让纹理平滑滑动，并用 `TextureMove` 模式。tween 会逐渐更改 `offset` 或 `tiling` 纹理属性值，在一段时间内以平滑且优化的方式进行。

{% hint style="warning" %}
**📔 注意**：Texture Tween 是仅在 DCL 2.0 桌面客户端中受支持的功能。
{% endhint %}

使用 `补间` 组件，并使用 `setTextureMove` 函数在两个位置之间移动纹理。

```ts
Tween.setTextureMove(myEntity, Vector2.create(0, 0), Vector2.create(1, 0), 2000)
```

纹理补间接受以下信息：

* `实体`：要移动其纹理的实体
* `start`：表示起始位置的 Vector2
* `end`：表示结束位置的 Vector2
* `持续时间`：在两个位置之间移动需要多少毫秒

另外还有一个可选参数：

* `movementType`：定义移动是否会在 `offset` 或 `tiling` 字段上。默认使用 `offset`.
* `easingFunction`：随时间变化速率的曲线，默认值是 `EasingFunction.EF_LINEAR`。其他值会使变化以不同速度加速和/或减速。

```ts
const myEntity = engine.addEntity()

MeshRenderer.setPlane(myEntity)

Transform.create(myEntity, {
	position: Vector3.create(4, 1, 4),
})

Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/water.png',
		wrapMode: TextureWrapMode.TWM_REPEAT,
	}),
})

Tween.setTextureMove(myEntity, Vector2.create(0, 0), Vector2.create(0, 1), 1000)
```

上面的示例运行一个持续 1 秒的 tween，并且只移动纹理一次。要实现持续移动，例如模拟瀑布的下落，你需要使用 `setTextureMoveContinuous`.

```ts
const myEntity = engine.addEntity()

MeshRenderer.setPlane(myEntity)

Transform.create(myEntity, {
	position: Vector3.create(4, 1, 4),
})

Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/water.png',
		wrapMode: TextureWrapMode.TWM_REPEAT,
	}),
})

Tween.setTextureMoveContinuous(myEntity, Vector2.create(0, 1), 1)
```

上面的示例使用 `setTextureMoveContinuous`，方向为 `(0, 1)`，速度为每秒 1 个单位。

连续纹理补间接受以下信息：

* `实体`：要移动其纹理的实体
* `方向`：表示移动的 Vector2
* `速度`：实体每秒将移动多少单位

还有这些其他可选参数可用：

* `movementType`：定义移动作用于偏移字段还是平铺字段。默认使用偏移。
* `持续时间`：持续移动多少毫秒。到这段时间后，移动将停止。

**复杂的 tween 序列**

你还可以让纹理移动遵循一个复杂序列，步骤数量不限。使用 `sequence` 字段来列出任意数量的 tween，它们会在 `补间` 组件。

```ts
//(...)
Tween.setTextureMove(myEntity, Vector2.create(0, 0), Vector2.create(0, 1), 1000)

TweenSequence.create(myEntity, {
	sequence: [
		{
			mode: Tween.Mode.TextureMove({
				start: Vector2.create(0, 1),
				end: Vector2.create(1, 1),
			}),
			duration: 1000,
			easingFunction: EasingFunction.EF_LINEAR,
		},
		{
			mode: Tween.Mode.TextureMove({
				start: Vector2.create(1, 1),
				end: Vector2.create(1, 0),
			}),
			duration: 1000,
			easingFunction: EasingFunction.EF_LINEAR,
		},
		{
			mode: Tween.Mode.TextureMove({
				start: Vector2.create(1, 0),
				end: Vector2.create(0, 0),
			}),
			duration: 1000,
			easingFunction: EasingFunction.EF_LINEAR,
		},
	],
	loop: TweenLoop.TL_RESTART,
})
```

请注意，在 TweenSequence 中定义补间时，你需要使用更详细的 `Tween.Mode.TextureMove` 来定义该 tween。

### 多层纹理

你可以使用多个图像文件作为图层来组合出更逼真的纹理，例如包括一个 `bumpTexture` 和一个 `emissiveTexture`.

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/wood.png',
	}),
	bumpTexture: Material.Texture.Common({
		src: 'materials/woodBump.png',
	}),
	emissiveTexture: Material.Texture.Common({
		src: 'materials/glow.png',
	}),
})
```

该 `bumpTexture` 可以通过修改表面法线在每个像素上的表现方式，来模拟表面的凹凸和皱褶。

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-b8e239e871864457936864ad2e8021a22338b8ad%2Fwood-bump.png?alt=media)

该 `emissiveTexture` 可以增强材质某些部分的发光效果，以实现非常有趣的效果。

**设置 UV**

另一种改变纹理缩放或对齐方式的办法，是配置 *uv* 属性，位于 [MeshRenderer 组件](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/shape-components.md).

你设置 *u* 和 *v* 坐标在纹理的二维图像上与形状的顶点对应。实体拥有的顶点越多，需要在纹理上定义的 *uv* 坐标就越多，例如一个平面需要有 8 个 *uv* 点被定义，每一面各 4 个。

```ts
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
	position: Vector3.create(4, 1, 4),
})
MeshRenderer.setPlane(
	meshEntity,
	[
		0, 0.75,

		0.25, 0.75,

		0.25, 1,

		0, 1,

		0, 0.75,

		0.25, 0.75,

		0.25, 1,

		0, 1,
	]
)

Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/wood.png',
		wrapMode: TextureWrapMode.TWM_REPEAT,
	}),
})
```

下面的示例包含一个简化 UV 设置的函数。 `setUVs` 这里定义的函数接收行数和列数作为参数，并设置 UV，使纹理图像重复指定的次数。

```ts
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
	position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity, setUVs(3, 3))

Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/atlas.png',
		wrapMode: TextureWrapMode.TWM_REPEAT,
	}),
})

function setUVs(rows: number, cols: number) {
	return [
		// 未旋转平面的北侧
		0, //左下角
		0,

		cols, //右下角
		0,

		cols, //右上角
		rows,

		0, //左上角
		rows,

		// 未旋转平面的南侧
		cols, // 右下角
		0,

		0, // 左下角
		0,

		0, // 左上角
		rows,

		cols, // 右上角
		rows,
	]
}
```

为一个 `盒子` 网格形状设置 UV 时，适用相同的结构。立方体的 6 个面中的每一个都使用 4 对坐标，每个角各一对。这 48 个值都作为单个数组列出。

{% hint style="warning" %}
**📔 注意**：Uv 属性目前仅适用于 `平面` 以及 `盒子` 形状。此外， *uv* 值会同等影响所有纹理层，因为它们是设置在 *形状*.
{% endhint %}

### 纹理缩放

当纹理被拉伸或缩小到与原始纹理图像不同的尺寸时，有时会产生伪影。在 3D 环境中，透视效果会自然造成这一点。存在各种 [纹理过滤](https://en.wikipedia.org/wiki/Texture_filtering) 算法可用来以不同方式对此进行补偿。

该 `材质` 对象默认使用 *双线性* 算法作为默认值，但你可以将其配置为使用 *最近邻* 或 *三线性* 算法来代替，只需设置纹理的 `filterMode` 属性。此项取值来自 `TextureFilterMode` 枚举：

* `TextureFilterMode.TFM_POINT`：使用“最近邻”算法。此设置非常适合像素风格图形，因为当纹理在屏幕上显示得更大时，其轮廓仍会保持清晰，而不会变得模糊。
* `TextureFilterMode.TFM_BILINEAR`：使用双线性算法来估算每个像素的颜色。
* `TextureFilterMode.TFM_TRILINEAR`：使用三线性算法来估算每个像素的颜色。

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Common({
		src: 'materials/atlas.png',
		filterMode: TextureFilterMode.TFM_BILINEAR,
	}),
})
```

## 无光照材质

大多数情况下，你会希望场景中的材质受光照条件影响，包括阴影以及被不同时段的色调变化所染色。但在某些情况下，你可能希望颜色保持纯净状态。这在播放视频时很有用，也适用于需要突出显示的抽象标记，它们用于向玩家提示信息。

要创建无光照材质，请使用 `Material.setBasicMaterial`。基础材质并不具备与 PBR 材质相同的所有属性，它们只包含最基本的：

* `diffuseColor`：用于颜色的 Color4
* `纹理`：纹理
* `alphaTexture`：用于透明层的单独纹理
* `alphaTest`：基于纹理颜色实现透明度的阈值
* `castShadows`：如果为 false，则不会向场景中的其他实体投射阴影。

```ts
Material.setBasicMaterial(screen, {
	diffuseColor: Color4.Red(),
})
```

## 头像肖像

要显示任何玩家的缩略图，请使用 `Material.Texture.Avatar` 在设置材质纹理时，传入一个现有玩家的地址。这样会从该玩家 256x256 的图像创建纹理，显示头部和肩膀。玩家会显示为当前服务器最近记录的穿戴物。

```ts
Material.setPbrMaterial(myEntity, {
	texture: Material.Texture.Avatar({
		userId: '0x517....',
	}),
})
```

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-76248b67a42c940111a0d827d74acde8d5093f73%2FavatarTexture.png?alt=media)

你可以获取任何 Decentraland 玩家的人像，即使他们当前没有连接，也即使他们没有已认领的 Decentraland 名称。

以下属性可在你作为参数传入的对象中使用：

* `userId`要显示其资料的用户 ID
* `filterMode`：决定纹理中的像素在渲染时如何被拉伸或压缩。此项取值来自 `TextureFilterMode` 枚举。参见 [纹理缩放](#texture-scaling).
* `wrapMode`：决定纹理如何平铺到对象上。此项取值来自 `TextureWrapMode` 枚举。参见 [纹理平铺](#texture-wrapping).

## 透明材质

要让纯色材质变为透明，只需将颜色定义为一个 `Color4`，并将第 4 个值设为 0 到 *0* 和 *1*之间的某个值。 *1*越接近

```typescript
let transparentRed = Color4.create(1, 0, 0, 0.5)

Material.setPbrMaterial(meshEntity, {
	albedoColor: transparentRed,
})
```

如果材质使用包含透明度的 .png 纹理，它默认会是不透明的，但你可以通过设置 `transparencyMode` 为 `MaterialTransparencyMode.MTM_ALPHA_BLEND`.

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-7cf6142ee2bc727894e78f01d271556822ebe0fc%2Ftransparent-image.png?alt=media)

```typescript
Material.setPbrMaterial(floor, {
	texture: Material.Texture.Common({
		src: 'assets/scene/transparent-image.png',
	}),
	transparencyMode: MaterialTransparencyMode.MTM_ALPHA_BLEND,
})
```

该 `transparencyMode` 可具有以下值：

* `MaterialTransparencyMode.MTM_OPAQUE`：完全不透明
* `MaterialTransparencyMode.MTM_ALPHA_TEST`：每个像素要么完全不透明，要么完全透明，取决于一个阈值。
* `MaterialTransparencyMode.MTM_ALPHA_BLEND`：可根据每个像素的值产生中间状态。
* `MaterialTransparencyMode.MTM_ALPHA_TEST_AND_ALPHA_BLEND`：使用两种方法的组合。
* `MaterialTransparencyMode.MTM_AUTO`：根据所提供的纹理来确定方法。

如果你设置了 `transparencyMode` 为 `MaterialTransparencyMode.MTM_ALPHA_TEST`，你可以微调用于判断每个像素是否透明的阈值。将 `alphaTest` 属性设置在 *0* 和 *1*之间。默认值是 *0.5*.

```ts
// 使用 alpha test
Material.setPbrMaterial(meshEntity1, {
	texture: Material.Texture.Common({
		src: 'images/myTexture.png',
	}),
	transparencyMode: MaterialTransparencyMode.MTM_ALPHA_TEST,
	alphaTest: 1,
})
```

当使用一个 [无光照材质](#unlit-materials)中，你可以可视化地添加一个 `alphaTexture` 来根据纹理仅使材质的某些区域透明时。

{% hint style="warning" %}
**📔 注意**：这必须是一张单通道图像。在这张图像中，使用红色或黑色来确定真实纹理的哪些部分应该是透明的。
{% endhint %}

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-29eeafc704d19336cc3b0dc023c91e278b4bcd94%2Fcircular-video-screen.png?alt=media)

```ts
// 使用 alpha test
Material.setPbrMaterial(meshEntity1, {
	texture: Material.Texture.Common({
		src: 'images/myTexture.png',
	}),
	alphaTexture: Material.Texture.Common({
		src: 'assets/scene/circle_mask.png',
		wrapMode: TextureWrapMode.TWM_MIRROR,
	}),
})
```

这与视频结合起来可以用在非常有趣的方式中。参见 [视频播放](/creator/content-creator-zh/chang-jing-sdk7/mei-ti/video-playing.md).

## 视频播放

要将 URL 中的视频流输入材质，或播放存储在场景中的文件视频，请参见 [视频播放](/creator/content-creator-zh/chang-jing-sdk7/mei-ti/video-playing.md).

视频被用作材质上的纹理时，你可以设置材质的任何其他属性来改变视频屏幕的外观。

## 高级语法

创建 `材质` 组件的完整语法，不借助任何简化它的辅助函数，如下所示：

```ts
Material.create(myEntity, {
	material: {
		$case: 'pbr',
		pbr: {
			texture: {
				tex: {
					$case: 'texture',
					texture: {
						src: 'images/scene-thumbnail.png',
					},
				},
			},
		},
	},
})

Material.create(myEntity, {
	material: {
		$case: 'pbr',
		pbr: {
			texture: {
				tex: {
					$case: 'avatarTexture',
					avatarTexture: {
						userId: '0x517....',
					},
				},
			},
		},
	},
})
```

这就是基础协议如何解释 Materials 组件的方式。辅助函数将其抽象掉并提供更友好的语法，但在幕后它们输出的是这种语法。

该 `$case` 字段允许你指定其中一种允许的类型。每种类型都支持一组不同的参数。有两层 `$case` 如下：

* 该 `材质` 字段支持以下值 `pbr` 和 `unlit`，用于定义材质的种类。
* 该 `tex` 字段在纹理内部支持以下值，用于定义纹理的种类：
  * `纹理`
  * `avatarTexture`
  * `videoTexture`

根据 `$case`，在这种情况下，你可以定义对应种类的对象，并传入任何相关属性。

要向一个 `材质` 组件添加到一个可能已经有该组件实例的实体上，请使用 `Material.createOrReplace()`。像 `Material.setPbrMaterial()` 这样的辅助函数会处理覆盖现有组件实例的问题，但对一个已经有此组件的实体运行 `Material.create()` 会返回错误。

## 修改 glTF 材质

使用 `GltfNodeModifiers` 组件来修改一个 *glTF* 模型的材质。此组件允许你使用自己的材质覆盖一个 *glTF* 模型的材质。你可以使用该 `材质` 组件，包括纹理、视频纹理、无光照材质等。

有两种方式使用 `GltfNodeModifiers` 组件：

* 通过将整个模型的材质修改为 `path` 属性留空字符串。
* 通过将模型中特定节点（或多个节点）的材质修改为设置 `path` 属性为该节点的路径。

### 修改整个模型的材质

以下示例展示了如何修改一个 *glTF* 模型的材质。在这种情况下，整个模型的材质被修改为红色。

```ts
import { GltfNodeModifiers, GltfContainer, Transform } from '@dcl/sdk/ecs'

const myEntity = engine.addEntity()

GltfContainer.create(myEntity, {
	src: 'models/myModel.glb',
})

Transform.create(myEntity, {
	position: Vector3.create(4, 0, 4),
})

GltfNodeModifiers.create(myEntity, {
	modifiers: [
		{
			path: '',
			material: {
				material: {
					$case: 'pbr',
					pbr: {
						albedoColor: Color4.Red(),
					},
				},
			},
		},
	],
})
```

该 `GltfNodeModifiers` 组件具有以下属性：

* `modifiers`：一个修饰符数组。每个修饰符具有以下属性：
  * `path`：要修改的模型中节点的路径。
  * `材质`：要使用的材质。

该 `path` 属性是一个字符串，表示模型中要修改的节点路径。 *glTF* 如果你想修改整个模型的材质，可以使用空字符串。如果你想修改某个特定节点的材质，可以使用该节点的路径。该路径必须指向一个网格节点，而不是顶点节点。

{% hint style="info" %}
**💡 提示**：你可以使用 [Babylon Sandbox 应用](https://sandbox.babylonjs.com/) 来检查 *glTF* 模型，并找到你想要修改的节点路径。

不过，在某些模型中，Babylon 沙盒可能会列出属于顶点而不是网格的路径，这些路径将无法生效。如果你尝试使用无效路径，场景的控制台会显示一条错误信息，其中包含该模型上所有有效路径的完整列表。
{% endhint %}

该 `材质` 属性是一个表示要使用的材质的对象。它需要使用 [高级语法](#advanced-syntax) 来编写材质，如上面的示例所示。像 `Material.setPbrMaterial()` 这样的辅助函数在这里不能使用。

### 修改模型中特定节点的材质

以下示例展示了如何修改 *glTF* 模型中特定节点的材质。在这种情况下，头部的材质被修改为使用另一张纹理。

```ts
import { GltfNodeModifiers, GltfContainer, Transform } from '@dcl/sdk/ecs'

const myEntity = engine.addEntity()

GltfContainer.create(myEntity, {
	src: 'models/myModel.glb',
})

Transform.create(myEntity, {
	position: Vector3.create(4, 0, 4),
})

GltfNodeModifiers.create(myEntity, {
	modifiers: [
		{
			path: 'M_Head_BaseMesh',
			material: {
				material: {
					$case: 'pbr',
					pbr: {
						texture: Material.Texture.Common({
							src: 'assets/scene/images/blinking-head.png',
						}),
					},
				},
			},
		},
	],
})
```

一个 `GltfNodeModifiers` 可以包含多个修饰符，每个修饰符修改模型中的不同节点。以下示例展示了如何修改一个 *glTF* 模型。

```ts
import { GltfNodeModifiers, GltfContainer, Transform } from '@dcl/sdk/ecs'

const myEntity = engine.addEntity()

GltfContainer.create(myEntity, {
	src: 'models/myModel.glb',
})

Transform.create(myEntity, {
	position: Vector3.create(4, 0, 4),
})

GltfNodeModifiers.create(myEntity, {
	modifiers: [
		{
			path: 'M_Head_BaseMesh',
			material: {
				material: {
					$case: 'pbr',
					pbr: {
						albedoColor: Color4.Red(),
					},
				},
			},
		},
		{
			path: 'M_Body_BaseMesh',
			material: {
				material: {
					$case: 'pbr',
					pbr: {
						albedoColor: Color4.Blue(),
					},
				},
			},
		},
	],
})
```

### 修改现有材质中的字段

Material 组件提供了一个简化的接口，用于访问和修改 Material 组件属性。它消除了深入嵌套的联合结构（`PBR` 与 `Unlit`, `纹理` 与 `avatarTexture` 与 `videoTexture`）之间的导航，使材质操作更加直观，也更不容易出错。

以下方法可轻松操作 `材质` 组件：

* `Material.getFlat(entity: Entity): ReadonlyFlatMaterial`：它返回一个 **只读** `FlatMaterial` 访问器对象，可直接读取材质属性。如果实体没有 `材质` 组件，就会返回错误。为避免这种情况，可以使用接下来的方法作为替代。

```ts
const src = Material.getFlat(entity).texture.src
```

* `Material.getFlatOrNull(entity: Entity): ReadonlyFlatMaterial | null`：它返回一个 **只读或空值**，具体取决于实体是否具有 `材质` 组件。

```ts
const src = Material.getFlatOrNull(entity)?.texture.src
```

* `Material.getFlatMutable(entity: Entity): FlatMaterial`：它返回一个 **读写** `FlatMaterial`对象，允许修改它的属性。如果被调用的实体没有 `材质` 组件。

```ts
const src = Material.getFlatMutable(entity).texture.src
```

* `Material.getFlatMutableOrNull(entity: Entity): FlatMaterial | null`：它返回一个 **读写或空值** `FlatMaterial` 对象，允许修改它的属性。如果实体没有 `Material 组件`，则会返回 `null`.

```ts
const flatMaterial = Material.getFlatMutableOrNull(entity)
if (flatMaterial) {
	flatMaterial.texture.src = myNewTextureFile
}
```

### 从 glTF 模型中移除阴影

要从一个 *glTF* 模型中移除阴影，你可以设置 `castShadows` 属性设为 `false` 在 `GltfNodeModifiers` 对象。这会保留模型原有的材质，但会阻止它投射阴影。这对于不需要投射阴影的模型很有用，例如光束。

```ts
import { GltfNodeModifiers } from '@dcl/sdk/ecs'

GltfNodeModifiers.create(myEntity, {
	modifiers: [
		{
			path: '',
			castShadows: false,
		},
	],
})
```

{% hint style="info" %}
**💡 提示**：材质中使用的纹理也可以应用到粒子效果中。请参见 [粒子系统](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/particle-system.md) ，了解如何将自定义纹理和精灵表与粒子一起使用。
{% endhint %}

{% hint style="info" %}
**💡 提示**：关于材质的可工作示例，请查看 [`52,-52-testing-gallery`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/52,-52-testing-gallery) 测试场景，它会并排展示 PBR 金属度/粗糙度/自发光/法线贴图组合， [`74,-8-gltfnodemodifier`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/74,-8-gltfnodemodifier)，它会在单个 GLTF 节点上覆盖 PBR 和无光照材质，以及 [`0,3-texture-movement`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,3-texture-movement)，它会为纹理偏移和平铺设置动画。
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/materials.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
