User Tag List

Results 1 to 9 of 9

Thread: [Help] Dynamic NES Palette Pixel Shader

  1. #1
    Clicker Fusion 2.5

    Join Date
    Jun 2015
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Question [Help] Dynamic NES Palette Pixel Shader

    I've literally just today entered the wonderful world of pixel shaders, with the wish of making a specific palette shader for a game I'm working on.

    I'm planning on making the game with the a slightly altered NES palette, and I've looked at some other Palette shaders to see how it would be done (and to see how HLSL generally worked).
    The game would run at a very low resolution (similar resolution to the NES, albeit with a different aspect ratio), and I'd then scale it up after everything else in the shader.

    Every single tile and sprite will already be fixed to this NES palette, therefore I don't need this shader to make other colors into the nearest colors in the palette.
    I'm not going to use any form of half-transparency or ink effects (only pixels that are pure pink, or 255, 0, 255 in RGB, will be fully transparent)

    I wanted the shader to find which index the color (of the current pixel) has in the palette, where the pixel's color (index) then would be shifted in the palette by a given offset value (primarily in the Y dimension)
    This would allow me to make everything 1 level (or more) darker or lighter, I'd then save an X offset for something like a constant shifting rainbow effect if that was needed, either that or lock the entire X dimension to a single column of colors, so everything could be in greyscale (or even GameBoy-like green colors).

    I've written an experimental shader that should be able to do the Y Offset part (that would also make sure that any colors above or below the palette-spectrum would reset to the limits).

    Code:
    sampler2D img;					// Input screen (per pixel)
    sampler2D Palette: register(s1);			// Palette
    float Xoffset;						// X offset value, currently unused
    float Yoffset;						// Y offset value
    int Mode;							// Greyscale, rainbow, GameBoy, etc. Currently unused
    
    float4 ps_main(in float2 In : TEXCOORD0) : COLOR0
    {	
    	float4 Out = tex2D(img, In);
    	float2 PaletteCoord;
    	PaletteCoord.xy = (0,0);
    
    	for(float PaletteIndex; tex2D(img, In).rgb != tex2D(Palette, PaletteCoord).rgb; PaletteIndex++)
    	{
    		PaletteCoord.x = PaletteIndex % 16;
    		PaletteCoord.y = Floor(PaletteIndex / 16);
    	}
    
    	//PaletteCoord.x += Xoffset;
    
    	if((PaletteCoord.y + Yoffset) > 3)
    	{
    		PaletteCoord.y = 3;
    	}
    	else if((PaletteCoord.y + Yoffset) < 0)
    	{
    		PaletteCoord.y = 0;
    	}
    	else
    	{
    		PaletteCoord.y += Yoffset;
    	}
    
    	Out.rgb = tex2D(Palette, PaletteCoord).rgb;
    
    	return Out;
    }
    technique tech_main
    {
    	pass P0
    	{
    		PixelShader  = compile ps_2_0 ps_main();
    	}
    }
    This doesn't work for some reason. Later on I then learned that it wasn't very efficient to use for-loops in shader-code (I should've predicted it, having to compare a color to 64 other colors for every pixel on the screen, 60 times a second, that's gotta be rough).
    My problem is that I can't see any other way to find out what color has what index in the palette, that is without making a 64-layered if-nest that compares the input to each color in the palette (which I think would almost be worse than a for-loop).

    I need a way to find the index in the palette of the colors from the input, from there I'm pretty sure I'd know how to manipulate the index numbers to make the outputs get their neighbor-colors in the palette and such.

  2. #2
    Clicker Fusion 2.5

    Join Date
    Jun 2015
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I made the shader work (sort of), although it required the removal of the for-loop. I also fixed some syntax-ish errors (the coordinates were not scaled correctly).

    Code:
    sampler2D img;						// Input screen (per pixel)
    sampler2D Palette: register(s1);			// Palette
    float Xoffset;						// X offset value
    float Yoffset;						// Y offset value
    int Mode;							// 0 = Normal, 1 = Rainbow, 2 = GameBoy
    
    float4 ps_main(in float2 In : TEXCOORD0) : COLOR0
    {	
    	float4 Out = tex2D(img, In);
    	float2 PaletteCoord;
    	PaletteCoord.x = 0.0;
    	PaletteCoord.y = 0.0;
    
    // This for-loop is what's making the shader not work.
    
    	[loop]for(float PaletteIndex; Out.rgb != tex2D(Palette, PaletteCoord).rgb; PaletteIndex++)
    	{
    		PaletteCoord.x = (PaletteIndex % 16) / 16;
    		PaletteCoord.y = (floor(PaletteIndex / 16)) / 4;
    	}
    // I tried the shader without the for-loop, and the shader behaved as expected.
    // It turned every pixel in the sprite into the color of the current indexed color on the palette + offset
    
    	//PaletteCoord.x += (Xoffset / 16);
    
    	if((PaletteCoord.y + (Yoffset / 4)) > 1.0)
    	{
    		PaletteCoord.y = 1.0;
    	}
    	else if((PaletteCoord.y + (Yoffset / 4)) < 0.0)
    	{
    		PaletteCoord.y = 0.0;
    	}
    	else
    	{
    		PaletteCoord.y += (Yoffset / 4);
    	}
    
    	Out.rgb = tex2D(Palette, PaletteCoord).rgb;
    	
    	return Out;
    }
    technique tech_main
    {
    	pass P0
    	{
    		PixelShader  = compile ps_2_0 ps_main();
    	}
    }

  3. #3
    Clicker Fusion 2.5 DeveloperSWF Export ModuleUnicode Add-on
    Looki's Avatar
    Join Date
    Aug 2006
    Location
    Karlsruhe, Germany
    Posts
    3,739
    Mentioned
    4 Post(s)
    Tagged
    1 Thread(s)
    (I should've predicted it, having to compare a color to 64 other colors for every pixel on the screen, 60 times a second, that's gotta be rough).
    That's cute. Your graphics card could do this without breaking a sweat in less than a millisecond for the entire window! Sadly, Fusion 2.5 limits us to the Shader Model 2.0 from 2006. I can't explain why, but this model did not allow quite a few things that you would assume are no problem, such as dynamic for loops. Simple for loops that loop a number from one constant to another is still possible. Try rewriting your shader so that it takes the form

    for (int i = 0; i < count; i++)

    you could then use i to calculate the offset within the palette.

    I don't remember the exact details of the limitations but it's definitely possible, I've written a few blur-like shaders that use loops to collect blur samples.

  4. #4
    Clicker Fusion 2.5

    Join Date
    Jun 2015
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yeah I've never really had a good sense of how powerful computers are when it comes to calculations like these... especially with Clickteam being 32-bit and all.

    Anyhoo, I believe I fixed the shader for now, all that's in my way is the fact that it's "too complex" according to Clickteam (there are "more active values than registers", apparently).
    I've tried my best to remove un-needed variables, but to no avail.

    Code:
    sampler2D img: register(s0); 			// Input screen (per pixel)
    sampler2D Palette: register(s1);			// Palette
    float Xoffset;						// X offset value
    float Yoffset;						// Y offset value
    int Mode;							// 0 = Normal, 1 = Rainbow, 2 = GameBoy
    
    float4 ps_main(in float2 In : TEXCOORD0) : COLOR0
    {	
    
    	float4 Out = tex2D(img, In);
    	float2 PaletteCoord = (0.0, 0.0);
    	//PaletteCoord.x = 0.0;
    	//PaletteCoord.y = 0.0;
    	float4 PalOut = tex2D(Palette, PaletteCoord);
    
    	for (int i = 0; i < 58; i++)
    	{
    		if(Out.r != PalOut.r || Out.g != PalOut.g || Out.b != PalOut.b)
    		//if(Out.r != tex2D(Palette, PaletteCoord).r || Out.g != tex2D(Palette, PaletteCoord).g || Out.b != tex2D(Palette, PaletteCoord).b)
    		//if(tex2D(img, In) != tex2D(Palette, PaletteCoord))
    		// ^^ I tried several different ways to if-check this, the first one makes it lag horribly with an error saying
    		//    the shader is too complex, while the second one creates the error "float expected".
    		{
    			PaletteCoord.x = fmod(i, 16) / 16;
    			PaletteCoord.y = (floor(i / 16)) / 4;
    		}
    		else
    		{
    			i = 58; // I tried break; but it didn't work, so this breaks the loop instead.
    			// There are 57 different colors, but 8 of the 64 spots in the palette are the same pink color which is used for transparency, and I have no need for 7 extra colors.
    			// so 56 colors + 1 transparent color (pink).
    		}
    	}
    	
    
    	//PaletteCoord.x += (Xoffset / 16);
    
    	if((PaletteCoord.y + (Yoffset / 4)) > 0.75)
    	{
    		PaletteCoord.y = 0.75;
    	}
    	else if((PaletteCoord.y + (Yoffset / 4)) < 0)
    	{
    		PaletteCoord.y = 0.0;
    	}
    	else
    	{
    		PaletteCoord.y += (Yoffset / 4);
    	}
    
    	Out.rgb = tex2D(Palette, PaletteCoord).rgb;
    	
    	return Out;
    }
    technique tech_main
    {
    	pass P0
    	{
    		VertexShader = NULL;
    		PixelShader  = compile ps_2_0 ps_main();
    	}
    }
    I've tested the shader where I removed the for-loop/color-index-search and instead just put a predefined index value (pointing to a specific color), which successfully colored the entire sprite in that color + offset, and the offset's limits also worked the way they should.

    The error used to say something about there being 32 values when D3D9 only allows 31, but now that I've removed some unnecessary values, it now believes there are still more than it has been able to register.
    Any idea on how this could be fixed?

    Edit: I just tried switching the number of times the for-loop would run, and it appeared to fix it. With the way the for-loop is designed now (+ the if-statement thingy inside) Clickteam can only afford to run the for-loop about 5-6 times... So yeah a for-loop is most likely out of the question...
    But anyway, this brings me back to another method I was trying out before I saw your reply Looki, which is to use the RGB values of the input to make a unique seed, which can be used to get a pre-defined index value in an array. The only problem with it was that it was difficult to find a way to generate a seed that would be unique for each color, since all the colors Red and Green values here are divisible by 4, and the Blue values are divisible by 8, there's a lot of similar numbers. But I found a way, by writing a program in C# that would calculate each color with this seed-generator. And then use a "check list for duplicates" website.
    The thing that stopped me from doing it this way before, was that Clickteam would lag horribly every time I used the seed-calculation to find the index value (which is in an INT array with 2048 spots, that btw works fine if I skip the calculation and input a certain seed-result instead). The problem persists. The seed-calculation must be too long...

  5. #5
    Clicker Fusion 2.5

    Join Date
    Jun 2015
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Okay, so after tinkering with this. I've found the definite problem to this seed-calculator thing.

    There's just too much going on for D3D9. I have no idea why, that's just how it is.

  6. #6
    Clicker Fusion 2.5 DeveloperSWF Export ModuleUnicode Add-on
    Looki's Avatar
    Join Date
    Aug 2006
    Location
    Karlsruhe, Germany
    Posts
    3,739
    Mentioned
    4 Post(s)
    Tagged
    1 Thread(s)
    Yeah, the limits of D3D9 shaders are very limited. We've had a lot of frustrating problems with that Usually the compiler is also good enough to make your code as resource-efficient as possible, meaning that if you try to outsmart it you will often not see an improvement at all. I would suggest that you redesign your shader somehow, if possible. Since the bottleneck here is the color index lookup, I can only suggest to get rid of this by directly storing the indices as the color of your objects. I know this would be frustrating to work with, however. Since you have 58 colors, you could store the index in the red channel of each pixel (To be on the safe side when it comes to floating point inaccuracies you could multiply the index by 4, so the numbers would span from 0 to 232). Since you seem to know C# you could write a tool that converts your images to the palette indices.

  7. #7
    Clicker Fusion 2.5

    Join Date
    Jun 2015
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've looked into using one of the channels like that, but the thing is I'm using the NES Palette here, and each of the channels have shades of R/G/B that are shared with other colors
    I even checked to see if I could use two of the channels together, but there's still duplicates no matter the combination.

    Changing the colors defeats what I'm trying to do, so that's out of the question (for now).

    I discovered that Clickteam can use the shader-version above ps_2_0, which is ps_2_a. This gives me 512 of those things that were limited to 64 (can't remember what they were called).

    So now, I have a functioning shader that (for some yet unknown reason) doesn't produce quite the output I'd expected

    Code:
    sampler2D img;						// Input screen (per pixel)
    sampler2D Palette: register(s1);	// Palette
    float Xoffset;						// X offset value
    float Yoffset;						// Y offset value
    int Mode;							// 0 = Normal, 1 = Rainbow, 2 = GameBoy
    
    float4 ps_main(in float2 In : TEXCOORD0) : COLOR0
    {	
    	float4 o = tex2D(img, In);
    	float2 PaletteCoord;
    	PaletteCoord.x = 0.0;
    	PaletteCoord.y = 0.0;
    	float4 p = tex2D(Palette, PaletteCoord);
    	
    	for (int i = 0; (o.r != p.r || o.g != p.g || o.b != p.b) && i < 63; i++)
    //	for (int i = 0; all(o.rgb != p.rgb) && i < 64; i++)
    	{
    		//PaletteCoord.x = (fmod(i, 16) / 16);
    		//PaletteCoord.y = ((floor(i / 16)) / 4); // Putting the index coordinates in like this yields a laggy shader that gives errors.
    		
    		PaletteCoord = ((fmod(i, 16) / 16), ((floor(i / 16)) / 4)); // But for some reason putting them in with this, works fine.
    		p = tex2D(Palette, PaletteCoord);
    	}
    
    	//PaletteCoord.x += (Xoffset / 16);
    
    	if((PaletteCoord.y + (Yoffset / 4)) > 0.75)
    	{
    		PaletteCoord.y = 0.75;
    	}
    	else if((PaletteCoord.y + (Yoffset / 4)) < 0)
    	{
    		PaletteCoord.y = 0.0;
    	}
    	else
    	{
    		PaletteCoord.y += (Yoffset / 4);
    	}
    
    	o.rgb = tex2D(Palette, PaletteCoord).rgb;
    	return o;
    }
    technique tech_main
    {
    	pass P0
    	{
    		VertexShader = NULL;
    		PixelShader  = compile ps_2_a ps_main();
    	}
    }
    This makes my test-image (all colors except black) all cyan (the cyan color at 12,3 to be exact, counting with zero), the alpha of the input is preserved however, so what's transparent in the input is transparent in the output as well (transparency with pink as the transparent color).

    This is the slightly modified NES palette I'm using: Name:  NesPalette.png
Views: 527
Size:  303 Bytes

    The only changes are that I've removed the duplicate grey-scale colors, so they are in one column instead of two, and I added the colors from the GameBoy (which are only gonna be used for GameBoy-mode).

  8. #8
    Clicker Fusion 2.5

    Join Date
    Jun 2015
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I just understood what you meant with the red channel. I read another thread with the same sort of problem, and they meant to use the red channel of the source image (input), which I NEVER thought about... I thought you meant I had to use the red channel of the palette. But now that I know this, I've completed the shader, and it works perfectly. The only down-side is that all images in the game are gonna have to be a very dark red (so only the index data of the red channel is there, 0-63 colors), then again, that would probably frustrate anybody who tries to rip the game's graphics to find secrets or what-not, which is nice.

    Anyway, thanks for the help Looki

  9. #9
    Clicker Fusion 2.5 DeveloperSWF Export ModuleUnicode Add-on
    Looki's Avatar
    Join Date
    Aug 2006
    Location
    Karlsruhe, Germany
    Posts
    3,739
    Mentioned
    4 Post(s)
    Tagged
    1 Thread(s)
    Glad to hear you figured it out. You could experiment with the color mapping a bit, e.g. multiply the red component by 4 so that it is more visible, or allow green and blue as well so that it's easier to work with in MMF (in the shader, you could add r, g and b together, and when the other two are zero only one is used etc.)

Similar Threads

  1. 256 color/Frame Palette Pixel Shader
    By Corlen in forum Multimedia Fusion 2 - Technical Support
    Replies: 0
    Last Post: 24th February 2012, 07:26 AM
  2. Sub-Pixel Positioning Pixel Shader
    By LazyCoder in forum Hardware Accelerated Runtime
    Replies: 15
    Last Post: 16th March 2008, 12:24 PM
  3. [Request/idea]Pixel palette changer
    By VictorT in forum Extension Development
    Replies: 4
    Last Post: 5th September 2007, 09:46 AM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •