Eyap
Eyap

Reputation: 694

How to write dynamically to whole stencil buffer in Unity

What do I want to achieve ?

I'd like to achieve an effect in Unity3D, where I superpose a few cameras on top of each other. Each cameras would draw to a specific area of the screen. If possible, I'd like these areas to change dynamically. I am using unity (latest version), and URP.

How technically I see it :

For implementation and performances reasons, it seems writing to the stencil buffer is the way to go. That way, I can only render what part of the screen I want for each camera. It is also quite easy once the stencil is made, cause the ForwardRendering settings in Unity offer such capabilities out of the box.

What I can't figure out :

The problem is, I don't know to efficiently write to the whole stencil buffer (each frame). The best way would be to use a compute shader (or maybe a simple script), that directly write the values after some calculations. Is there a way for that ? If yes, How ?

Another alternative may be to use a transparent quad in front of one of each camera, and to write to the stencil buffers like that. But 1) It seems there exist a SV_StencilRef keyword in the fragment buffer, but not supported by Unity yet ? 2) I will still lose performance nevertheless.

Thanks for any help / ideas about how to tackle this problem.

Edit (Clarification) : I'd like to be able to render free shapes, and not only rects, which prevent the use of the standard ViewportRect. After some search, I found the Voronoi split screen to be quite similar (with a technical view) to what I'd like to achieve (See here)

Upvotes: 3

Views: 846

Answers (2)

h4ri
h4ri

Reputation: 369

If I understand correctly, you only need to play with the different camera Viewport Rect (https://docs.unity3d.com/ScriptReference/Camera-rect.html) to determine what camera should render what part of the screen.

Response to comment: no, it's not stretched. Here is an example with four cameras:

Create a scene with four cameras, add this script to it and add the cameras to the array on the script. I added the _movingObject just to see something moving, but it's not necessary.

using UnityEngine;

public class CameraHandler : MonoBehaviour
{
    [SerializeField] private Transform _movingObject;
    [SerializeField] private float _posMod = 10.0f;

    [SerializeField] private float _cameraPosMod = 0.1f;
    [SerializeField] private Camera[] _cameras;

    private void Update()
    {
        float t = Time.time;
        float x = Mathf.Sin(t);
        float y = Mathf.Cos(t);

        if (_movingObject) _movingObject.position = new(x * _posMod, 1.0f, y * _posMod);

        Vector2 center = new(0.5f + x * _cameraPosMod, 0.5f + y * _cameraPosMod);

        // bottom left camera
        _cameras[0].rect = new(0.0f, 0.0f, center.x, center.y);

        // bottom right camera
        _cameras[1].rect = new(center.x, 0.0f, 1.0f - center.x, center.y);

        // upper left camera
        _cameras[2].rect = new(0.0f, center.y, center.x, 1.0f - center.y);

        // upper right camera
        _cameras[3].rect = new(center.x, center.y, 1.0f - center.x, 1.0f - center.y);
    }
}

Upvotes: 3

derHugo
derHugo

Reputation: 90813

Not exactly an answer to your question about stencil buffer but I had a (hopefully) similar use case recently.

The main issue: In the URP Camera stack

  • If your camera is set to Base it will overdraw the entire screen
  • you can not adjust the Viewport on any Overlay camera

You can actually try to set the viewport via code -> result your camera renders only the correct part of the scene ... but it gets stretched to the entire screen ^^


What I did in the end was

  • Leave all content and cameras at the origin position

  • Apply according masks to filter the content per camera

  • Make your camera Overlay (as usual)

  • go through a custom Camera.projectionMatrix

    m_Camera.projectionMatrix = Matrix4x4.Translate(projectionOffset) * Matrix4x4.Perspective(m_Camera.fieldOfView, m_Camera.aspect, m_Camera.nearClipPlane, m_Camera.farClipPlane);
    

    where the projectionOffset is an offset in viewport space (normalized 0 - 1) from the bottom left corner.

    For example in my case I wanted a minimap at 400, 400 pixels from the top-right corner so I did

    var topRightOffsetPixels = new Vector2(400, 400);
    var topRightOffsetViewport = Vector2.one - new Vector2(topRightOffsetPixels.x * 2 / Screen.width, topRightOffsetPixels.y * 2 / Screen.height);
    m_Camera.projectionMatrix = Matrix4x4.Translate(topRightOffsetViewport) * Matrix4x4.Perspective(m_Camera.fieldOfView, m_Camera.aspect, m_Camera.nearClipPlane, m_Camera.farClipPlane);
    

See also Matrix4x4.Perspective

Upvotes: 2

Related Questions