N64 3 Point Filtering

DKO’s 3point GLSL shader
https://www.shadertoy.com/view/Ws2fWV

Be sure your texture import is set to “Nearest Neighbor”

This shader emulates the N64 filtering style by filtering the texture based on the 3 points of a triangle instead of the traditional bilinear “square” filtering.

Shader code
shader_type spatial;
uniform bool flip = false;
uniform sampler2D albedo : filter_nearest;
// DKO's 3point GLSL shader
// https://www.shadertoy.com/view/Ws2fWV
// Be sure your texture import is set to "Nearest Neighbor"
vec2 norm2denorm(sampler2D tex, vec2 uv)
{
    return uv * vec2(textureSize(tex, 0)) - 0.5;
}

ivec2 denorm2idx(vec2 d_uv)
{
    return ivec2(floor(d_uv));
}

ivec2 norm2idx(sampler2D tex, vec2 uv)
{
    return denorm2idx(norm2denorm(tex, uv));
}

vec2 idx2norm(sampler2D tex, ivec2 idx)
{
    vec2 denorm_uv = vec2(idx) + 0.5;
    vec2 size = vec2(textureSize(tex, 0));
    return denorm_uv / size;
}

vec4 texel_fetch(sampler2D tex, ivec2 idx)
{
    vec2 uv = idx2norm(tex, idx);
    return texture(tex, uv);
}


/*
 * Unlike Nintendo's documentation, the N64 does not use
 * the 3 closest texels.
 * The texel grid is triangulated:
 *
 *     0 .. 1        0 .. 1
 *   0 +----+      0 +----+
 *     |   /|        |\   |
 *   . |  / |        | \  |
 *   . | /  |        |  \ |
 *     |/   |        |   \|
 *   1 +----+      1 +----+
 *
 * If the sampled point falls above the diagonal,
 * The top triangle is used; otherwise, it's the bottom.
 */

vec4 texture_3point(sampler2D tex, vec2 uv)
{
    vec2 denorm_uv = norm2denorm(tex, uv);
    ivec2 idx_low = denorm2idx(denorm_uv);
    vec2 ratio = denorm_uv - vec2(idx_low);


    bool lower_flag;
    ivec2 corner0;
    ivec2 corner1;
    ivec2 corner2;
	if(flip){
	    // this uses one diagonal orientation
	    // using conditional, might not be optimal
	    lower_flag = 1.0 < (ratio.x + ratio.y);
	    corner0 = lower_flag ? ivec2(1, 1) : ivec2(0, 0);
	    corner1 = ivec2(0, 1);
	    corner2 = ivec2(1, 0);
	} else{
	    // orient the triangulated mesh diagonals the other way
	    lower_flag = (ratio.x - ratio.y) > 0.0;
	    corner0 = lower_flag ? ivec2(1, 0) : ivec2(0, 1);
	    corner1 = ivec2(0, 0);
	    corner2 = ivec2(1, 1);
	}
    ivec2 idx0 = idx_low + corner0;
    ivec2 idx1 = idx_low + corner1;
    ivec2 idx2 = idx_low + corner2;

    vec4 t0 = texel_fetch(tex, idx0, uv);
    vec4 t1 = texel_fetch(tex, idx1, uv);
    vec4 t2 = texel_fetch(tex, idx2, uv);

    // This is standard (Crammer's rule) barycentric coordinates calculation.
    vec2 v0 = vec2(corner1 - corner0);
    vec2 v1 = vec2(corner2 - corner0);
    vec2 v2 = ratio   - vec2(corner0);
    float den = v0.x * v1.y - v1.x * v0.y;
    /*
     * Note: the abs() here is necessary because we don't guarantee
     * the proper order of vertices, so some signed areas are negative.
     * But since we only interpolate inside the triangle, the areas
     * are guaranteed to be positive, if we did the math more carefully.
     */
    float lambda1 = abs((v2.x * v1.y - v1.x * v2.y) / den);
    float lambda2 = abs((v0.x * v2.y - v2.x * v0.y) / den);
    float lambda0 = 1.0 - lambda1 - lambda2;

    return lambda0*t0 + lambda1*t1 + lambda2*t2;
}

void fragment(){
    vec4 c = texture_3point(albedo, UV);
    ALBEDO = c.rgb;
}
Live Preview
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.

More from granitrocky

Related shaders

guest

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Purps
Purps
8 months ago

how does one use this?

BlueBrain
BlueBrain
7 months ago

This shader appears to be broken, there are a few references to variables that are never initiated…

flip on line 61 and r on lines 79, 80 and 81