lens-studio-materials-shaders
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')
// BAD: modifies the shared asset, affecting all objects
meshVisual.material.mainPass.baseColor = new vec4(1, 0, 0, 1)
// GOOD: clone creates a per-instance copy
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
// Solid color (RGBA, each component 0–1)
mat.mainPass.baseColor = new vec4(0.2, 0.8, 0.4, 1.0)
// Transparency (0 = fully transparent, 1 = fully opaque)
mat.mainPass.opacity = 0.5
// Texture assignment
mat.mainPass.baseTex = myTexture
// Emissive / glow strength
mat.mainPass.emissiveColor = new vec4(1.0, 0.5, 0.0, 1.0)
// Metallic / roughness (PBR materials only)
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 // standard alpha blend (default)
mat.mainPass.blendMode = BlendMode.Add // adds colour — fire, glow, neon
mat.mainPass.blendMode = BlendMode.Screen // lightens only — soft glow
mat.mainPass.blendMode = BlendMode.Multiply // darkens — shadow, colour grading
mat.mainPass.blendMode = BlendMode.AlphaToCoverage // MSAA-friendly transparency
Use
BlendMode.Addfor particles and VFX that should glow over the scene. UseBlendMode.Normalwithopacityfor standard transparent surfaces.
Depth & Cull Settings
// Depth test: should this surface check if something is in front of it?
mat.mainPass.depthTest = true // default; disable for UI-always-on-top
mat.mainPass.depthWrite = true // writes this surface to the depth buffer; disable for transparent surfaces
// Show both sides of the mesh (no back-face culling)
mat.mainPass.twoSided = true
// Manual cull mode
mat.mainPass.cullMode = CullMode.Back // default (cull back-faces)
mat.mainPass.cullMode = CullMode.Front // cull front-faces (inverted normals)
mat.mainPass.cullMode = CullMode.None // same as twoSided = true
Render Order
Objects render in ascending renderOrder. Lower numbers render first (farther back):
meshVisual.renderOrder = 0 // renders before renderOrder = 1
meshVisual.renderOrder = 100 // renders in front of most objects
// For UI / always-on-top objects:
meshVisual.renderOrder = 9999
mat.mainPass.depthTest = false
Texture Assignment at Runtime
// Assign a texture loaded from a RemoteMediaModule call
remoteMediaModule.loadResourceAsImageTexture(resource, (texture: Texture) => {
mat.mainPass.baseTex = texture
}, print)
// Use a RenderTarget texture as a material input (render pipeline chaining)
mat.mainPass.baseTex = myRenderTarget.getTexture()
// Clear / reset to default texture
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
// Use the render target's texture on a display surface
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 // variant 1
@input materialB: Material // variant 2
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>:
// If you exposed 'tintColor' as a vec4 input:
mat.mainPass.tintColor = new vec4(1, 0.2, 0.2, 1)
// If you exposed 'scroll' as a float:
mat.mainPass.scroll = getTime() * 0.5 // animate scroll UV in every frame
Colour Lerp / Animated Materials
// Animate material color between two values using time
const updateEvent = this.createEvent('UpdateEvent')
updateEvent.bind(() => {
const t = (Math.sin(getTime() * 2) + 1) * 0.5 // oscillates 0→1
const colA = new vec4(1, 0.2, 0, 1)
const colB = new vec4(0, 0.5, 1, 1)
// Manual lerp: a + (b - a) * t
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.
blendModemust match opacity usage — ifblendModeisNormalbutopacityis 0.5, the surface may not sort correctly. For transparent surfaces, also setdepthWrite = false.- Graph parameter names are case-sensitive —
mat.mainPass.TintColorandmat.mainPass.tintColorare different. - Render target format must match what the sampling shader expects — use
RGBA8for general textures,R11G11B10Ffor HDR pipelines. - Render order and transparency: transparent materials (alpha blend) must have higher
renderOrderthan opaque objects behind them, otherwise sorting artifacts occur. twoSidedvscullMode—twoSided = trueis shorthand forcullMode = CullMode.None; use either but not both.