Materials are used to express how a graphical component (a sprite, tilemap, font, GUI node, model etc) should be rendered.
A material holds tags, information that is used in the rendering pipeline to select objects to be rendered. It also holds references to shader programs that are compiled through the available graphics driver and uploaded to the graphics hardware and run when the component is rendered each frame.
To create a material, right click a target folder in the Assets browser and select New... ▸ Material. (You can also select File ▸ New... from the menu, and then select Material). Name the new material file and press Ok.

The new material will open in the Material Editor.

The material file contains the following information:
render.enable_material(). The name should be unique..vp) to use when rendering with the material. The vertex shader program is run on the GPU for each of a component’s primitive vertices. It computes the screen position of each vertex and also optionally output “varying” variables that are interpolated and input to the fragment program..fp) to use when rendering with the material. The program runs on the GPU for each of a primitive’s fragments (pixels) and its purpose is to decide the color of each fragment. This is usually done by texture lookups and calculations based on input variables (varying variables or constants).render.predicate() to collect components that should be drawn together. See the Render documentation on how to do that. The maximum number of tags you can use in a project is 32.Shader attributes (also referred to as vertex streams, or vertex attributes), is a mechanism for how the GPU retrieves vertices from memory in order to render geometry. The vertex shader specifies a set of streams by using the attribute keyword and in most cases Defold produces and binds the data automatically under the hood based on the names of the streams. However, in some cases you might want to forward more data per vertex to achieve a specific effect that the engine does not produce. A vertex attribute can be configured with the following fields:
SEMANTIC_TYPE_COLOR will show a color picker in the editor, while the data will still be passed in as-is from the engine to the shader.
SEMANTIC_TYPE_NONE The default semantic type. Does not have any other effect on the attribute other than passing the material data for the attribute directly to the vertex buffer (default)SEMANTIC_TYPE_POSITION Produces per-vertex position data for the attribute. Can be used together with coordinate space to tell the engine how the positions will be calculatedSEMANTIC_TYPE_TEXCOORD Produces per-vertex texture coordinates for the attributeSEMANTIC_TYPE_PAGE_INDEX Produces per-vertex page indices for the attributeSEMANTIC_TYPE_COLOR Affects how the editor interprets the attribute. If an attribute is configured with a color semantic, a color picked widget will be shown in the inspectorSEMANTIC_TYPE_NORMAL Produces per-vertex normal data for the attributeSEMANTIC_TYPE_TANGENT Produces per-vertex tangent data for the attributeSEMANTIC_TYPE_WORLD_MATRIX Produces per-vertex world matrix data for the attributeSEMANTIC_TYPE_NORMAL_MATRIX Produces per-vertex normal matrix data for the attributeSEMANTIC_TYPE_TEXTURE_TRANSFORM_2D Produces a per-vertex 3x3 texture transform matrix for the attribute. For particle components, the engine provides a matrix that transforms coordinates into atlas space for the image property on the component. For sprite components, the engine provides a matrix for each image the component is using (when using multi-texturing). For model components, an identity matrix is provided.TYPE_BYTE Signed 8-bit byte valuesTYPE_UNSIGNED_BYTE Unsigned 8-bit byte valuesTYPE_SHORT Signed 16-bit short valuesTYPE_UNSIGNED_SHORT Unsigned 16-bit short valuesTYPE_INT Signed integer valuesTYPE_UNSIGNED_INT Unsigned integer valuesTYPE_FLOAT Floating point values (default)VECTOR_TYPE_SCALAR Single scalar valueVECTOR_TYPE_VEC2 2D vectorVECTOR_TYPE_VEC3 3D vectorVECTOR_TYPE_VEC4 4D vector (default)VECTOR_TYPE_MAT2 2D matrixVECTOR_TYPE_MAT3 3D matrixVECTOR_TYPE_MAT4 4D matrixVertex Once per vertex, e.g a position attribute will typically be given to the vertex function per vertex in the mesh (default)Instance Once per instance, e.g a world matrix attribute will typically be given to the vertex function once per instanceCustom attributes can also be used to trim memory footprint on both CPU and GPU by reconfiguring the streams to use a smaller data type, or a different element count.
The material system will assign a default semantic type automatically based on the name of the attribute in run-time for a specific set of names:
position - semantic type: SEMANTIC_TYPE_POSITIONtexcoord0 - semantic type: SEMANTIC_TYPE_TEXCOORDtexcoord1 - semantic type: SEMANTIC_TYPE_TEXCOORDpage_index - semantic type: SEMANTIC_TYPE_PAGE_INDEXcolor - semantic type: SEMANTIC_TYPE_COLORnormal - semantic type: SEMANTIC_TYPE_NORMALtangent - semantic type: SEMANTIC_TYPE_TANGENTmtx_world - semantic type: SEMANTIC_TYPE_WORLD_MATRIXmtx_normal - semantic type: SEMANTIC_TYPE_NORMAL_MATRIXmtx_texture_transform_2d - semantic type: SEMANTIC_TYPE_TEXTURE_TRANSFORM_2DIf you have entries for these attributes in the material, the default semantic type will be overridden with whatever you have configured in the material editor.
Similar to user defined shader constants, you can also update vertex attributes in runtime by calling go.get, go.set and go.animate:

go.set("#sprite", "tint", vmath.vector4(1,0,0,1))
go.animate("#sprite", "tint", go.PLAYBACK_LOOP_PINGPONG, vmath.vector4(1,0,0,1), go.EASING_LINEAR, 2)
There are some caveats to updating the vertex attributes however, whether or not a component can use the value depends on the semantic type of the attribute. For example, a sprite component supports the SEMANTIC_TYPE_POSITION so if you update an attribute that has this semantic type, the component will ignore the overridden value since the semantic type dictates that the data should always be produced by the sprites position.
Model components also expose custom material attributes through go.get(), go.set() and go.animate(). For example, after defining an attribute named my_attribute in the model material:
go.set("#model", "my_attribute", vmath.vector4(1, 0, 0, 1))
go.animate("#model", "my_attribute", go.PLAYBACK_LOOP_PINGPONG,
vmath.vector4(0, 1, 0, 1), go.EASING_LINEAR, 2)
Only the first mesh in a model with multiple meshes can currently be addressed this way. Updating a non-instanced per-vertex attribute may also rebuild and upload vertex data proportional to the mesh size, so frequent updates can be expensive for large meshes.
In cases where that a vertex attribute is either a scalar or a vector type other than a Vec4 you can still set the data using go.set:
-- The last two components in the vec4 will not be used!
go.set("#sprite", "sprite_position_2d", vmath.vector4(my_x,my_y,0,0))
go.animate("#sprite", "sprite_position_2d", go.PLAYBACK_LOOP_PINGPONG, vmath.vector4(1,2,0,0), go.EASING_LINEAR, 2)
The same is true for matrix attributes, if the attribute is a matrix type other than a Mat4 you can still set the data using go.set.
Using a texture transform attribute to convert UV coordinates to atlas space:
#version 140
in vec3 position;
in vec4 texcoord0;
in mat3 texture_transform_2d;
out vec2 var_texcoord0;
void main()
{
// Extract position from the transform
vec2 atlas_pos = texture_transform_2d[2].xy;
// Extract the scale from the transform
vec2 atlas_size = vec2(
length(texture_transform_2d[0].xy),
length(texture_transform_2d[1].xy)
);
// convert to local UV (0..1)
vec2 localUV = (texcoord0 - atlas_pos) / atlas_size;
// Alternatively, if the UV coordinates already are in the 0..1 range,
// you can transform into atlas space directly by multiplying the transform:
vec2 transformedUv = texture_transform_2d * texcoord0;
// Pass the value into the fragment shader
var_texcoord0 = localUV;
// ... rest of vertex shader
}
Instancing is a technique used to efficiently draw multiple copies of the same object in a scene. Instead of creating a separate copy of the object each time it’s used, instancing allows the graphics engine to create a single object and then reuse it multiple times. For example, in a game with a large forest, instead of creating a separate tree model for each tree, instancing allows you to create one tree model and then place it hundreds or thousands of times with different positions and scales. The forest can now be rendered with a single draw call instead of individual draw calls for each tree.
Instancing is currently only available for Model components.
Instancing is enabled automatically when possible. Defold heavily relies on batching the draw state as much as possible - for instancing to work some requirements must be met:
render.enable_material)Configuring a vertex attribute to be repeated per instance requires that the Step function is set to Instance. This is done automatically for certain semantic types based on name (see the Default attribute semantics table above), but it can also be set manually in the material editor by setting the Step function to Instance.
As a simple example, the following scene has four game objects with a model component each:

The material is configured as such, with a single custom vertex attribute that is repeated per instance:

The vertex shader has multiple per-instance attributes specified:
// Per vertex attributes
attribute highp vec4 position;
attribute mediump vec2 texcoord0;
attribute mediump vec3 normal;
// Per instance attributes
attribute mediump mat4 mtx_world;
attribute mediump mat4 mtx_normal;
attribute mediump vec4 instance_color;
Note that the mtx_world and mtx_normal will be configured to use the step function Instance by default. This can be changed in the material editor by adding an entry for them and setting the Step function to Vertex, which will make the attribute be repeated per vertex instead of per instance.
To verify that the instancing works in this case, you can look at the web profiler. In this case, since the only thing that changes between the instances of the box is the per-instance attributes, it can be rendered with a single draw call:

OpenGL 3.1 on desktop and OpenGL ES 3.0 on mobile provide instancing as a core feature. Older OpenGL ES and WebGL contexts may still support it through an extension such as ANGLE_instanced_arrays; other older adapters do not. When instancing is unavailable, rendering still works by default, but may be less performant.
Use graphics.get_adapter_info() to detect support and select a cheaper material or omit instance-heavy content when necessary. The features field is an array of the supported feature constants, not a table keyed by those constants:
local function has_context_feature(feature)
local adapter_info = graphics.get_adapter_info()
for _, supported_feature in ipairs(adapter_info.features) do
if supported_feature == feature then
return true
end
end
return false
end
local instancing_supported = has_context_feature(
graphics.CONTEXT_FEATURE_INSTANCING
)
Shader constants, or “uniforms” are values that are passed from the engine to vertex and fragment shader programs. To use a constant you define it in the material file as either a Vertex Constant property or a Fragment Constant property. Corresponding uniform variables need to be defined in the shader program. The following constants can be set in a material:
CONSTANT_TYPE_WORLDCONSTANT_TYPE_VIEWCONSTANT_TYPE_PROJECTIONCONSTANT_TYPE_VIEWPROJCONSTANT_TYPE_WORLDVIEWCONSTANT_TYPE_WORLDVIEWPROJCONSTANT_TYPE_WORLD_INVERSECONSTANT_TYPE_VIEW_INVERSECONSTANT_TYPE_PROJECTION_INVERSECONSTANT_TYPE_VIEWPROJ_INVERSECONSTANT_TYPE_WORLDVIEW_INVERSECONSTANT_TYPE_WORLDVIEWPROJ_INVERSECONSTANT_TYPE_NORMALCONSTANT_TYPE_TIMEvector4 where .x is elapsed time since engine start, .y is the delta time from the previous frame, and .z and .w are currently zero. The engine updates this value automatically; it does not need to be updated with go.set(). See the Shadertoy tutorial for an example.
Declare a Time constant named time in a modern GLSL uniform block:
uniform fragment_inputs
{
vec4 time;
};
CONSTANT_TYPE_USERExample:
go.set("#sprite", "tint", vmath.vector4(1,0,0,1))
go.animate("#sprite", "tint", go.PLAYBACK_LOOP_PINGPONG, vmath.vector4(1,0,0,1), go.EASING_LINEAR, 2)
CONSTANT_TYPE_USER_MATRIX4Example:
go.set("#sprite", "m", vmath.matrix4())
Inside a GUI script, read and write a node’s material constants with gui.get() and gui.set() rather than the go functions. Vector components, matrix constants, and constant arrays are supported. Array indices in the options table are 1-based:
local node = gui.get_node("button")
local tint = gui.get(node, "tint")
gui.set(node, "tint.x", 0.5)
gui.set(node, "light_matrix", vmath.matrix4())
gui.set(node, "tint_array", vmath.vector4(1, 0, 0, 1), { index = 1 })
In order for a material constant of type CONSTANT_TYPE_USER or CONSTANT_TYPE_USER_MATRIX4 to be available using go.get() and go.set(), or gui.get() and gui.set(), it has to be used in the shader program. If the constant is defined in the material but not used in the program it will be removed from the material and it will not be available at run-time.
Samplers are used to sample the color information from a texture (a tile source or atlas). The color information can then be used for calculations in the shader program.
Sprite, tilemap, GUI and particle effect components automatically bind their image texture to the first declared sampler2D. Sprite components also support multiple textures: every sampler declared in the material becomes a named image slot in the Sprite component. The first texture supplies the sprite’s animation data and drives the frame sequence. For each frame, its image id is used to find the corresponding image in every additional texture, which supplies its own UVs. The assigned atlases or tile sources should therefore contain matching frame ids and similarly shaped images; differing polygon-packed shapes can cause texture bleeding. See Multi textured sprites for details.
For a component or render workflow that does not expose an additional texture slot, use render.enable_texture() to bind extra texture samplers from the render script.
![]()
-- mysprite.fp
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D MY_SAMPLER;
void main()
{
gl_FragColor = texture2D(MY_SAMPLER, var_texcoord0.xy);
}
You can specify a component’s sampler settings by adding the sampler by name in the materials file. If you don’t set up your sampler in the materials file, the global graphics project settings are used.

For model components, you need to specify your samplers in the material file with the settings you want. The editor will then allow you to set textures for any model component that use the material:

-- mymodel.fp
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D TEXTURE_1;
uniform lowp sampler2D TEXTURE_2;
void main()
{
lowp vec4 color1 = texture2D(TEXTURE_1, var_texcoord0.xy);
lowp vec4 color2 = texture2D(TEXTURE_2, var_texcoord0.xy);
gl_FragColor = color1 * color2;
}

sampler2D declared in the fragment shader.WRAP_MODE_REPEAT will repeat texture data outside the range [0,1].WRAP_MODE_MIRRORED_REPEAT will repeat texture data outside the range [0,1] but every second repetition is mirrored.WRAP_MODE_CLAMP_TO_EDGE will set texture data for values greater than 1.0 to 1.0, and any values less than 0.0 is set to 0.0—i.e. the edge pixels will be repeated to the edge.Default uses the default filter option specified in the game.project file under Graphics as Default Texture Min Filter and Default Texture Mag Filter .FILTER_MODE_NEAREST uses the texel with coordinates nearest the center of the pixel.FILTER_MODE_LINEAR sets a weighted linear average of the 2x2 array of texels that lie nearest to the center of the pixel.FILTER_MODE_NEAREST_MIPMAP_NEAREST chooses the nearest texel value within an individual mipmap.FILTER_MODE_NEAREST_MIPMAP_LINEAR selects the nearest texel in the two nearest best choices of mipmaps and then interpolates linearly between these two values.FILTER_MODE_LINEAR_MIPMAP_NEAREST interpolates linearly within an individual mipmap.FILTER_MODE_LINEAR_MIPMAP_LINEAR uses linear interpolation to compute the value in each of two maps and then interpolates linearly between these two values.When the rendering pipeline draws, it pulls constant values from a default system constants buffer. You can create a custom constants buffer to override the default constants and instead set shader program uniforms programmatically in the render script:
self.constants = render.constant_buffer() -- <1>
self.constants.tint = vmath.vector4(1, 0, 0, 1) -- <2>
...
render.draw(self.my_pred, {constants = self.constants}) -- <3>
tint constant to bright redNote that the buffer’s constant elements are referenced like an ordinary Lua table, but you can’t iterate over the buffer with pairs() or ipairs().