Skip to content

Mirror reflection is captured from the local player camera but sampled in screen space per rendering camera #896

@minetake01

Description

@minetake01

Describe the bug?

Basis mirrors look correct from the local player camera, but the reflection becomes incorrect when the same mirror is viewed from the Unity Scene View or another non-player camera (for example, a handheld capture camera).

This is an architectural mismatch, not a one-off rendering glitch.

BasisSDKMirror updates the reflection render texture from a fixed source camera (the local player camera), while the mirror shader samples that texture in screen space using UVs derived from whichever camera is currently drawing the mirror mesh.

That combination only works when the viewing camera and the capture camera are effectively the same. The player camera happens to satisfy that condition. Scene View, handheld cameras, and other runtime cameras do not.

Technical summary

  1. Capture path: BasisSDKMirror subscribes to Application.onBeforeRender and renders portal cameras using BasisLocalCameraDriver.Instance.Camera as the source.
  2. Sampling path: Mirror shaders (for example TransparentMirror.shader and the examples mirror Shader Graph) use screen-space UVs from positionNDC / Screen Position to sample _ReflectionTexLeft / _ReflectionTexRight.
  3. Result: The texture contains a reflection for camera A, but camera B samples it with B's screen coordinates → offset, stretching, or other visible mismatch.

In URP, Application.onBeforeRender is also a weak hook for this use case because it does not identify which camera is about to render. URP's RenderPipelineManager.beginCameraRendering (or an equivalent per-camera callback such as OnWillRenderObject) is the appropriate integration point.

Existing partial behavior

There is already a limited editor fallback in BasisSDKMirror.OnBeforeRender:

  • If the local player camera is unavailable, Scene View's camera may be used instead.
  • In Play Mode, while the player camera exists, Scene View does not get its own reflection update.

So this issue is most obvious when debugging in Play Mode with both Game View and Scene View active, but it is not limited to the editor.

Related concerns

If mirror rendering is moved to a per-camera callback, mirror reflection cameras must be reliably distinguishable from normal scene cameras. Otherwise, multiple mirrors can recursively trigger or interfere with each other when their reflection cameras are processed.

Today, external mirror implementations can only work around this by checking camera name prefixes such as MirrorCam_. Camera names are not a stable API surface.

BasisCullingCameraRegistry is unrelated: it exists for auxiliary camera culling (for example jiggle), not for identifying mirror reflection cameras.

Steps to Reproduce

  1. Add a Basis SDK mirror to a scene (for example the BasisSDKMirror example prefab or net.minetake.basis.transparent-mirror).
  2. Enter Play Mode.
  3. View the mirror from the local player camera (Game View).
  4. Observe that the reflection appears correct.
  5. Without stopping Play Mode, view the same mirror from the Unity Scene View, or from another runtime camera that is not the local player camera.
  6. Observe that the reflection no longer matches that camera's viewpoint (offset / misaligned / otherwise incorrect).
  7. (Optional, recursion case) Add more than one mirror, or add a custom mirror implementation that renders per camera.
  8. Observe that mirror reflection cameras must be excluded from mirror rendering to avoid recursive rendering or cross-mirror interference. Today this requires fragile MirrorCam_ name-prefix checks.

The expected behavior

Mirrors should render a coherent reflection for the camera that is actually viewing them.

If a mirror is visible from the player camera, Scene View, a handheld camera, or any other runtime camera, the reflection texture should be generated from that same source camera before the mirror is drawn.

Suggested priority

Priority Scope
Must Local player camera and other gameplay/runtime cameras that render the mirror (for example handheld capture)
Should Scene View in Play Mode (developer debugging workflow)
Could Editor-only optimizations (lower resolution / reduced update rate for Scene View)

Possible fix direction

Update mirror reflections from the camera that is about to render the mirror, for example during RenderPipelineManager.beginCameraRendering, or via OnWillRenderObject on the mirror renderer (update only when visible to that camera).

To support that safely, Basis should also provide a stable way to identify mirror reflection cameras, for example:

if (BasisMirrorReflectionCameraRegistry.IsReflectionCamera(camera))
{
    return;
}

or a public marker component on reflection cameras:

// Added automatically to portal cameras created by BasisSDKMirror
public sealed class BasisMirrorReflectionCamera : MonoBehaviour { }

This would allow SDK mirrors and third-party mirror implementations to skip mirror reflection cameras without relying on object name prefixes.

Additional implementation notes

  • Mirror rendering currently coordinates with avatar head visibility via BasisLocalAvatarDriver.ScaleHeadToNormal() / ScaleheadToZero() in onBeforeRender. Any per-camera migration must preserve correct head visibility in reflections and in the main view (see also BasisLocalCameraDriver.BeginCameraRendering).
  • Portal cameras are created with enabled = false and rendered via UniversalRenderPipeline.SubmitRenderRequest, which helps avoid some recursion paths, but explicit reflection-camera identification is still needed once mirror updates become per viewing camera.
  • Performance safeguards (for example update only when the mirror is visible to the current camera, optional frame skipping) should be considered if all active cameras trigger mirror updates.

Screenshots

No response

Build Info

Version: 2.0.1
Unity: 6000.4.11f1
Platform: WindowsEditor
Mode: Desktop
Build GUID: 00000000000000000000000000000000

Log Files

N/A

Additional Context

Basis mirrors follow the same general approach as VRChat's VRCMirrorReflection: a hidden reflection camera renders into a texture, and the mirror shader samples it in screen space.

VRChat's mirror clear-flag option "From Reference Camera" implies that the mirror is rendered relative to the camera drawing the mirror plane. Basis currently breaks that assumption by always capturing from the local player camera.

VRChat also exposes shader globals such as _VRChatMirrorMode and _VRChatMirrorCameraPos for mirror-aware rendering. Basis has no equivalent public surface today.

Note: Mirror reflection camera identification prevents recursion, but may not be enough long-term. Consider a small official camera capability registry (separate from BasisCullingCameraRegistry) for viewing cameras such as handheld capture and Scene View.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions