MatCap/View-Based Fake Vertex-Shading
Created using my previous work as reference:
Camera-Stable Material Capture Shader
This is a shader that simulates a light source parented to the camera. It replicates the lighting style of N64-era (Mostly inspired by how Super Mario 64 does it) games where the main character or NPCs are always illuminated, regardless of their orientation to the camera or other lights in the scene.
This emission-based shader ensures your model is always lit from the camera’s viewpoint, acting as a perfect fill light that is fully compatible with your existing scene lighting.
Shader code
shader_type spatial;
render_mode vertex_lighting, depth_draw_opaque, cull_back;
group_uniforms Shader_Settings;
uniform vec3 light_direction = vec3(0.0, -1.0, 0.0);
uniform vec3 light_color : source_color = vec3(1.0, 1.0, 1.0);
uniform vec3 ambient_color : source_color = vec3(0.0, 0.0, 0.0);
uniform float light_strength : hint_range(0.0, 2.0) = 1.0;
uniform float ambient_strength : hint_range(0.0, 1.0) = 0.0;
uniform float terminator_offset : hint_range(0.0, 1.0) = 0.0;
uniform float roughness : hint_range(0.0, 1.0);
uniform bool use_lighten_blend = false;
varying vec3 custom_lighting;
float calc_diffuse(vec3 normal, vec3 light_dir, float offset, float rough) {
float ndotl = dot(normal, light_dir);
float smoothed = mix(ndotl, 0.5, rough);
return clamp(smoothed + offset, 0.0, 1.0);
}
void vertex() {
// Combined view calculations
vec4 view_pos = MODELVIEW_MATRIX * vec4(VERTEX, 1.0);
vec3 view_dir = normalize(-view_pos.xyz);
vec3 view_normal = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
// Alternative orthonormal basis using cross product method
vec3 up_vector = vec3(0.0, 1.0, 0.0);
vec3 right_vector = vec3(1.0, 0.0, 0.0);
// Choose appropriate reference vector based on view direction
vec3 ref_vector = (abs(view_dir.y) > 0.9) ? right_vector : up_vector;
// Create orthonormal basis using Gram-Schmidt process
vec3 tangent = normalize(cross(ref_vector, view_dir));
vec3 bitangent = cross(view_dir, tangent);
// Project normal onto the tangent-bitangent plane
vec2 matcap_coords = vec2(
dot(view_normal, tangent),
dot(view_normal, bitangent)
);
// Fast approximate normalize for the matcap normal
float len_sq = dot(matcap_coords, matcap_coords);
float z = sqrt(max(0.0, 1.0 - len_sq));
vec3 matcap_normal = vec3(matcap_coords, z);
// Precomputed lighting
vec3 light_dir = normalize(-light_direction);
float diffuse_factor = calc_diffuse(matcap_normal, light_dir, terminator_offset, roughness);
// Optimized blending
vec3 ambient_light = ambient_color * ambient_strength;
vec3 fake_diffuse = diffuse_factor * light_color * light_strength;
custom_lighting = use_lighten_blend
? max(ambient_light, fake_diffuse)
: ambient_light + fake_diffuse;
}
void fragment() {
EMISSION = custom_lighting;
}



