- Home /
Skew a sprite at runtime while preserving the pixel shape?
Hello,
I currently use a 4x4 transformation matrix within a vertex shader to skew (or is it shear?) my sprites. It works but it is also skewing the shape of individual pixels which gives a weird look to my pixel art.
Transformed by my current shader:
Transformed by Gimp's perspective tool, notice the pixels are still square:
What would be the best way of skewing a sprites without affecting the pixel shapes?
Is there a trick that could be used to correct the result of the vertex shader? Force all edges to be either vertical or horizontal? (that one is for you shader gurus.. I know your out there!)
FYI I have also tried applying my transformation matrix to the texture coordinates but it's giving similar results. I have also came accross an opengl article mentionning something about modifying the coordinates while doing something similar to a tex2d lookup but I didnt quite get it and lost the link :(
EDIT: Hey followers! Ok, no definitive answer but there's lots of people following this. How about a group brainstorm? Pitch in some ideas! No matter how far fetched it sounds just pitch it in, worst case it will serve to elimiate what doesnt/can't work. Personally my shader knowledge is limited but im sure that as a group we can manage it! I'll update this question with our R&D results as we go.
What doesnt work:
4x4 Transform matrix on the vertex also skew the pixel shape
4x4 Transform matrix on the texture coordinates also skew the pixel shape and causes the texture to move outside the sprite's mesh, making parts of it disappear.
Have you tried http://www.sector12games.com/skewshear-vertex-shader/
i'm not sure transformation matrices are going to do this for you.
you may need to dynamically create a new texture and do the skew yourself to the actual pixels.
skew is an affine transformation, and one of the properties of all affine transformations is that parallel lines in the original are still parallel after the transformation. so consider your original image. the walls of each pixel are vertical, and the walls of the overall image are also vertical: they're parallel. but after the skew, what you want is the walls of the overall image to be slanted but the walls of the individual pixels to still be vertical: no longer parallel. therefore no affine transformation will achieve this.
what's going on in the Gimp version is that individual pixels are being moved, while in the shader version it's the geometry/texture mapping underneath the pixels which is being transformed.
Answer by FortisVenaliter · Feb 08, 2017 at 08:06 PM
If you're going to do this at runtime, you're probably going to need to write a specialized shader to do so.
The way to do it would be as follows:
When taking in the UV coordinates, multiply them to the target resolution and middle them (like a mix of floor/ceil, leaving you with a .5 after the decimal), then bring them back to the 0-1 UV space. This will get you a non-blended UV no matter the scale, as if you were turning off texture filtering.
From there, you can apply your skew function to the modified uv to get the lookup value from the source texture, and output it through the return value of the pixel shader.
That should allow you to get the effect you want. The most likely problem with this is that the geometry won't change, so you'll likely want to use oversized quads to render it with out of bounds UV values so it doesn't skew the image right off the geometry. Just remember to sanitize the UVs in your shader and add a transparent buffer border to your images.
Thank you very much, I almost understand what you mean... almost... (/sweat)
Could you or someone else clarify the following points?
Target resolution: Are we talking about the resolution of the sprite when pixels are 1:1 ie.: uv.xy = uv.xy * float2(64,64)? or do I need to take into account the PPU?
$$anonymous$$iddle them: Not sure I know how to do this, is there a built in function?
Sanitize the UV: Obviously of course... mmm how is that done again?
The rest (the easy part) I understand.
Thank you.
Correct.
You can use floor(value)+0.5
Clamp it to 0-1
Hi, I got my code together and the good news is it does preserve the pixel shape. The bad news is it completely deforms the sprite.
Here it is with no transformation code applied, the circle is deformed anyways:
Here it is with an horizontal transformation of -0.2. When increasing the value (in the negaives) it first enlarges then eventually moves further to the right. Going into positive values makes it colapse on itself from both sides equally until pixels start disapearing and nothing is left.
Here is the code from my fragment shader:
// Build a transform matrix
float4x4 transform$$anonymous$$atrix = float4x4(
1,-0.2,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1);
// $$anonymous$$ultiply UV by target res (actual resolution of the png file)
float2 uv = IN.texcoord * float2(24, 24);
// $$anonymous$$iddle the values
float2 middleUV = floor(uv) + 0.5;
// Bring values back to 0-1 UV space
float2 normalizedUV = normalize(middleUV);
// Sanitize UV values
float2 sanitizedUV = clamp(normalizedUV, 0, 1);
// Apply transform matrix
float2 transformedTextcoord = mul(transform$$anonymous$$atrix, sanitizedUV);
// Fetch the color and apply tint
fixed4 c = tex2D(_$$anonymous$$ainTex, transformedTextcoord) * IN.color;
// Apply the alpha
c.rgb *= c.a;
// Output the values
return c;
Since we normalize the values, do we really need to sanitize them afterwards?
To be honest, other than having something to do with "upscaling" the UV map and middleing the values, I dont understand how this manages to keep the pixels straight. If you have the time to explain the logic behind it I may be able to troubleshoot it.
Thank you very much.