N64-style three-sample bilinear shader

So I stumbled upon this thread:

http://www.emutalk.net/threads/54215-Em … ng-Shaders

The point of that thread was to somehow incorporate the shader code there into N64 emulator plugins so as to more accurately emulate the N64’s imperfect bilinear filtering. However, in lieu of that, could that code be taken and made into a regular shader for use with any game? I am still not very familiar with Cg, so I am not sure how to make use of it. I was hoping someone more experienced with shaders could convert it to the format RetroArch uses, or at least point me in the right direction.

For the sake of convenience, here is the code:

float4 n64BilinearFilter( in float4 vtx_color : COLOR, in float2 texcoord_0 : TEXCOORD0) : COLOR {
	
	float2 tex_pix_a = float2(1/Texture_X,0);
	float2 tex_pix_b = float2(0,1/Texture_Y);
	float2 tex_pix_c = float2(tex_pix_a.x,tex_pix_b.y);
	float2 half_tex = float2(tex_pix_a.x*0.5,tex_pix_b.y*0.5);
	float2 UVCentered = texcoord_0 - half_tex;
	
	float4 diffuseColor = tex2D(ColorSampler,UVCentered);
	float4 sample_a = tex2D(ColorSampler,UVCentered+tex_pix_a);
	float4 sample_b = tex2D(ColorSampler,UVCentered+tex_pix_b);
	float4 sample_c = tex2D(ColorSampler,UVCentered+tex_pix_c);
	
	float interp_x = modf(UVCentered.x * Texture_X, Texture_X);
	float interp_y = modf(UVCentered.y * Texture_Y, Texture_Y);

	if (UVCentered.x < 0)
	{
		interp_x = 1-interp_x*(-1);
	}
	if (UVCentered.y < 0)
	{
		interp_y = 1-interp_y*(-1);
	}
	
	diffuseColor = (diffuseColor + interp_x * (sample_a - diffuseColor) + interp_y * (sample_b - diffuseColor))*(1-step(1, interp_x + interp_y));
	diffuseColor += (sample_c + (1-interp_x) * (sample_b - sample_c) + (1-interp_y) * (sample_a - sample_c))*step(1, interp_x + interp_y);

    return diffuseColor * vtx_color;

n64BilinearFilter.cgp

shaders = 1
shader0 = n64BilinearFilter.cg
filter_linear0 = false

n64BilinearFilter.cg

struct input
{
   float2 video_size;
   float2 texture_size;
   float2 output_size;
   float  frame_count;
   float  frame_direction;
   float frame_rotation;
};

void main_vertex
(
   float4 position : POSITION,
   out float4 oPosition : POSITION,
   uniform float4x4 modelViewProj,
   float2 tex : TEXCOORD,
   out float2 texcoord_0 : TEXCOORD
)
{
   oPosition = mul(modelViewProj, position);
   texcoord_0 = tex;
}

float4 main_fragment (in float2 texcoord_0 : TEXCOORD, uniform sampler2D ColorSampler : TEXUNIT0, uniform input IN) : COLOR
{
	float Texture_X=IN.texture_size.x;
	float Texture_Y=IN.texture_size.y;
	
	
	float2 tex_pix_a = float2(1/Texture_X,0);
	float2 tex_pix_b = float2(0,1/Texture_Y);
	float2 tex_pix_c = float2(tex_pix_a.x,tex_pix_b.y);
	float2 half_tex = float2(tex_pix_a.x*0.5,tex_pix_b.y*0.5);
	float2 UVCentered = texcoord_0 - half_tex;
	
	float4 diffuseColor = tex2D(ColorSampler,UVCentered);
	float4 sample_a = tex2D(ColorSampler,UVCentered+tex_pix_a);
	float4 sample_b = tex2D(ColorSampler,UVCentered+tex_pix_b);
	float4 sample_c = tex2D(ColorSampler,UVCentered+tex_pix_c);
	
	float interp_x = modf(UVCentered.x * Texture_X, Texture_X);
	float interp_y = modf(UVCentered.y * Texture_Y, Texture_Y);

	if (UVCentered.x < 0)
	{
		interp_x = 1-interp_x*(-1);
	}
	if (UVCentered.y < 0)
	{
		interp_y = 1-interp_y*(-1);
	}
	
	diffuseColor = (diffuseColor + interp_x * (sample_a - diffuseColor) + interp_y * (sample_b - diffuseColor))*(1-step(1, interp_x + interp_y));
	diffuseColor += (sample_c + (1-interp_x) * (sample_b - sample_c) + (1-interp_y) * (sample_a - sample_c))*step(1, interp_x + interp_y);

    return diffuseColor ;
}


the “rupee” filtering looks pretty cool :slight_smile:

Thanks for making this aliaspider.

The idea I have is to bake this into the mupen64 core for Glide64 and apply it per texture.

Right now, applying hardware bilinear on textures can have bad side-effects for certain games - with some N64 games it creates a jigsaw effect (noticeable on the Zelda OOT subscreen), for games like Starcraft 64, enabling bilinear will produce lines around some sprites, and then there is a game like Mortal Kombat Trilogy where enabling hardware bilinear on the character sprites will produce a silhouette. Currently the only way to get around some of these issues is to set texturing to ‘nearest’, but this will be applied for every texture right now and IIRC, Nintendo didn’t offer devs the option to turn off their inferior version of bilinear filtering - so games like Zelda OOT will have noticeable artefacts and will just look wrong.

Anyway, the idea would be to force everything to nearest and then apply this shader per texture instead. Not sure how feasible it is and what kind of a performance cost we’re looking at, but it might be worth it from an accuracy perspective.

You can see some screenshots of N64 games looking wrong with hardware GL bilinear here - it’s at the bottom of the page.

http://emulation-general.wikia.com/wiki/Nintendo_64

pretty cool idea :slight_smile:

it probably wouldn’t solve the texturing issue though as you can see in the test shots here :

nearest textures prescaled 8x with n64BilinearFilter then nearest textures prescaled 8x with n64BilinearFilter then bilinear bilinear

the problem is that in this case, the textures weren’t scaled in the original N64, as they were big enough for the low resolution TVs, that’s why they took the liberty of combining them that way. textures that are to be scaled then combined are required to have the same edge color for this reason. it’s a problem that can’t be solved unless someone can find find a way to detect those pattern and scale them together. but maybe adding shader support there would allow for some tricks to lessen the effect.

this is how it would look like ingame : screens

Is it losing the alpha channel on those n64bilinear in-game shots?

Otherwise, I think it indeed captures that peculiar N64 texture filtering look (especially on that menu screen where the sword gets the ‘rupee’ jaggy right in the middle of the blade :P), though whether it’s worth the performance hit of applying it per-texture or not is yet to be seen.

For reference, here is the game using software rendering:

http://imageshack.us/a/img829/212/i1ri.png

With the coverage filters stripped:

http://imageshack.us/a/img854/9509/vr44.png

ah, good catch ! my batch converter was indeed ignoring the alpha channel. I uploaded the correct one.

these are the pre-scaled textures used for those shots : textures.7z

I don’t think it would be a big performance hit by the way. it’s probably faster apply a shader to all the small textures residing in GPU memory, than to read and upload those big png files used in the hires-texture-packs, and those don’t seem to hurt much. and with the usage of CRC checks and enough video memory, they would only need to be converted once per game session.