So, HLSL is a huge pain in the butt. I did some mucking around and managed to get something working, but not in a way that I like.
I couldn’t get the sampler2d for the textures being passed into the shader to work correctly. Whenever I tried, everything would just start to mess up. It’s extra difficult because MonoGame seems to do some optimizations when it compiles the shaders and this makes debugging difficult. I might play around with this a bit more later, but hopefully someone with more shader experience will have some input!
So instead, I tried a different approach. Instead of using the textures and associated samplers, I figured putting the palette into float4 arrays would work. This was a huge pain to get working because the compiler again spewed errors. First my loops were too large, and then second, I was using too many instructions… even though I can’t for the life of me figure out why. I’ve certainly written more complex shaders in XNA before.
Anyway, I pared it down to this… which seems to do what you want, but it’s pretty specialized. I suspect there’s a better solution here and I think your approach using textures might be more “shader friendly”. I’m gonna keep playing around, but here’s something that works at least
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif
sampler2D InputSampler = sampler_state
{
Texture = <SpriteTexture>;
};
float4 xSourcePal[4];
float4 xTargetPal[4];
struct VertexShaderOutput
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
float2 UV : TEXCOORD0;
};
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 cIn = tex2D(InputSampler, input.UV) * input.Color;
float4 cOut = cIn;
for (int i = 0; i < 4; i++)
{
if (xSourcePal[i].r == cIn.r && xSourcePal[i].g == cIn.g && xSourcePal[i].b == cIn.b)
{
cOut = xTargetPal[i];
break;
}
}
return cOut;
}
technique Technique1
{
pass Pass1
{
PixelShader = compile PS_SHADERMODEL PixelShaderFunction();
}
}
Game1.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace RecolourTest
{
/// <summary>
/// This is the main type for your game.
/// </summary>
public class Game1 : Game
{
GraphicsDeviceManager _graphics;
SpriteBatch _spriteBatch;
RenderTarget2D _target;
Texture2D _train;
Texture2D _sourcePal;
Texture2D _targetPal;
Vector4[] _sourcePalData;
Vector4[] _targetPalData;
Effect _effect;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
this.IsMouseVisible = true;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
_target = new RenderTarget2D(_graphics.GraphicsDevice, _graphics.PreferredBackBufferWidth, _graphics.PreferredBackBufferHeight);
_train = this.Content.Load<Texture2D>("train");
_sourcePal = this.Content.Load<Texture2D>("source_pal");
_targetPal = this.Content.Load<Texture2D>("target_pal");
_effect = this.Content.Load<Effect>("RecolourEffect");
Color[] sourceData = new Color[_sourcePal.Width * _sourcePal.Height];
_sourcePal.GetData<Color>(sourceData);
_sourcePalData = new Vector4[sourceData.Length];
for (int i = 0; i < sourceData.Length; i++)
_sourcePalData[i] = sourceData[i].ToVector4();
Color[] targetData = new Color[_targetPal.Width * _targetPal.Height];
_targetPal.GetData<Color>(targetData);
_targetPalData = new Vector4[targetData.Length];
for (int i = 0; i < targetData.Length; i++)
_targetPalData[i] = targetData[i].ToVector4();
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
_graphics.GraphicsDevice.SetRenderTarget(_target);
_graphics.GraphicsDevice.Clear(Color.Transparent);
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
_spriteBatch.Draw(_train, new Vector2(10, 10), Color.White);
_spriteBatch.Draw(_sourcePal, new Vector2(10 + _train.Width + 10, 10), Color.White);
_spriteBatch.Draw(_targetPal, new Vector2(10 + _train.Width + 10, 10 + _sourcePal.Height + 10), Color.White);
_spriteBatch.End();
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, _effect, null);
_effect.Parameters["xSourcePal"].SetValue(_sourcePalData);
_effect.Parameters["xTargetPal"].SetValue(_targetPalData);
_effect.CurrentTechnique.Passes[0].Apply();
_spriteBatch.Draw(_train, new Vector2(10, 10 + _train.Height + 10), Color.White);
_spriteBatch.End();
float scale = 4f;
_graphics.GraphicsDevice.SetRenderTarget(null);
_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
_spriteBatch.Draw(_target, Vector2.Zero, null, Color.White, 0, Vector2.Zero, scale, SpriteEffects.None, 0f);
_spriteBatch.End();
base.Draw(gameTime);
}
}
}
(Set up your content project as required)