Materials via code
Materials #
Materials can be applied to entities that use primitive shapes (cube, sphere, plane, etc) by adding a Material
component. This component has several fields that allow you to configure the properties of the material, add a texture, etc.
You can’t add material components to glTF models. glTF models include their own materials that are implicitly imported into a scene together with the model.
When importing a 3D model with its own materials, keep in mind that not all shaders are supported by the Decentraland engine. Only standard materials and PBR (physically based rendering) materials are supported. See external 3D model considerations for more details.
There are different types of supported materials:
- PBR (Physically Based Rendering): The most common kind of material in Decentraland. It supports plain colors or textures, and different properties like metallic, emissive, transparency, etc. Read more about PBR .
- Basic materials: They don’t respond to lights and shadows, which makes them ideal for displaying billboard images.
Use the Scene Editor #
The easiest way to give an entity a Material is to use the Scene Editor . You can add a Material component to your entity and then configure all of the available fields on the Scene Editor UI. See Add Components .
Add a material #
The following example creates a PBR material and sets some of its fields to give it a red color and metallic properties. This material is added to an entity that also has a box shape, so it will color the box with this material.
//Create entity and assign shape
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity)
//Create material and configure its fields
Material.setPbrMaterial(meshEntity, {
albedoColor: Color4.Red(),
metallic: 0.8,
roughness: 0.1,
})
To change the material of an entity that already has a Material
component, run Material.setPbrMaterial()
or any of the other helper functions and it will overwrite the original material. There’s no need to remove the original Material
or to use the advanced syntax.
//Create entity and assign shape
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity)
//Create material and configure its fields
Material.setPbrMaterial(meshEntity, {
albedoColor: Color4.Red(),
})
//Overwrite with new material component
Material.setPbrMaterial(meshEntity, {
albedoColor: Color4.Blue(),
})
📔 Note: The
Material
component must be imported via
import { Material } from "@dcl/sdk/ecs"
See Imports for how to handle these easily.
Material colors #
Give a material a plain color. In a PBR Material, you set the albedoColor
field. Albedo colors respond to light and can include shades on them.
Color values are of type Color4
, composed of r, g and b values (red, green, and blue). Each of these takes values between 0 and 1. By setting different values for these, you can compose any visible color. For black, set all three to 0. For white, set all to 1.
📔 Note: If you set any color inalbedoColor
to a value higher than 1, it will appear as emissive, with more intensity the higher the value. So for example,{r: 15, g: 0, b: 0}
produces a very bright red glow.
See color types for more details on how to set colors.
You can also edit the following fields in a PBR Material to fine-tune how its color is perceived:
- emissiveColor: The color emitted from the material.
- reflectivityColor: AKA Specular Color in other nomenclature.
To create a plain color material that is not affected by light and shadows in the environment, create a basic material instead of a PBR material.
Material.setBasicMaterial(myEntity, {
diffuseColor: Color4.Black(),
})
Using textures #
Set an image file as a texture on a material by setting the texture
parameter.
//Create entity and assign shape
const meshEntity = engine.addEntity()
Transform.create(meshEntity, {
position: Vector3.create(4, 1, 4),
})
MeshRenderer.setBox(meshEntity)
//Create material and configure its fields
Material.setPbrMaterial(meshEntity, {
texture: Material.Texture.Common({
src: 'materials/wood.png',
}),
})
In the example above, the image for the material is located in a materials
folder, which is located at root level of the scene project folder.
💡 Tip: We recommend keeping your texture image files separate in a /materials
folder inside your scene.
While creating a texture, you can also pass additional parameters:
filterMode
: Determines how pixels in the texture are stretched or compressed when rendered. This takes a value from theTextureFilterMode
enum. See Texture Scaling .wrapMode
: Determines how a texture is tiled onto an object. This takes a value from theTextureWrapMode
enum. See Texture Wrapping .
Material.setPbrMaterial(myEntity, {
texture: Material.Texture.Common({
src: 'materials/wood.png',
filterMode: TextureFilterMode.TFM_BILINEAR,
wrapMode: TextureWrapMode.TWM_CLAMP,
}),
})
To create a texture that is not affected by light and shadows in the environment, create a basic material instead of a PBR material.
Material.setBasicMaterial(myEntity, {
texture: Material.Texture.Common({
src: 'materials/wood.png',
}),
})
Textures from an external URL #
You can point the texture of your material to an external URL instead of an internal path in the scene project.
Material.setBasicMaterial(myEntity, {
texture: Material.Texture.Common({
src: 'https://wearable-api.decentraland.org/v2/collections/community_contest/wearables/cw_tuxedo_tshirt_upper_body/thumbnail',
}),
})
The URL must start with https
, http
URLs aren’t supported. The site where the image is hosted should also have
CORS policies (Cross Origin Resource Sharing)
that permit externally accessing it.
Multi-layered textures #
You can use several image files as layers to compose more realistic textures, for example including a bumpTexture
and a emissiveTexture
.
Material.setPbrMaterial(myEntity, {
texture: Material.Texture.Common({
src: 'materials/wood.png',
}),
bumpTexture: Material.Texture.Common({
src: 'materials/woodBump.png',
}),
})
Texture wrapping #
If you want the texture to be mapped to specific scale or alignment on your entities, then you need to configure uv properties on the MeshRenderer component .
You set u and v coordinates on the 2D image of the texture to correspond to the vertices of the shape. The more vertices the entity has, the more uv coordinates need to be defined on the texture, a plane for example needs to have 8 uv points defined, 4 for each of its two faces.
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,
}),
})
The following example includes a function that simplifies the setting of uvs. The setUVs
function defined here receives a number of rows and columns as parameters, and sets the uvs so that the texture image is repeated a specific number of times.
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 [
// North side of unrortated plane
0, //lower-left corner
0,
cols, //lower-right corner
0,
cols, //upper-right corner
rows,
0, //upper left-corner
rows,
// South side of unrortated plane
cols, // lower-right corner
0,
0, // lower-left corner
0,
0, // upper-left corner
rows,
cols, // upper-right corner
rows,
]
}
For setting the UVs for a box
mesh shape, the same structure applies. Each of the 6 faces of the cube takes 4 pairs of coordinates, one for each corner. All of these 48 values are listed as a single array.
You can also define how the texture is tiled if the mapping spans more than the dimensions of the texture image. The texture
object lets you configure the wrapping mode by setting the wrapMode
field. This property takes its values from the TextureWrapMode
enum, which allows for the following values:
TextureWrapMode.TWM_CLAMP
: The texture is only displayed once in the specified size. The rest of the surface of the mesh is left transparent.TextureWrapMode.TWM_REPEAT
: The texture is repeated as many times as it fits in the mesh, using the specified size.TextureWrapMode.TWM_MIRROR
: As in wrap, the texture is repeated as many times as it fits, but the orientation of these repetitions is mirrored.
Material.setPbrMaterial(myEntity, {
texture: Material.Texture.Common({
src: 'materials/atlas.png',
wrapMode: TextureWrapMode.TWM_MIRROR,
}),
})
The example above sets the wrapping mode to TWM_MIRROR
.
📔 Note: Uv properties are currently only available onplane
and onbox
shapes.
Texture scaling #
When textures are stretched or shrinked to a different size from the original texture image, this can sometimes create artifacts. In a 3D environment, the effects of perspective cause this naturally. There are various texture filtering algorithms that exist to compensate for this in different ways.
The Material
object uses the bilinear algorithm by default, but it lets you configure it to use the nearest neighbor or trilinear algorithms instead by setting the samplingMode
property of the texture. This takes a value from the TextureFilterMode
enum:
TextureFilterMode.TFM_POINT
: Uses a “nearest neighbor” algorithm. This setting is ideal for pixel art style graphics, as the contours will remain sharply marked as the texture is seen larger on screen instead of being blurred.TextureFilterMode.TFM_BILINEAR
: Uses a bilinear algorithm to estimate the color of each pixel.TextureFilterMode.TFM_TRILINEAR
: Uses a trilinear algorithm to estimate the color of each pixel.
Material.setPbrMaterial(myEntity, {
texture: Material.Texture.Common({
src: 'materials/atlas.png',
filterMode: TextureFilterMode.TFM_BILINEAR,
}),
})
Unlit Materials #
Most of the times you’ll want the materials in your scene to be affected by the lighting conditions, including shadows and being tinted by the hue changes of different times of day. But in other cases you might want to show the colors in their pure state. This is useful when playing videos, or also for abstract markers that need to stand out, that are meant for signalling hints to the player.
To create an unlit material, use Material.setBasicMaterial
. Basic materials don’t have all the same properties as PBR materials, they only have the essential:
diffuseColor
: Color4 for the colortexture
: TexturealphaTexture
: Separate texture for the transparency layeralphaTest
: Threshold for achieving transparency based on the color of the texturecastShadows
: If false, no shadows are projected onto other entities in the scene.
Material.setBasicMaterial(screen, {
diffuseColor: Color4.Red(),
})
Avatar Portraits #
To display a thumbnail image of any player, use Material.Texture.Avatar
when setting the texture of your material, passing the address of an existing player. This creates a texture from a 256x256 image of the player, showing head and shoulders. The player is displayed wearing the set of wearables that the current server last recorded.
Material.setPbrMaterial(myEntity, {
texture: Material.Texture.Avatar({
userId: '0x517....',
}),
})
You can fetch the portrait of any Decentraland player, even if they’re not currently connected, and even if they don’t have a claimed Decentraland name.
The following properties are supported within the object you pass as an argument:
userId
: ID of the user who’s profile you want to displayfilterMode
: Determines how pixels in the texture are stretched or compressed when rendered. This takes a value from theTextureFilterMode
enum. See Texture Scaling .wrapMode
: Determines how a texture is tiled onto an object. This takes a value from theTextureWrapMode
enum. See Texture Wrapping .
Transparent materials #
To make a material with a plain color transparent, simply define the color as a Color4
, and set the 4th value to something between 0 and 1. The closer to 1, the more opaque it will be.
let transparentRed = Color4.create(1, 0, 0, 0.5)
Material.setPbrMaterial(meshEntity, {
albedoColor: transparentRed,
})
To make a material with a texture only transparent in regions of the texture:
-
Set an image in
alphaTexture
.Note: This must be a single-channel image. In this image use the color red to determine what parts of the real texture should be transparent.
-
Optionally set the texture normally, and set the
transparencyMode
to field.
The transparencyMode
takes its value from the MaterialTransparencyMode
enum, that can have the following values:
MaterialTransparencyMode.MTM_OPAQUE
: No transparency at allMaterialTransparencyMode.MTM_ALPHA_TEST
: Each pixel is either completely opaque or completely transparent, based on a threshold.MaterialTransparencyMode.MTM_ALPHA_BLEND
: Intermediate values are possible based on the value of each pixel.MaterialTransparencyMode.MTM_ALPHA_TEST_AND_ALPHA_BLEND
: Uses a combination of both methods.MaterialTransparencyMode.MTM_AUTO
: Determines the method based on the provided texture.
If you set the transparencyMode
to MaterialTransparencyMode.MTM_ALPHA_TEST
, you can fine tune the threshold used to determine if each pixel is transparent or not. Set the alphaTest
property between 0 and 1. By default its value is 0.5.
// Using alpha test
Material.setPbrMaterial(meshEntity1, {
texture: Material.Texture.Common({
src: 'images/myTexture.png',
}),
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_TEST,
alphaTest: 1,
})
// Using a alpha blend
Material.setPbrMaterial(meshEntity1, {
texture: Material.Texture.Common({
src: 'images/myTexture.png',
}),
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_BLEND,
})
Video playing #
To stream video from a URL into a material, or play a video from a file stored in the scene, see video playing .
The video is used as a texture on a material, you can set any of the other properties of materials to alter how the video screen looks.
Advanced syntax #
The complete syntax for creating a Materials
component, without any helpers to simplify it, looks like this:
Material.create(myEntity, {
texture: {
tex: {
$case: 'texture',
texture: {
src: 'images/scene-thumbnail.png',
},
},
},
})
Material.create(myEntity, {
texture: {
tex: {
$case: 'avatarTexture',
avatarTexture: {
userId: '0x517....',
},
},
},
})
This is how the base protocol interprets Materials components. The helper functions abstract away from this and expose a friendlier syntax, but behind the scenes they output this syntax.
The $case
field allows you to specify one of the allowed types. Each type supports a different set of parameters. In the example above, the box
type supports a uvs
field.
The supported values for $case
are the following:
texture
avatarTexture
Depending on the value of $case
, it’s valid to define the object for the corresponding shape, passing any relevant properties.
To add a Material
component to an entity that potentially already has an instance of this component, use Material.createOrReplace()
. The helper functions like MeshRenderer.setPbrMaterial()
handle overwriting existing instances of the component, but running Material.create()
on an entity that already has this component returns an error.