N64 RDP Texture Filtering Operations

The main goal of this shader code is to attempt to replicate the distinctive visual characteristics of the Nintendo 64’s Texture filtering:

  • 3-Point Bilinear Sampling
  • “Average” Sampling / “True” Bilinear

ShaderInclude written using angrylion-rdp-plus as a point of reference.
A Simple shader code is included showing how to make use of the filter operations.

ShaderInclude:

ivec2 get_texture_size(sampler2D tex) {
    return textureSize(tex, 0);
}

ivec2 wrap_texel(ivec2 coord, ivec2 size) {
    return ivec2(
        (coord.x % size.x + size.x) % size.x,
        (coord.y % size.y + size.y) % size.y
    );
}

#ifdef FILTER_NONE
vec4 sample_filter(sampler2D tex, vec2 uv) {
    return texture(tex, uv);
}
#endif

#ifdef FILTER_3POINT
vec4 sample_filter(sampler2D tex, vec2 uv) {
    ivec2 texture_size = get_texture_size(tex);
    vec2 pixel_coord_float = uv * vec2(texture_size) - 0.5;
    
    ivec2 texel_coord = ivec2(floor(pixel_coord_float));
    vec2 frag_coord = fract(pixel_coord_float);
    
    vec4 t0 = texelFetch(tex, wrap_texel(texel_coord, texture_size), 0);
    vec4 t1 = texelFetch(tex, wrap_texel(texel_coord + ivec2(1, 0), texture_size), 0);
    vec4 t2 = texelFetch(tex, wrap_texel(texel_coord + ivec2(0, 1), texture_size), 0);
    vec4 t3 = texelFetch(tex, wrap_texel(texel_coord + ivec2(1, 1), texture_size), 0);

    if (frag_coord.x + frag_coord.y < 1.0) {
        return t0 + (t1 - t0) * frag_coord.x + (t2 - t0) * frag_coord.y;
    } else {
        vec2 weights = vec2(1.0 - frag_coord.x, 1.0 - frag_coord.y);
        return t3 + (t2 - t3) * weights.x + (t1 - t3) * weights.y;
    }
}
#endif

#ifdef FILTER_BOX
vec4 sample_filter(sampler2D tex, vec2 uv) {
    ivec2 texture_size = get_texture_size(tex);
    vec2 pixel_coord = uv * vec2(texture_size) - 0.5;
    
    ivec2 texel_coord = ivec2(floor(pixel_coord));
    vec2 f = fract(pixel_coord);
    
    vec4 t0 = texelFetch(tex, wrap_texel(texel_coord, texture_size), 0);
    vec4 t1 = texelFetch(tex, wrap_texel(texel_coord + ivec2(1, 0), texture_size), 0);
    vec4 t2 = texelFetch(tex, wrap_texel(texel_coord + ivec2(0, 1), texture_size), 0);
    vec4 t3 = texelFetch(tex, wrap_texel(texel_coord + ivec2(1, 1), texture_size), 0);
    
    return mix(mix(t0, t1, f.x), mix(t2, t3, f.x), f.y);
}
#endif
Shader code
shader_type canvas_item;

// Only one of these should be defined at a time
//#define FILTER_NONE
#define FILTER_3POINT
//#define FILTER_BOX

#include "res://location-to/texture_filter_operations.gdshaderinc"

void fragment() {
    COLOR = sample_filter(TEXTURE, UV);
}
Tags
AngryLionRDP, blurry, filter, filtering, N64, Nintendo, nintendo 64, retro, shaderinclude
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

Purp’s COMPATIBILITY RENDERER Decal Shader

MatCap/View-Based Fake Vertex-Shading

Purp’s N64 Soft/Blurry Post Process Effect

Related shaders

N64 RDP Dither + VI Post-Process Effect

3-point texture filtering

Smooth 3D pixel filtering

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments