A system to bake dynamic lights and shadows into lightmaps at runtime within Unity's Universal Render Pipeline (URP).
This tool is designed for scenarios where lighting conditions can change dynamically, but the scene geometry remains static, allowing for high-quality baked lighting without the performance cost of fully dynamic lights.
| Lighting Scene | Baked Lightmap Texture |
|---|---|
![]() |
![]() |
The system is composed of two main scripts that work together to perform the baking process.
This is the main MonoBehaviour component that you add to your scene. It orchestrates the entire baking process.
- Target Management: It holds a list of
Renderercomponents that you want to bake lighting for. - Resource Management: For each target renderer, it creates and manages a dedicated
RenderTexturewhich will become its lightmap. - Baking Isolation: It uses a clever trick with
RenderingLayerMaskto isolate each target object. Before baking, it assigns a unique rendering layer to a target renderer and configures the scene's lights to only affect that layer. This ensures that only one object is drawn into its lightmap at a time. - Command Queueing: It prepares all the necessary information for each target (the renderer, its lightmap texture, and its unique layer mask) and enqueues this data into the
RealTimeLightBakerFeaturefor processing within the URP render loop. - Material Texture Passthrough: Before each bake pass, it inspects the target renderer’s material for
_BaseMap,_BumpMap, and_SpecGlossMap. When found, those textures (and their tiling/offset values) are pushed to the baking pipeline so the bake material renders with the same surface inputs the target uses at runtime. - Finalization: After the feature has finished baking, this script handles applying the newly baked lightmap texture to the target's material properties.
This is a ScriptableRendererFeature that integrates the baking process directly into URP's rendering pipeline.
- Pipeline Injection: It adds a custom
ScriptableRenderPass(BakePass) to the pipeline at a specified event (e.g., after shadows are rendered). - Bake Execution: The
BakePassreceives the list of targets from theRuntimeLightmapBaker. For each target, it performs the following steps using the Render Graph API:- It sets the render target to the appropriate
RenderTexture. - It creates a
RendererListthat contains only the single object to be baked, filtered by the uniqueRenderingLayerMaskprovided by the "Conductor". - It draws the object using a special override material (
UVRuntimeBakerURP.shader).
- It sets the render target to the appropriate
- Cleanup: After all targets are baked, it calls back to the
RuntimeLightmapBakerto restore all the temporary changes made to renderers and lights.
The baking process relies on a few key shaders.
This is the core shader responsible for the baking itself. It doesn't render the object to the screen, but rather "unwraps" it into a 2D texture.
- Vertex Shader: The key operation happens here. Instead of transforming the mesh vertices into camera clip space, it uses the object's lightmap UV coordinates (
TEXCOORD1or uv2) to lay the mesh flat in clip space. The result is that the object's surface is drawn across the render texture, guided by its UV layout. - Fragment Shader: For each pixel on this unwrapped surface, the fragment shader calculates the full URP lighting, including contributions from the main light, additional lights, and their shadows. The final computed light and shadow color is written to the render texture, which effectively becomes the object's lightmap.
- Supplied Global Textures: At bake time the pipeline sets the following global textures (and accompanying scale/offset vectors) so the shader can faithfully reproduce the target material:
_RTLB_BaseMap/_RTLB_BaseMap_ST– main surface texture._RTLB_BumpMap/_RTLB_BumpMap_ST– normal map (falls back toTexture2D.normalTexturewhen absent)._RTLB_SpecGlossMap/_RTLB_SpecGlossMap_ST– specular/smoothness map (falls back to black). Custom bake shaders should declare matchingTEXTURE2D/SAMPLERpairs and sample them using uv0 transformed by the provided ST values.
This is an optional but highly recommended post-processing shader that runs after the main bake pass.
- Purpose: It prevents visual artifacts like black seams that can appear at the edges of UV islands in the lightmap.
- Process: It performs a dilation or "bleed" operation. For each empty pixel (a pixel not covered by the object's UVs), it looks at its neighbors. If it finds a neighbor with baked lighting, it copies that color, effectively bleeding the color outward by a few pixels. This ensures that texture filtering near UV edges has valid data to sample from.
This is a utility shader not directly used in the lightmap baking process itself, but useful in combination with it.
- Function: It's a transparent shader that doesn't render any color or texture, but only receives shadows. It calculates the amount of shadow falling on it and outputs that as a transparent, tinted color.
- Use Case: This is perfect for creating "shadow catcher" planes. You can have a fully baked scene, but place a transparent plane on the ground to receive dynamic shadows from a moving character, blending the dynamic and baked worlds seamlessly.
- Add the Feature: In your project's URP settings, select your
UniversalRendererDataasset and add theRealTimeLightBakerFeatureto the list of "Renderer Features". - Assign Materials: Assign the
UVRuntimeBakerURP.shaderto theBake Materialfield in the feature's settings. If you want to use dilation, enable it and assign theUVDilation.shaderto theDilation Materialfield. - Add the Component: Add the
RuntimeLightmapBakercomponent to a GameObject in your scene. - Set Targets: Assign the
Renderercomponents of the objects you want to bake to the "Targets" list on theRuntimeLightmapBaker. - Check UVs: Ensure that all target objects have a valid second UV channel (UV2) properly unwrapped for lightmapping.
- Configure: Adjust settings like
Bake Every FrameandLightmap Sizeas needed. - Update Material Shader: The baked lightmap is sent to the material via a texture property (default:
_RuntimeLightmap). Your object's material shader must be modified to read this texture and apply it. For example, you could multiply the final albedo color by the lightmap color.
// Example of sampling the runtime lightmap in your shader
half4 runtimeLightmap = SAMPLE_TEXTURE2D(_RuntimeLightmap, sampler_RuntimeLightmap, i.uv2);
finalColor.rgb *= runtimeLightmap.rgb;Authoring custom bake shaders: If you author an alternative to
UVRuntimeBakerURP.shader, declareTEXTURE2D/SAMPLERpairs for_RTLB_BaseMap,_RTLB_BumpMap,_RTLB_SpecGlossMapand read them with UV0 transformed by the associated_RTLB_*_STvectors. These globals mirror the textures assigned to each target renderer at bake time, guaranteeing the bake material receives the same surface inputs users see in-game.


