How to make a VR red dot gun sight in unity

So you want to make a VR shooter and you realized that generic gun sights don’t work that well in VR. The red dot sight is much better for VR since it is a lot easier to use especially if you are like me an have a Windows MR headset where you sometimes have to deal with twitchy tracking. The one above works by using the stencil buffer to make a red sphere visible only when viewed through the sights.

The Stencil Buffer

put simply the stencil buffer stores a number for each pixel on the screen with the default being zero. Using custom shaders we can check and set these numbers. The sights work in two steps: First, the mesh representing the glass in the sights is set to render before anything else, and when it does it sets the numbers in the buffer representing the pixels it is covering equal to one. Second, when the ball renders it checks every pixel it will render two and if they are not set to one It doesn’t render, making it invisible, but if they are equal to one it does render and it thus becomes visible.

The Shaders

I make this work by using two custom shaders, the first sets the stencil buffer to 1, and the second reads it and decides whether or not to render.

Here Is the First:

Shader "Custom/MakeItOne" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallicout("Metallic", Range(0,1)) = 0.0
	}
	SubShader {
                //START OF CUSTOM CODE
               // make sure "Queue" = "Geometry-1" "ForceNoShadowCasting" = "True" are set, the first makes it so that it is renderd first, the scond makes it so it doesn't cast shadows
		Tags { "RenderType"="Opaque" "Queue" = "Geometry-1" "ForceNoShadowCasting" = "True" }
		ColorMask 0//makes this object invisable 
		ZWrite off

                Stencil{
			Ref 1//says we are using 1 you can use any number
			Comp always//always compleats action regardless of Stencil value
			Pass replace//sets stencil buffer two the Ref number
                }

                //END OF CUSTOM CODE

		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows
                // Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0
                sampler2D _MainTex;

                struct Input {
			float2 uv_MainTex;
		};
                
                half _Glossiness;
		half _Metallic;
		fixed4 _Color;

                // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
		// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
		// #pragma instancing_options assumeuniformscaling
		UNITY_INSTANCING_BUFFER_START(Props)
			// put more per-instance properties here
		UNITY_INSTANCING_BUFFER_END(Props)

                void surf (Input IN, in			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

And Here is the second:

Shader "Custom/ShowIfOne" {
	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
	}
	SubShader {
               //START OF CUSTOM CODE
		Tags { "RenderType"="Opaque" "ForceNoShadowCasting" = "True" }
		LOD 200
		Stencil{
			Ref 1//sets refrence to one
			Comp Equal//checks if the Ref and Stencil buffer are the same if set to Notequal the shader will do the oppisite function
			Pass Keep//Keeps the render for the pixel but only if the Stencil and Ref are the same
                }
                //END OF CUSTOM CODE
		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows
		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		half _Glossiness;
		half _Metallic;
		fixed4 _Color;

		// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
		// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
		// #pragma instancing_options assumeuniformscaling
		UNITY_INSTANCING_BUFFER_START(Props)
			// put more per-instance properties here
		UNITY_INSTANCING_BUFFER_END(Props)

		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}


To use the Shaders, create a new material and drag the shaders onto the material, then use the material on any object you want.

To make the Scope above, just make a sight out of a cylinder assign the first material to it, then make a sphere and place it about 6 meters from the front of the sight and assign the second material and there you go!

Liked it? Take a second to support WireWhiz on Patreon!
Become a patron at Patreon!