The other day i was helping a guy porting over a 2d refraction shader in a post and i thought it would be fun to see what else i could come up with.
So i figured id post the shader i made as a little demo with a bunch of different takes on refraction shading and mix it up with other stuff it might help someone else trying to do it.
Plus its a example for how you can use pixel shaders with spritebatch.
If you try it of course you’ll have to use your own textures and spritefont you only need two textures for the shader found at the bottom and attached were labeled in the comments in game1 all all can be replaced with your own images of course.
The two images you need to recreate the shown image are at the bottom of the post.
Credit goes to charles humphrey for the xna image i used it’s a very good image for the example.
The shader It’s pretty big but i tried to make it clearly labeled and separated.
All the shaders can be called by technique name from the single game draw method that uses them on any texture. The game1 example here draws them all to the screen.
Edit fixed a bug in the limited refraction shader and changed the RefractionVectorRange to be correct in pixels.
//
// 2d Refraction related shaders
//
#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
//-----------------------------------------------------------------------------
//
// Requisite variable and texture samplers
//
//-----------------------------------------------------------------------------
// This vector should be in motion in order to achieve the desired effect.
float2 DisplacementMotionVector;
float SampleWavelength;
float Frequency; // .51 .3
float RefractiveIndex;
// one more little test here.
float2 RefractionVector;
float RefractionVectorRange;
Texture2D Texture : register(t0);
sampler TextureSampler : register(s0)
{
Texture = (Texture);
};
Texture2D DisplacementTexture;
sampler2D DisplacementSampler = sampler_state
{
magfilter = linear;
minfilter = linear;
AddressU = Wrap;
AddressV = Wrap;
Texture = <DisplacementTexture>;
};
//-----------------------------------------------------------------------------
//
// Requisite functions.
//
//-----------------------------------------------------------------------------
// function reflect map pixel grabber my version
float4 FuncMyRefractMap(float4 color, float2 texCoord)
{
// the SampleWavelength grabs a smaller or larger area of texture coordinates.
// the motion moves the uvs over the refraction map to give the feeling of motion.
// the Frequency reduces the manitude of the range the coordinates are grabed from.
texCoord += (tex2D(DisplacementSampler, texCoord * SampleWavelength + DisplacementMotionVector).xy - float2(0.5f, 0.5f)) * Frequency;
return tex2D(TextureSampler, texCoord) * color;
}
// function reflect map pixel grabber the ported version.
float4 FuncGuysRefractMap(float4 color, float2 texCoord)
{
texCoord += tex2D(DisplacementSampler, texCoord * SampleWavelength + DisplacementMotionVector) * 0.2 - 0.15;
return tex2D(TextureSampler, texCoord) * color;
}
// function reflect map pixel grabber hlsl snells
float4 FuncHlslRefractMap(float2 ray, float4 color, float2 texCoord)
{
float2 normalValue = abs(normalize(tex2D(DisplacementSampler, texCoord * SampleWavelength + DisplacementMotionVector).rg));
texCoord = texCoord + refract(ray, normalValue, RefractiveIndex); //* Amplitude;
// Look up into the main textur's pixel and return.
return tex2D(TextureSampler, texCoord) * color;
}
// function monochrome
float4 FuncMonoChrome(float4 col)
{
col.rgb = (col.r + col.g + col.b) / 3.0f;
return col;
}
// function outline
float4 FuncDiagnalAvg(float4 col, float2 texCoord)
{
col -= tex2D(TextureSampler, texCoord.xy - 0.003) * 2.7f;
col += tex2D(TextureSampler, texCoord.xy + 0.003) * 2.7f;
return col;
}
// function outline
float4 FuncDiagnalAvgMonochrome(float4 col, float2 texCoord)
{
col -= tex2D(TextureSampler, texCoord.xy - 0.003) * 2.7f;
col += tex2D(TextureSampler, texCoord.xy + 0.003) * 2.7f;
col.rgb = (col.r + col.g + col.b) / 3.0f;
return col;
}
// function highlight golden ratio steped.
float4 FuncHighlight(float4 col, float2 texCoord)
{
// try to brighten on blue.
float2 temp = texCoord;
float dist = 0.01f;
float gldr = 1.6f;
temp += float2(dist * gldr, 0.00f);
float4 col01 = tex2D(TextureSampler, temp);
temp = texCoord;
temp += float2(0.00f, dist * gldr * 2.0f);
float4 col03 = tex2D(TextureSampler, temp);
temp = texCoord;
temp += float2(-dist * gldr * 3.0f, 0.00f);
float4 col02 = tex2D(TextureSampler, temp);
temp = texCoord;
temp += float2(0.00f, -dist * gldr * 4.0f);
float4 col04 = tex2D(TextureSampler, temp);
float4 tempCol = ((col01 + col02 + col03 + col04) - col) / 2.0f;
tempCol.bg = col.bg;
col.rgb = saturate(col.rgb * tempCol.rgb);
return col;
}
//-----------------------------------------------------------------------------
//
// Requisite Shaders.
//
//-----------------------------------------------------------------------------
//__________________________________________________
// (Tech) refraction map shader mine
//__________________________________________________
float4 PsRefractionMap(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 col = FuncMyRefractMap(color, texCoord.rg);
return col;
}
//__________________________________________________
// (Tech) refraction map shader guys
//__________________________________________________
float4 PsRefraction2(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 col = FuncGuysRefractMap(color, texCoord.rg);
return col;
}
//__________________________________________________
// (Tech) RefractDiagnalAverageMonochrome
//__________________________________________________
float4 PsDiagnalAverageMonochromeShader(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 col = FuncMyRefractMap(color, texCoord.rg);
col = FuncDiagnalAvgMonochrome(col, texCoord.rg);
return col;
}
//__________________________________________________
// (Tech) the golden ratio spiral highlight
//__________________________________________________
float4 PsRefractGoldenRatioHighlight(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 col = FuncMyRefractMap(color, texCoord.rg);
col =FuncHighlight(col, texCoord.rg);
return col;
}
//__________________________________________________
// (Tech) RefractMonoCromeClipDarkness
//__________________________________________________
float4 PsRefractMonoCromeClipDarkness(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 col = FuncMyRefractMap(color, texCoord.rg);
col = FuncMonoChrome(col);
clip(col.r - 0.4f);
return col;
}
//__________________________________________________
// (Tech) RefractAntiRefractionArea
// this prevents a area around the point from being refracted
//__________________________________________________
float4 PsRefractAntiRefractionArea(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
// Determine distance to the anti Refract position.
float dist = saturate(distance(position.xy, RefractionVector) / RefractionVectorRange);
float2 warpedCoords = texCoord + (tex2D(DisplacementSampler, texCoord * SampleWavelength + DisplacementMotionVector).xy - float2(0.5f, 0.5f)) * Frequency;
float2 lerpedCoords = (warpedCoords - texCoord) * (dist * dist) + texCoord;
float4 col = tex2D(TextureSampler, saturate(lerpedCoords)) * color;
// Visually highlight effect range.
//col.b += (1.0f - dist) * (1.0f - dist);
//col.r += (dist) * (dist);
return col;
}
//__________________________________________________
// (Tech) LimitedRefractionArea
// this only creates a warped inverse refraction area around the point
//__________________________________________________
float4 PsLimitedRefractionArea(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
// Determine distance to the anti Refract position.
float dist = saturate(distance(position.xy, RefractionVector) / RefractionVectorRange);
float warpedCoords = (tex2D(DisplacementSampler, texCoord * SampleWavelength + DisplacementMotionVector).xy - float2(0.5f, 0.5f)) * Frequency;
float2 lerpedCoords = (warpedCoords) * (1.0f - dist) * (1.0f - dist) + texCoord;
float4 col = tex2D(TextureSampler, saturate(lerpedCoords)) * color;
// Visually highlight effect range.
//col.r += (1.0f - dist) * (1.0f - dist);
//col.b += (dist) * (dist);
return col;
}
//__________________________________________________
// (Tech) TwoPassTechnique uses mine modified.
// this is for the double pass test shader.
//__________________________________________________
float4 PsSecondaryShader(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
// the frequency grabs a smaller area of texture coordinates the motion moves the uvs over the refraction map.
float2 coordRange = texCoord * SampleWavelength + DisplacementMotionVector;
// brightspots and color change well add this last.
float2 brightspots = abs(float2(sin(coordRange.x), cos(coordRange.y)));
// we get the texels from the refraction map and shrink grab range.
texCoord += (tex2D(DisplacementSampler, coordRange).rg - float2(0.5f, 0.5f)) * Frequency;
// Look up into the main texture.
float4 col = tex2D(TextureSampler, saturate(texCoord)) * color * float4(brightspots.x, brightspots.y, 1.0f, 0.5f);
return col;
}
//__________________________________________________
// Unfortunately i cant seem to figure out how to do the 2d one.
// the 3d one seems pretty simple.
// but as it is this ones busted.
//
// (Tech) refraction hlsl call using snells law.
//__________________________________________________
float4 PsRefractionSnells(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float2 ray = float2(0.5f, .5f); // float2(0.707106f, 0.707106f);
float4 col = FuncHlslRefractMap(ray, color, texCoord.rg);
return col;
}
//-----------------------------------------------------------------------------
//
// Requisite Techniques.
//
//-----------------------------------------------------------------------------
technique Refraction2
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefraction2(); // ps_2_0 doesn't error either.
}
}
technique RefractionMap
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefractionMap();
}
}
technique RefractDiagnalAverageMonochrome
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsDiagnalAverageMonochromeShader();
}
}
technique RefractGoldenRatioHighlight
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefractGoldenRatioHighlight();
}
}
technique RefractMonoCromeClipDarkness
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefractMonoCromeClipDarkness();
}
}
technique RefractAntiRefractionArea
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefractAntiRefractionArea();
}
}
technique LimitedRefractionArea
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsLimitedRefractionArea();
}
}
technique RefractionSnells
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefractionSnells();
}
}
technique TwoPassTechnique
{
pass Pass0
{
PixelShader = compile PS_SHADERMODEL
PsRefractionMap();
}
pass Pass1
{
PixelShader = compile PS_SHADERMODEL
PsSecondaryShader();
}
}
the game1
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
namespace RefractionShader
{
/// <summary>
/// This is the main type for your game.
/// </summary>
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont font;
Effect refractionEffect;
Texture2D tex2dForground;
Texture2D tex2dRefractionTexture;
Texture2D tex2dXnaUkImage;
Texture2D tex2dcool02;
Texture2D tex2dEgyptianDesert;
Texture2D tex2dfire01;
Texture2D tex2dbackground_mat_rgo;
Texture2D tex2dpgfkp;
Texture2D tex2dwater04;
Texture2D tex2dRefractionImg;
// some default control values for the refractions.
float speed = .02f;
float waveLength = .08f;
float frequency = .2f;
float refractiveIndex = 1.3f;
Vector2 refractionVector = new Vector2(0f, 0f);
float refractionVectorRange = 100;
// To alter the size and position on the screen of all the image squares.
// Change the w h values.
public int StW{get;set;}
public int StH{get;set;}
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = 1200;
graphics.PreferredBackBufferHeight = 800;
Window.AllowUserResizing = true;
Content.RootDirectory = "Content";
IsMouseVisible = true;
StW = graphics.PreferredBackBufferWidth / 4;
StH = graphics.PreferredBackBufferHeight /3;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>("MgGenFont");
refractionEffect = Content.Load<Effect>("RefractShader");
// extra images for testing.
tex2dXnaUkImage = Content.Load<Texture2D>("XnaUkImage");
tex2dcool02 = Content.Load<Texture2D>("cool02");
tex2dEgyptianDesert = Content.Load<Texture2D>("EgyptianDesert");
tex2dfire01 = Content.Load<Texture2D>("fire01");
tex2dpgfkp = Content.Load<Texture2D>("pgfkp");
tex2dRefractionImg = Content.Load<Texture2D>("RefactionTexture");
tex2dwater04 = Content.Load<Texture2D>("water04");
tex2dbackground_mat_rgo = Content.Load<Texture2D>("background_mat_rgo");
// the primary two textures used for all the tests.
tex2dForground = tex2dXnaUkImage;
tex2dRefractionTexture = tex2dRefractionImg;
}
protected override void UnloadContent()
{
}
float pauseTimer = 1.0f;
float pauseDelay = .5f;
int choice = 0;
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (pauseTimer > 0f)
pauseTimer = pauseTimer - (float)gameTime.ElapsedGameTime.TotalSeconds;
else
{
if (Keyboard.GetState().IsKeyDown(Keys.Space))
{
choice++;
if (choice > 3)
choice = 0;
switch (choice)
{
case 1:
tex2dForground = tex2dEgyptianDesert;
break;
case 2:
tex2dForground = tex2dwater04;
break;
case 3:
tex2dForground = tex2dfire01;
break;
default:
tex2dForground = tex2dXnaUkImage;
break;
}
pauseTimer = pauseDelay;
}
if (Keyboard.GetState().IsKeyDown(Keys.Up))
waveLength += .001f;
if (Keyboard.GetState().IsKeyDown(Keys.Down))
waveLength -= .001f;
if (Keyboard.GetState().IsKeyDown(Keys.Right))
frequency += .001f;
if (Keyboard.GetState().IsKeyDown(Keys.Left))
frequency -= .001f;
}
refractionVector = Mouse.GetState().Position.ToVector2();
refractionVector.Y = graphics.GraphicsDevice.Viewport.Bounds.Size.ToVector2().Y - refractionVector.Y;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// Draw the images well be using.
// row 1 far left we squeeze the regular two images in here that we use.
spriteBatch.Begin();
spriteBatch.Draw(tex2dRefractionTexture, new Rectangle(StW * 0, StH * 0, StW, StH/2), Color.White);
spriteBatch.Draw(tex2dForground, new Rectangle(StW * 0, StH /2, StW, StH/2), Color.White);
spriteBatch.DrawString(font, "wavelength " + waveLength + "\nfrequency " + frequency, new Vector2(10, 10), Color.Wheat);
spriteBatch.DrawString(font, "Mouse: " + refractionVector, new Vector2(10, 60), Color.Moccasin);
spriteBatch.End();
// row 2 and 3 far left
Draw2dRefractionTechnique("RefractMonoCromeClipDarkness", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 0, StH * 1, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
Draw2dRefractionTechnique("RefractDiagnalAverageMonochrome", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 0, StH * 2, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
// row1
Draw2dRefractionTechnique("Refraction2", tex2dForground, tex2dRefractionTexture, new Rectangle(StW*1, StH*0, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
Draw2dRefractionTechnique("Refraction2", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 2, StH * 0, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), true, gameTime);
Draw2dRefractionTechnique("RefractionSnells", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 3, StH * 0, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
// row 2
Draw2dRefractionTechnique("RefractionMap", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 1, StH * 1, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
Draw2dRefractionTechnique("RefractionMap", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 2, StH * 1, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), true, gameTime);
Draw2dRefractionTechnique("TwoPassTechnique", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 3, StH * 1, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
// row 3
Draw2dRefractionTechnique("RefractGoldenRatioHighlight", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 1, StH * 2, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
Draw2dRefractionTechnique("RefractAntiRefractionArea", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 2, StH * 2, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), false, gameTime);
Draw2dRefractionTechnique("LimitedRefractionArea", tex2dForground, tex2dRefractionTexture, new Rectangle(StW * 3, StH * 2, StW, StH), speed, refractiveIndex, frequency, waveLength, refractionVector, refractionVectorRange, new Vector2(10, 1), true, gameTime);
// Draw to the whole screen for testing.
//Draw2dRefractionTechnique("RefractAntiRefractionArea", tex2dForground, tex2dRefractionTexture, new Rectangle(0, 0, StW * 4, StH * 3), speed, refractiveIndex, frequency, waveLength, new Vector2(10, 1), true, gameTime);
base.Draw(gameTime);
}
/// <summary>
/// Draw a refracted texture using the refraction technique.
/// </summary>
public void Draw2dRefractionTechnique(string technique, Texture2D texture, Texture2D displacementTexture, Rectangle screenRectangle, float refractionSpeed, float refractiveIndex, float frequency, float sampleWavelength, Vector2 refractionVector, float refractionVectorRange, Vector2 windDirection, bool useWind, GameTime gameTime)
{
Vector2 displacement;
double time = gameTime.TotalGameTime.TotalSeconds * refractionSpeed;
if (useWind)
displacement = -(Vector2.Normalize(windDirection) * (float)time);
else
displacement = new Vector2((float)Math.Cos(time), (float)Math.Sin(time));
// Set an effect parameter to make the displacement texture scroll in a giant circle.
refractionEffect.CurrentTechnique = refractionEffect.Techniques[technique];
refractionEffect.Parameters["DisplacementTexture"].SetValue(displacementTexture);
refractionEffect.Parameters["DisplacementMotionVector"].SetValue(displacement);
refractionEffect.Parameters["SampleWavelength"].SetValue(sampleWavelength);
refractionEffect.Parameters["Frequency"].SetValue(frequency);
refractionEffect.Parameters["RefractiveIndex"].SetValue(refractiveIndex);
// for the very last little test.
refractionEffect.Parameters["RefractionVector"].SetValue(refractionVector);
refractionEffect.Parameters["RefractionVectorRange"].SetValue(refractionVectorRange);
spriteBatch.Begin(0, null, null, null, null, refractionEffect);
spriteBatch.Draw(texture, screenRectangle, Color.White);
spriteBatch.End();
DisplayName(screenRectangle, technique, useWind);
}
public void DisplayName(Rectangle screenRectangle , string technique, bool useWind)
{
spriteBatch.Begin();
var offset = screenRectangle;
offset.Location += new Point(20, 20);
spriteBatch.DrawString(font, technique, offset.Location.ToVector2(), Color.White);
if (useWind)
{
offset.Location += new Point(0, 30);
spriteBatch.DrawString(font, "wind on", offset.Location.ToVector2(), Color.White);
}
spriteBatch.End();
}
}
}
credit to charles for the image.
.
Well i hope this helps someone.