[SPOILERS] Fun with shaders for LD37, part 3!

Posted by (twitter: @avaskoog)
December 18th, 2016 5:03 pm

Hey! Posted part 1 and part 2 earlier about some of the custom shaders that were written for our LD37 entry, LOCK AND RELOAD. c:

Since the posts easily get a bit lengthy, I decided to divide this into a series of more bite-sized posts so that I don’t have to sacrifice all too much clarity for the sake of space. This is part three, about two shaders that make use of UV scrolling (sort of) and number two is a spoiler, so do play the game first! c;

(ノ◕ヮ◕)ノ*:・゚✧

Some of the effects are connected to slightly fundamental parts of the story, and since the game ended up being really short due to time constraints, I recommend you play the game first to avoid the spoilers and get the most out of the game with it’s little twists! So I’ll be adding a “read more” tag at to some parts to hide some of the stuff from the front page of the blog.

Note: the game was made in Unity and the shaders were written accordingly and so any code will be given as boiled down versions of the shader code written for that, but obviously everything is readily convertible to something like GLSL with minor to no changes.


 

Animated TV noise

In the small room that the game takes place there is a television set, and at the start of the game you’ll find that the screen is turned on, but showing nothing but static. Moving static, of course, and not just a frozen image!

View post on imgur.com

So how does this work? We could of course have gone super fancy and generate noise programmatically right there in the shader, but since we were not looking to do anything procedural here, that was completely unnecessary, and we’re only using a static texture with the noise already in it. The movement comes from randomly jumping around (rather than scrolling) the UV coördinates! ᕕ( ᐛ )ᕗ

UV scrolling

What does it mean to “scroll UV’s”, or “move UV coördinates around”? Well, the UV’s are what determine where and how textures will map onto a mesh/model. By moving the x and y positions of the UV’s along the surface onto which a texture is being rendered, the position of the texture will move. There are many neat uses for this, so let’s look at a few before we move on, just to make sure you understand! (◕‿◕✿)

View post on imgur.com

A waterfall is easy to achieve by scrolling a repeating texture vertically along a simple rectangular plane. In this particular case, there are multiple layers of textures moving at different speeds on top of each other, as well as some particle systems to help sell the effect further.

This next GIF was recorded from this YT video from the channel ClassicGameJunkie and their show How did they do that?, so go check that cool channel out! Its creator is also working on the 3D platformer Clive ‘N’ Wrench!

View post on imgur.com

The flat, cartoony eyes in The Legend of Zelda: Twilight Princess are best animated by scrolling the UV’s of the texture around the flat geometry rather than trying to actually move any vertices.

The noise texture

I admit! The briefing on UV scrolling was a filler, to some extent, because there isn’t very much to say about it. It’s very easy, at least now that you know how the concept works! c:

When UV’s scroll enough that we’re able to see one repetition of the texture meet another, there are two ways to go about rendering it. It can either be repeated/tiled, or it can be clamped. You may have seen these option before when looking at a texture in Unity’s inspector. To make it clearer what’s going on, let’s look at the noise texture in both modes:

ld37_shd_rep_clm

As you can see, clamping means that the last pixel of each side of the texture will be repeated indefinitely in the corresponding direction, while repeating means, well, repeating. This is what we want, since we want to be able to move to random locations on the texture and still get the TV screen filled up nicely with static.

The noise shader

Finally! As you would know, perhaps from the previous part, the basic way of going about shaders is to run the program on each pixel, or fragment, of something to be rendered, and process the final colour and alpha to output for each. To get the colour from the currently processed pixel, we just use the UV coördinate in the texture that we got from the previous part of the shader, the vertex part. Like so:

fixed4 col = tex2D(_MainTex, i.uv);

What we can do now is to add some numbers to the UV’s—an offset—that will move to a different part in the texture. We want this to happen in a way that looks random, and we want erratic jumping, not smoothly sliding over to the next part in the texture.

The thing to do when one does want to move smoothly around a texture would be to move by a constantly increasing value, like the game’s time. This might alternatively be plugged into some function, like cosine or sine, to move around in wavy or circular fashions.

We won’t do exactly that, of course, but we still need the time variable to have the conditions keep changing. What we can do to get the jagged movements we want is to make use of the modulo operator to see if the current time is divisible by some number, and use that as the basis of our stuff. We’ll even use some wave functions!

Since the rounded number will remain divisible by something until it has changed into something else that no longer is divisible by that number, we’ll be seeing each position where the UV’s end up for at least a few frames, which is of course what we want. We want this to change over time, so we’ll modulo the time by the time itself, but multiply each operand by a different number.

float factor = (time * 10) % (time * 22);

Something like that! That determines how much we move. Now to determine how far and where to move! We’ll use cosine and sine for this, again plugging in the time, multiplied perhaps by something else (or even nothing at all—i.e. 1.0—this time). This will make it move around in circles, but of course we won’t be able to see that as we only skip around on those circles.

float2 movement = float2(cos(time), sin(time) * distance;

The distance variable ensures that we move about a fair distance on the texture, so that the difference between each position is clear. And honestly, we might as well plug in something like time % 100 in there as well to make the noise as varied as possible!

And so we modify our initial line of code:

fixed4 col = tex2D(_MainTex, i.uv + movement * factor);

That’s it! Incredibly cheap cheating for some fake randomness that looks good enough for something like this, where the texture is already very random to begin with. I only used a texture that’s big enough to precisely cover the screen, but in retrospect I’d say this works even better with a texture that’s much bigger than the TV screen, to make any repetitions less obvious!

 


 

!!!ヽ(゚Д゚)ノ NEXT UP IS A SPOILER, SO THE “READ MORE” TAG WILL NOW BE INSERTED! ヽ(゚Д゚)ノ !!!

Thus you may want to play the game before you move on! c;

 


 

Flickering TV footage with more noise

So if you, hopefully, have played the game, you’ll end up seeing something more than static on that television screen. Just in case, I still won’t say what it actually really is, but what you’ll see is this:

View post on imgur.com

Might be difficult to see with the lo-fi style that this game has going for it, but the flicker is the very same noise texture from before. In addition there’s a a character against a background that’s supposed to be giving off the appearance of talking on the video. The image is actually completely unanimated, because it didn’t feel necessary thanks to the style, but it does move around a little bit to make it seem just a tad livelier!

Multiple textures

This all means that this time around the shader needs to accept two inputs: the character texture and the noise texture. To do this, we add another texture at the top of our shader code, which in Unity goes like so:

_MainTex ("Albedo (RGB)", 2D) = "white" {}
_NoiseTex ("Albedo (RGB)", 2D) = "white" {}

Now we can sample them individually, perhaps like so, depending on what you name your variables:

fixed4 colMain = tex2D(_MainTex, i.uvMain);
fixed4 colNoise = tex2D(_NoiseTex, i.uvNoise);

Moving the picture around

Again with the UV scrolling. This is another case of a circular motion using cosine and sine, only this time we aren’t really making it jagged, but leave it smooth. The idea was that this was supposed to emulate the character holding the camera in their hands and being slightly shaky. Something like this:

fixed4 colMain = tex2D(_MainTex, i.uvMain + fixed2(cos(time), sin(time)) * distance);

Adding the noise at random intervals

We do basically the same thing to the noise texture’s colour that we did before to have it hop to random UV’s. Then we need to overlay it randomly on top of the main image. Again we figure out some calculation using the modulo operator in order to have a value that’s either 0 or 1 sometimes, and then use that value to determine whether or not to draw the image, and do something like this:

colMain.rgb = colMain.rgb - oneOrZero * (colMain.rgb - colNoise.rgb);

This way if oneOrZero is simply zero, nothing is subtracted, and we just get the main colour, but if it is one, we’ll subtract the main colour (removing it) and add the noise colour, but this means that the noise will completely replacing the main texture:

ld37_sdh_tv2

This might be fine, but for this game we wanted the noise only to slightly overlay the main texture, so a factor less than one to multiply the noise contribution was necessary. In fact, we want to control the main texture’s contribution separately as well. For example:

fixed contrMain = 0.6;
fixed contrNoise = 0.7;
colMain.rgb = colMain.rgb - oneOrZero * (colMain.rgb * (1.0 - contrMain) - colNoise.rgb * contrNoise);

ld37_sdh_tv1

There we go!

 


 

Bonus: scan lines across screen

Now for something that’s not actually in the game, but which might be fun to do anyway, just to play around a little more with this. Let’s add some lines moving across the screen!

View post on imgur.com

Difficult? Extra textures? Well, uh, technically, nah. All we need is again that time variable and some maths! The lines are just black, right? So we just need to decrease the colours where we want the lines.

It’s actually quite similar to how the checkerboard pattern was calculated on top of the whole image, which you could read about in part 1! By using the modulo operator to get the remainder when dividing the current pixel’s y position in the texture (i.e. its UV v position) we can separate odd values from even values and get every other one. That way we could colour every even vertical line of horizontal pixels black and leave every odd line untouched. Since UV’s are normalised between 0.0 and 1.0, a line will be as tall as the entire TV screen, so what we do is simply to scale the coördinates down! ᕕ( ᐛ )ᕗ

So, it goes something like this:

fixed2 offset = fixed2(0.0, time * speed);
fixed2 pos = i.uvMain + offset;

By multiplying the time by some speed factor (probably less than one) we can make the lines move down the screen at the desired speed. Now we need a scale factor to to make the lines as thick as we want them. Due to the ways UV’s work, we’ll actually have to divide by the factor to make the lines smaller if the factor is less than one rather than multiplying if we want it to make any sense.

We’ll also add a variable for contribution, which will basically be the alpha value of the lines as they’re overlaid on top of the main texture, as we don’t actually want them to be opaque black. And so we can accumulate all of that into a few more lines of code:

fixed scale = 0.095;
fixed contribution = 0.15;
float lines = ((pos.y / scale) % 2) * contribution;

Finally we can blend the lines on top of the main texture!

colMain.rgb -= colMain.rgb * lines + lines;

Now, the effect will be different depending on whether we put it before or after we also blend the noise texture on top of the main texture, since if we do it after, then the lines will end up on top of the noise and there won’t be any noise where the lines are:

ld37_shd_lines_firstlast

 

That’s it for this part! In the next one (and I won’t be posting it right away as I don’t want to be spammy), we’ll be talking about the final shader that required extra cameras and render textures, which will be a BIG SPOILER, so play the game first! c:

Hold out for the next part of our lineup! ☆

Tags: , , , , , , , , , , , , , , , , , , , , , , , , , ,


Leave a Reply

You must be logged in to post a comment.

[cache: storing page]