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;
}
Tags
matcap, Material Capture, retro, shading, Spatial, view
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Purpbatboi2i

Camera-Stable Material-Capture Shader

N64 RDP Texture Filtering Operations

Purp’s COMPATIBILITY RENDERER Decal Shader

Related shaders

View-Matcap Based Fake Vertex Lighting

Fake Godrays (Godot 4.x)

Fake Interior Shader

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments