XR-Safe Matcap

Most matcap shaders directly face the camera- as they should. But sometimes the trick starts becoming very noticable. In stereo, a lot of simple matcap techniques look like ‘glued on textures’.

This shader aims to work around this issue by reflecting the vertical axis into world-space (and optionally, the horizontal axis too). This is heavily inspired by VRChat’s matcap shader for Quest avatars.

This shader works just fine on flat screens too.

Shader code
shader_type spatial;
render_mode unshaded; // Remove this line to apply lighting.

	'Matcap' is the matcap texture.
	'Margin' reduces the size of the final UV, to avoid seams.
	'Is Spatial' allows horizontal wrapping.
uniform sampler2D matcap: source_color, hint_default_black;
uniform float margin: hint_range(0.0, 0.099) = 0.008;
uniform bool is_spatial = false;

	World-space normal, position, and eye position.
varying vec3 v_world_normal;
varying vec3 v_world_pos;
varying vec3 v_eye_pos;

// Up direction.
const vec3 up = vec3(0.0, 1.0, 0.0);

void vertex() {
		Set varyings into place;
	v_world_normal = (MODEL_NORMAL_MATRIX * NORMAL);
	v_world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
	v_eye_pos = (INV_VIEW_MATRIX * vec4(EYE_OFFSET, 1.0)).xyz;

void fragment() {
		Fix the normal, and calculate the direction from the eye position.
		Then calculate the reflection based on both of these.
		All of this in world space, for convenience.
	vec3 normal = normalize(v_world_normal);
	vec3 view_dir = normalize(v_eye_pos - v_world_pos);
	vec3 reflection = reflect(view_dir, normal);
		Find the horizontal and vertical target direction.
		Differs if we're going spatial.
	float vert = dot(reflection, up);
	float hor;
	if (is_spatial) {
		hor = cross(reflection, up).z;
	} else {
		hor = cross(reflection, normal).y;
		Calculate the UV based on the horizontal and vertical direction.
		Account for the margin.
	vec2 uv = vec2(hor, vert) * (1.0 - margin);
	uv = 0.5 + 0.5 * uv;
		Sample the texture and finish up.
	vec4 color = texture(matcap, uv);
	ALBEDO = color.rgb;
	// ALPHA = color.a;
matcap, reflective, Stereo, VR, XR
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders


Matcap Shader

Notify of

Inline Feedbacks
View all comments