📚 Documentation (Wiki): https://github.com/saimgulay/Unity-PlanarMirrorReflection/wiki
Self-contained planar reflection system for Unity URP using a secondary reflection Camera + a dedicated planar reflection shader.
- Component:
PlanarMirrorSurface(C#) - Shader:
EndlessOcean/URP/MirrorSurfaceSRP_RayPlane
Designed for clean integration in real projects: visibility gating, frame-skipping, distance culling, RT stability (lock/hysteresis), and an optional player-centric radius mask.
This repository/project is organised as:
Assets/Scripts/PlanarMirrorSurface.cs— main component (reflection camera, RT, MPB pushes)
Assets/Shaders/MirrorSurfaceSRP_RayPlane.shader— planar reflection + URP PBR blend
Assets/Materials/- Mirror materials using the shader
Assets/Scenes/- Example scenes (recommended: keep a small demo scene here)
Assets/Settings/- URP assets / renderer settings (project dependent)
Images/- README screenshots / promo images
- Unity with Universal Render Pipeline (URP) enabled
- A scene camera rendered by URP
- A reflective mesh (plane / quad / any mesh with a renderer)
The system calls
UniversalRenderPipeline.RenderSingleCamera(...)and hooks intoRenderPipelineManager.beginCameraRendering.
-
Add the component
- Add
PlanarMirrorSurfaceto the reflective object (the object must have aRenderer).
- Add
-
Assign the mirror material
- Use shader:
EndlessOcean/URP/MirrorSurfaceSRP_RayPlane - Apply the material to the same renderer as the script.
- Use shader:
-
Layer safety (important)
- Put the mirror object on a dedicated layer (e.g.
Mirror). - In
PlanarMirrorSurface.reflectionCullingMask, exclude that layer to avoid recursive feedback.
- Put the mirror object on a dedicated layer (e.g.
-
Play
- The component creates a hidden reflection camera, renders into a RenderTexture, and pushes:
_MirrorTex,_PlanePosWS,_PlaneNormalWS,_MirrorVP- optional radius mask params:
_PlayerPosWS,_Radius,_RadiusFeather
- The component creates a hidden reflection camera, renders into a RenderTexture, and pushes:
-
On each URP camera render, the component:
- Checks visibility/distance/update-rate.
- Ensures a hidden reflection camera exists.
- Allocates or reuses a RenderTexture (with lock/hysteresis options).
- Reflects the view matrix around the mirror plane.
- Applies an oblique clip plane to remove artifacts.
- Renders the scene once into
_MirrorTex. - Sets per-renderer properties via
MaterialPropertyBlock.
-
The shader:
- Computes a world-space reflection ray and intersects it with the mirror plane.
- Projects the hit point using
_MirrorVPinto reflection UV. - Optionally distorts by normal map.
- Blends planar reflection with URP PBR lighting (LERP / SCREEN / ADD).
- Optionally masks reflection by a player-centric radius disc on the mirror plane.
- Ground_UseUp (default): uses
transform.up(backwards compatible) - Wall_UseForward: uses
transform.forward(vertical mirrors) - CustomNormal: uses
customNormalWS(world space)
resolutionScale(0.25–2.0): reflection RT size relative to the source cameramsaa(1–8): MSAA samples for reflection RT (1 disables)allowHDR: uses HDR RT format when enabled
clipPlaneOffset: pushes clip plane to avoid acnematchSourceProjection: mirrors FOV/ortho parameters from the source camera
reflectionCullingMask: layers rendered in reflectiondisableShadowsInReflection: disables shadow rendering for performance (URP additional camera data)
requireVisibility: only render if mirror renderer is visible to the current cameraupdateEveryNFrames: render frequency (1 = every frame)maxRenderDistance: skip rendering when far away (0 = disabled)
player: transform used as mask centre (null -> world origin)radius: <= 0 disables mask (identical behaviour); > 0 enables disc maskradiusFeather: smooth falloff width
lockRtSize: prevents frequent reallocations by forcing fixed RT sizelockedWidth / lockedHeight: fixed RT dimensions when lockedresizeHysteresis: when unlocked, reallocate only if the resolution change exceeds this ratio
- Tint:
_Tint(RGBA, alpha drives blending) - Base map:
_BaseMap - Normals:
_NormalMap,_NormalScale_UseRGPackedif normal is not imported as Unity normal
- Height/parallax:
_HeightMap,_HeightScale(base only) - AO:
_OcclusionMap,_OcclusionStrength - PBR:
_Metallic,_Smoothness - Reflection:
_Reflectiveness,_FresnelPower_DistortionStrength(normal-based UV warp)_ReflectionBlend(0=LERP, 1=SCREEN, 2=ADD)
- Ambient diffuse (optional):
_AmbientDiffuse(0..1 SH contribution)
_MirrorTex— reflection RT_PlanePosWS,_PlaneNormalWS— mirror plane definition_MirrorVP— reflection camera VP matrix_PlayerPosWS,_Radius,_RadiusFeather— optional radius mask
- Exclude the mirror layer from
reflectionCullingMaskto prevent infinite recursion. - For best performance:
requireVisibility = trueupdateEveryNFrames = 2(or more) for large scenesdisableShadowsInReflection = trueif acceptable- Use
lockRtSizefor stable GPU memory usage (e.g. 1024×1024 / 2048×2048)
- For vertical mirrors (walls), set
planeMode = Wall_UseForward.
- Planar reflections are view-dependent and limited to a plane; this is not SSR.
- Multiple mirrors in view can multiply cost (each creates its own reflection render).
- Very large distortion values can sample outside the reflection RT (clamped wrap mode).
- Transparent objects and certain post effects may not match perfectly (depends on URP configuration).
Reflection is black
- Confirm URP is active and the object uses the provided shader.
- Confirm the reflection camera renders something (culling mask not empty).
- Ensure the mirror object isn’t being rendered in the reflection (exclude layer).
Feedback / “hall of mirrors”
- Exclude the mirror layer from
reflectionCullingMask.
Flickering RT allocations
- Enable
lockRtSizeor increaseresizeHysteresis.
Artifacts near plane
- Adjust
clipPlaneOffsetslightly (e.g. 0.02–0.1).
MIT License. See LICENSE.
Developed by: @saimgulay




