| name | lens-studio-materials-shaders |
| description | Reference guide for materials and shaders in Lens Studio — covering runtime material property changes (clone-before-modify, mainPass.baseColor, mainPass.opacity, mainPass.baseTex), blend modes (Normal/Alpha/Add/Screen/Multiply), depth and cull settings (depthTest, depthWrite, twoSided, cullMode), render order, material variants, assigning textures and render targets, reading and writing RenderTarget textures for post-processing, the graph-based Material Editor node system, custom shader graph nodes, and common shader pitfalls. Use this skill for any lens that needs to change material colors or textures at runtime, implement custom visual effects with shaders, set up post-processing render pipelines, chain render targets, or debug material/blend-mode issues — covering MaterialEditor, Drawing, and HairSimulation examples. |
Lens Studio Materials & Shaders — Reference Guide
Lens Studio uses a graph-based Material Editor to create shaders, combined with a runtime TypeScript API for modifying material properties. Most visual customization flows through RenderMeshVisual.material.
The Golden Rule: Clone Before Modify
Materials in Lens Studio are shared assets — multiple scene objects can use the same material. Modifying it directly changes every object using it. Always clone first:
const meshVisual = this.sceneObject.getComponent('Component.RenderMeshVisual')
meshVisual.material.mainPass.baseColor = new vec4(1, 0, 0, 1)
const mat = meshVisual.material.clone()
meshVisual.material = mat
mat.mainPass.baseColor = new vec4(1, 0, 0, 1)
Clone once in OnStartEvent, then modify the cached clone freely each frame.
Common mainPass Properties
const mat = meshVisual.material.clone()
meshVisual.material = mat
mat.mainPass.baseColor = new vec4(0.2, 0.8, 0.4, 1.0)
mat.mainPass.opacity = 0.5
mat.mainPass.baseTex = myTexture
mat.mainPass.emissiveColor = new vec4(1.0, 0.5, 0.0, 1.0)
mat.mainPass.metallic = 0.0
mat.mainPass.roughness = 0.8
Blend Modes
Set blendMode on the mainPass to control how the material composites over what's behind it:
mat.mainPass.blendMode = BlendMode.Normal
mat.mainPass.blendMode = BlendMode.Add
mat.mainPass.blendMode = BlendMode.Screen
mat.mainPass.blendMode = BlendMode.Multiply
mat.mainPass.blendMode = BlendMode.AlphaToCoverage
Use BlendMode.Add for particles and VFX that should glow over the scene. Use BlendMode.Normal with opacity for standard transparent surfaces.
Depth & Cull Settings
mat.mainPass.depthTest = true
mat.mainPass.depthWrite = true
mat.mainPass.twoSided = true
mat.mainPass.cullMode = CullMode.Back
mat.mainPass.cullMode = CullMode.Front
mat.mainPass.cullMode = CullMode.None
Render Order
Objects render in ascending renderOrder. Lower numbers render first (farther back):
meshVisual.renderOrder = 0
meshVisual.renderOrder = 100
meshVisual.renderOrder = 9999
mat.mainPass.depthTest = false
Texture Assignment at Runtime
remoteMediaModule.loadResourceAsImageTexture(resource, (texture: Texture) => {
mat.mainPass.baseTex = texture
}, print)
mat.mainPass.baseTex = myRenderTarget.getTexture()
mat.mainPass.baseTex = null
Render Targets
A RenderTarget lets you render one camera's view into a texture, which another material can then use. This is the basis for post-processing, mirrors, and portals.
Setup in Lens Studio
- Create a Render Target asset (Asset Browser → + → Render Target).
- Assign it to a Camera component's Render Target field.
- The camera renders into this texture each frame.
- Assign the texture to another material's
baseTex.
Scripting
@input renderTarget: RenderTarget
@input displayMesh: RenderMeshVisual
onAwake(): void {
this.createEvent('OnStartEvent').bind(() => {
const mat = this.displayMesh.material.clone()
this.displayMesh.material = mat
mat.mainPass.baseTex = this.renderTarget.getTexture()
})
}
Render target formats
| Format | Use |
|---|
RGBA8 | Standard colour + alpha (default) |
R11G11B10F | HDR without alpha — VFX, bloom |
Depth | Depth-only — shadow maps, depth effects |
Material Variants
The graph-based material editor lets you create variants — instances of a graph with different parameter values, without code. In script you select between them by switching which material is assigned to the mesh visual:
@input materialA: Material
@input materialB: Material
function switchToVariant(variant: 'a' | 'b'): void {
meshVisual.material = (variant === 'a' ? this.materialA : this.materialB).clone()
}
Graph-Based Material Editor
Lens Studio's material editor uses a node graph (similar to Blender's shader nodes or Unreal's Material Editor):
- Inputs (leftmost): Vertex attributes (position, normal, UV, color), time, textures
- Math nodes: Add, Multiply, Lerp, Remap, Noise, Abs …
- Texture nodes: Sample Texture 2D, Gradient, Render Target
- Output (rightmost): connects to
baseColor, emissive, opacity, normal, metallic, roughness
Exposing a graph parameter to script
- Right-click a value node in the graph → Create Input Property
- Give it a name (e.g.,
"tintColor")
- In script, access it via
mat.mainPass.<propertyName>:
mat.mainPass.tintColor = new vec4(1, 0.2, 0.2, 1)
mat.mainPass.scroll = getTime() * 0.5
Colour Lerp / Animated Materials
const updateEvent = this.createEvent('UpdateEvent')
updateEvent.bind(() => {
const t = (Math.sin(getTime() * 2) + 1) * 0.5
const colA = new vec4(1, 0.2, 0, 1)
const colB = new vec4(0, 0.5, 1, 1)
mat.mainPass.baseColor = new vec4(
colA.r + (colB.r - colA.r) * t,
colA.g + (colB.g - colA.g) * t,
colA.b + (colB.b - colA.b) * t,
1.0
)
})
Screen-Space / Post-Processing Pattern
To apply a full-screen effect:
- Render the world camera into Render Target A.
- Apply Render Target A as a texture to a full-screen quad.
- That quad uses a custom shader graph for the effect (color grade, distort, blur, etc.).
World Camera → RenderTarget A
↓
Full-Screen Quad (Post FX Shader) → Output Camera
Common Gotchas
- Always clone before modifying — forgetting this is the #1 material bug, since all objects using the same asset change together.
blendMode must match opacity usage — if blendMode is Normal but opacity is 0.5, the surface may not sort correctly. For transparent surfaces, also set depthWrite = false.
- Graph parameter names are case-sensitive —
mat.mainPass.TintColor and mat.mainPass.tintColor are different.
- Render target format must match what the sampling shader expects — use
RGBA8 for general textures, R11G11B10F for HDR pipelines.
- Render order and transparency: transparent materials (alpha blend) must have higher
renderOrder than opaque objects behind them, otherwise sorting artifacts occur.
twoSided vs cullMode — twoSided = true is shorthand for cullMode = CullMode.None; use either but not both.