It looks like my shader acts differently when the sprite is packed into a sprite atlas and was wondering if anyone could see what would be wrong with my frag shader. It looks like it’s sampling the right uv’s as it looks correct without the shader but when the sprite is packed, it almost looks as though the effect is doubled?
fixed4 WSpriteFrag(v2f IN) : SV_Target
{
float sine = sin((IN.texcoord.y + abs(_Time * _Speed)) * _Intensity);
IN.texcoord.x += sine * _XFrequency
IN.texcoord.y += sine * _YFrequency;
fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
c.rgb *= c.a;
return c;
}
Nothing is wrong with the shader. The problem is the UVs change when you’re using an atlas.
UVs are a normalized range between 0.0 and 1.0 where 0.0, 0,0 is the lower right corner and 1.0, 1.0 is the upper right corner of the current texture. The resolution or aspect ratio of the texture does not affect this. But a texture atlas will because that “one Sprite” is no longer the entire texture. It’s just a small part of the overall atlas texture, so the bottom left of the sprite’s UVs might now be 0.2, 0.45 and the top right might be 0.4, 0.65. Or some other random position that depends on where it is in the atlas texture.
One thing you can do is modify the intensity to be in texel scale. The current texture’s dimensions are passed to the shader in a float4 _MainTex_TexelSize; if you add it to the code. The zw values of that float 4 are dimensions of the texture, and the xy are 1/dimensions. So you can use that to scale the “intensity”, “frequency”, and the texcoord input to sin() so that it looks roughly the same regardless of if it’s in an atlas or standalone.
I’ve tried using the values but I’m incredibly new to shaders so I don’t really know where to start with converting the intensity and frequency to texel scale.
I guess I could do _Intensity * _MainTex_TexelSize.xy; but I guess that sets the Intensity to an incredibly low number.
I’ve also tried doing this but I suspect it’s wrong.
Well, some pedantry first. Those values setting the “frequency” … aren’t setting the frequency. That’s adjusting the amplitude. And the intensity is controlling the same thing, though using a single value to control both the x and y amplitude scale is useful. The “frequency” is the time multiplied by the speed property that’s going into the sine function.
The bigger problem is the texcoord.x and texcoord.y values you’re using as the input for the sine. That’s the phase shift, and right now that (along with the amplitude) are going the be things that change the most between a sprite on it’s own and a sprite in an atlas. Multiply the texcoord .x and .y by the _TexelSize .z and .w respectively within the sin(). You’ll then either want to have a hard coded number or another material property to adjust the phase shift scale. Basically divide those by some arbitrary number of texels. The idea is you’re rescaling the phase shift to be in texels, which will keep the effect consistent between single and atlased sprites, assuming your art assets are using a consistent texel size.
For the amplitude “frequency” x and y values, you want to multiply those by the _TexelSize .x and .y values. Again this is to keep things scaled to texel sizes. Though in this case you’re using the xy values instead of the zw because you’re applying this to the texcoord used to sample the texture which by its nature needs to be in the normalized 0.0 to 1.0 range.
One thing to be weary of is for atlased sprites if you push the amount of distortion you do, you’ll start to see other sprites in the atlas. There no way to avoid this. The only solution is to not use an atlas for those sprites. Or to extract the sprite’s UV range and pass it to each sprite renderer’s material so the shader can clip() when the UV is outside of that range.
I believe I’ve implemented everything that you said but the only thing I wasn’t 100% clear on was creating the phase shift scale by dividing “those” by some arbitrary number of texels? I wasn’t quite sure on what you meant by those and left a comment on the line
I’ve modified my shader with the feedback. Does this look correct now?
The _TexelSize.z is the x dimension, _TexelSize.w is the y dimension. You’re apply those in reverse above to the texcoord.x and texcoord.y.
Eh … sort of. The frequency is how often the wave repeats its cycle (how frequently it repeats). The phase shift is an offset to the cycle’s start. What is the frequency and what is the phase shift depends a bit on if you see time as driving the frequency or if you see the position as driving the frequency. And then either the position or the time is then the phase shift for the time or position based frequency.
Personally I view time as driving the frequency and the position as the phase shift, but neither option is more correct. Alternatively you could refer to them as the temporal frequency and the spatial frequency instead of trying to figure out which one is the phase shift.
However, either way, you shouldn’t be dividing the speed by the _TexelSize.
In your code you’re using the texcoord as the phase shift, or spatial frequency. Multiplying the UV by the texture’s dimensions changes the spatial period of the sine wave to be across 6.28 (2 pi) texels instead of 6.28 texture widths. That’s going to be way, way to short a spatial period, hence why you need to divide that by some large value. The value you divide by is the number of texels you want the spatial frequency’s period to be. So, if you liked how it was working with the original sprite and it was 64 texels wide, you’d want to divide by around 400 (64 * 2 * pi = 402.1239) to get the same spatial frequency. If you liked how it looked with the atlas, and the atlas was 1024 texels wide, then divide by around 6400 (1024 * 2 * pi = 6433.9818). Etc. Or add that as another property you can set.
Once you do that, the _Speed value you already have will let you set a consistent temporal frequency without needing to multiply or divide it by anything as you’re already doing the corrections to the sine wave’s appearance and frequency by modifying it by the texcoord inputs.