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:
How can I have my Camera not render specific world space pixels?
Answer
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:
And here it is using the triangle aborting trick above to remove some of the excess geometry outside the clipping volume:
No comments:
Post a Comment