Earthbound-style battle background shader!
A few days ago I wrote my own Earthbound inspired shader to make these rad backgrounds. I'm not ready to announce what part Go Go Kudamono this is for, but I have gotten several requests from folks asking how I made this shader, so I thought I'd make this post devoted to the process. Also, please excuse any spelling errors / typos... I'm dyslexic and rarely catch them :p
NOTE: any shader code is written in the formal Unity likes. I'm not very experienced with writing shaders and am not familiar with any other shader coding.
STEP 1: Understand what is happening in the EB backgroundsBefore you can code any effects, you need to understand which effects are being employed. I found this nifty read-me [https://github.com/gjtorikian/Earthbound-Battle-Backgrounds-JS/blob/gh-pages/README.md] that details some of the effects written by a gentleman who wrote a javascript version of the EB backgrounds.
He says:
There are also three types of distortions that use the result of the Offset function:
- Horizontal translations, where each line is shifted left by the given number of pixels
- Horizontal interlaced translations, where every other line is shifted right by the given number of pixels
- Vertical compression translations, where each line is shifted up or down by the given number of pixels
Unfortunately for me, there are definitely a few more things happening in these backgrounds that just sin distortion. From what I can tell there's also a color shift, where the colors of each distorted background shift from one location to the next, and an overlay allowing 2 distorted & color shifted backgrounds to simultaneously occur.
I couldn't find any info online on how exactly these are done, so I took my best guess.
STEP 2: Distortion EffectsI use 4 variables (per distorted background) in my shader to control the distortion effects:
_EffectTypeA
_MagnitudeA
_FrequencyA
_SpeedA
Each of these variables end in an "A" because I have 2 sets of these, one for each background that can be overlaid.
EffectType is an integer that controls which effect is being used. 0 = sin horizontal 1 = sin interlaced 2 = sin vertical.
Magnitude, Frequency and Speed all effect the size and speed of the waves.
In that same readme he gives the formula for the effect:
Offset (y, t) = A sin ( F*y + S*t )
where:
y is the vertical coordinate being transformed
t is time that's elapsed
A is the amplitude
F is the frequency
S is the speed or frameskip of the transformation
In my shader this formula is re-written as:
SIN X// the amount to displace each pixel
float2 disp = float2(0,0);
// calculates how much to displace the x coordinate based on the variables above, time and the y coordinate.
disp.x = _MagnitudeA * sin(_FrequencyA * i.uv.y + _SpeedA * _Time.x);
// the color of the pixel
float4 col = tex2D( _Pattern1, i.uv + disp);
SIN YSame as above, but I displace the Y based on the Y rather than X based on Y:
disp.y = _MagnitudeA * sin(_FrequencyA * i.uv.y + _SpeedA * _Time.x);
SIN INTERLACEDSame as SIN X, but on every other pixel I multiply the displacement by -1.
// Using the # of vertical pixels on screen, I check if it's even or odd to produce 1 or -1 every other pixel.
float everyOther = 1 - ((floor(i.uv.y * _GameResolutionHeight) % 2) * 2);
// same as SIN X
disp.x = _MagnitudeA * sin(_FrequencyA * i.uv.y + _SpeedA * _Time.x);
// make the distortion negative every other time.
disp.x = disp.x * everyOther;
STEP 3: Color shiftThis one's a bit tricky. On the SNES, sprites were assigned color pallets, so shifting colors was as easy as rotating the palette. In unity sprites/textures are images don't have their palettes stored separately.
To get around this, I wrote a script that converts a background image into two new pngs: The first is a black & white version of the pattern. The second is a color pallet that's the number of colors in the image wide, and 1px tall. I then can use the value of any pixel in the black and white image to correspond to each pixel of the palette image. 100% white being the left most pixel on the palette, and 100% black being the right-most pixel. With this setup I'm able to put a modifier on the value % I'm using to look up the pixel to shift the color.
I ended up just using another sin wave to move this % up and down
// gets the value from 0 to 1 for the pixel, representing it's value from white to black, which I can use to look up a corresponding spot on the palette.
float colorValueB = ((colB.r + colB.g + colB.b) / 3);
// the wave that shifts the color value. You can play with this to get some differing results
float colorOffsetB = sin(_Time.x * _ColorCycleSpeedB);
// look up the color to show from the palette
float4 colorB = tex2D(_PaletteB, float2(colorValueB - colorOffsetB, 0));
You could also just do a linear color change, which is what I think Earthbound does:
colorOffsetB = _Time.x * _ColorCycleSpeedB;
STEP 4: Combining the PatternsFor this I had to do some guesswork. I was pretty sure that one of the few color combining features the SNES supported was color add, a simple ColorA + ColorB = ColorC, so this is what I opted for.
I did add one step extra by adding an intensity slider on each of the two patterns, so I was able to control how intensely that color was combined. Most likely in the SNES version, half of each color was added to each other to create a new overlapping color.
float4 finalColor = (colorA * _IntenselyA) + (colorB * _IntenselyB);
Most likely in Earthbound this was just set to an equal mix something like:
finalColor = (colorA * 0.5) + (colorB * 0.5);
I hope ya'll found that interesting / helpful!