2
\$\begingroup\$

I am making a VR app in Unity. I have a giant map that I want to display on a virtual table. The map is too large to fit on the table, and I cannot change its size. (It is a third party asset, and does not easily have that capability.)

How can I tell the camera not to render the parts of the map that fall off of the table? Perhaps something along the lines of:

Camera.DoNotRender(new Vector3(100, 0, 0));

I have been looking into using some MonoBehaviour messages such as OnPreRender, OnRenderObject, etc. But I do not know which one can help me, if any. Ideally, the pixels behind the non-rendered pixels should be rendered, but that is not my priority.

The map is made up of 100s of little renderers:

enter image description here

How can I have my Camera not render specific world space pixels?

\$\endgroup\$
  • \$\begingroup\$ Is that map asset a 3d mesh or a 2d texture? \$\endgroup\$ – Philipp Apr 10 '17 at 13:28
  • \$\begingroup\$ @Philipp A Map Asset. Made up of 100s of little parts, each with their own renderer. assetstore.unity3d.com/en/#!/content/68889 \$\endgroup\$ – Evorlor Apr 10 '17 at 14:06
  • 1
    \$\begingroup\$ The way you'll do this will likely depend on how the asset is being rendered. What kinds of rendering components are in play, meshes/sprites/other? Is it using a custom shader or multiple materials? A picture is often worth a thousand words when asking about graphics problems. \$\endgroup\$ – DMGregory Apr 10 '17 at 14:06
  • \$\begingroup\$ @DMGregory Edited. It is made up of 100s of little renderers. I think they are all primitives. \$\endgroup\$ – Evorlor Apr 10 '17 at 14:08
  • \$\begingroup\$ Render it to a texture, then mask that texture with the outline of the table, rendering that to the screen. Exercise for the reader: determine the edge of the table. \$\endgroup\$ – Draco18s Apr 10 '17 at 14:26
4
\$\begingroup\$

One way to do this is with the clip function in a shader, which aborts rendering of a pixel if it fails a particular condition.

This lets you create custom-shaped clipping regions, but it has a downside: by the time you reach the clip test, most of the work of rasterizing the object is already done, so you end up paying a significant amount for the invisible portions of the model that don't actually get drawn in the end.

Be sure to profile this to determine whether it's an issue in your particular application - it might not be. If it is, there are some additional tricks you can use with depth, stencil, and off-screen buffers to exclude some of the redundant geometry.


Edit: a neat trick I've just learned is if you introduce a NaN or infinity into a vertex position, you can abort rendering of triangles using that vertex. So this can let you do some of this clipping per-vertex instead of solely per fragment, saving some rasterization costs.

v.vertex.x /= step(outOfBounds, 0.1f);

You need a margin like that 0.1f there that's bigger than your typical triangle - otherwise you can have a vertex outside your clipping margin abort a whole triangle that's partly inside the clipping volume.

Note that this doesn't seem to be an officially documented feature, just something GPUs tend to do 'cause how else are they going to render that triangle? ;)


Here's an example shader using this approach - use something like this on your map material to achieve the clipping:

Shader "Custom/ClipVolume" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0

        // Expose parameters for the minimum x, minimum z,
        // maximum x, and maximum z of the rendered volume.
        _Corners ("Min XZ / Max XZ", Vector) = (-1, -1, 1, 1)
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        // Allow back sides of the object to render.
        Cull Off

        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
            float3 worldPos;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Read the min xz/ max xz material properties.
        float4 _Corners;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            // Calculate a signed distance from the clipping volume.
            float2 offset;
            offset = IN.worldPos.xz - _Corners.zw;
            float outOfBounds = max(offset.x, offset.y);
            offset = _Corners.xy - IN.worldPos.xz;
            outOfBounds = max(outOfBounds, max(offset.x, offset.y));
            // Reject fragments that are outside the clipping volume.
            clip(-outOfBounds);

            // Default surface shading.
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    // Note that the non-clipped Diffuse material will be used for shadows.
    // If you need correct shadowing with clipped material, add a shadow pass
    // that includes the same clipping logic as above.
    FallBack "Diffuse"
}

Here's what it looks like on Unity's "Ethan" model:

Example of using the shader above to clip a character mesh

And here it is using the triangle aborting trick above to remove some of the excess geometry outside the clipping volume:

Example showing cropping of the underlying geometry

\$\endgroup\$
1
\$\begingroup\$

Using Stencils is another simple option

Stencil lets you use a "mask" to define where the shader is rendered. This is incredibly flexible and easy to modify in the editor.

here's unity's documentation

https://docs.unity3d.com/Manual/SL-Stencil.html

To use it, you'll need to put this in the "mask" shader

Stencil{
    Ref 1
    comp always
    Pass replace
}

and then this in the map render shader

Stencil{
    Ref 1
    comp Equal
    Pass keep
}

This way only pixels viewed "through" the mask will be rendered.

\$\endgroup\$
  • 1
    \$\begingroup\$ Note that this masking is applied in screen space, rather than worldspace. This can give somewhat odd results in VR, as a bit of content that's drawn for the left eye might, through parallax, find itself outside the stencil when viewed from the right eye. This is usually OK if there's something solid rendered in front of the stencil edge to explain the occlusion (imagine looking out a window frame, or seeing the edge of a screen playing a 3D movie "behind" it), but if the stencil is just hanging in mid-air, or "behind" the clipped content, this can be confusing for the eyes to try to focus on. \$\endgroup\$ – DMGregory Apr 12 '17 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.