Screen Position, Shaders For People Who Don't Know How To Shader

Last time we learned about making a shader that pulses to audio. In this lesson we're going to go back to what we learned in the lesson on making a scanline effect to learn how to put a scanline effect on a 3d object in the scene.

Setup

Have a scene open in Unity with a 3d model in the hierarchy. In this lesson I'm using the Michelle model from mixamo but you can use any model you want, including any of the basic shapes included with Unity.

Anywhere it makes sense in your project, like Assets/Shaders/Lit, right click and select Create > Shader > Standard Surface Shader and name the new shader LitScanlines

Where you keep your models and materials, make a new material also called LitScanlines and assign the new shader to the material and put the material on the model in the scene. 

Go ahead and arrange your lighting and camera so you can see everything clearly and things look nice. This is what I have:

Surface Shader Scanlines

We're just gonna copy over an effect we've already made, just for a 3d object instead of the screen, so let's put our scanline code into our new shader. Open the new shader in Visual Studio. Change the top line to Shader "Xibanya/Lit/LitScanlines" 

First things first, we can clean up the template by deleting the comments and the instancing buffer stuff. Next, open up the scanline shader we made. Copy everything from the frag function into the surf function.

Of course, we can't use it like this. We're also missing the _Speed and _Height properties. Let's go ahead and add those now.

In the surface function, we won't return a color as we do in a frag function, we'll write it to our albedo value, so let's update the last line to do that, as well as add back the glossiness and metallic sliders.

And of course, we're not getting our texture coordinates from i.texcoord, we're getting them from IN.uv_MainTex, so we should replace that now too.

Save and go back to Unity. You won't see anything. Remember, we're using the main color for these scanlines, change it to something other than white and they'll show up!

Hm. About that.

ScreenPos

These lines aren't evenly scaled and aren't horizontal because we're drawing them using the texture coordinates. Many models use texture coordinates that are laid out for the most efficient use of the space, and that often means rotating UV islands to fit, even if it means they're sideways or upside down.

If we're drawing scanlines, we want them to be horizontal no matter what, but there's no guarantee that the UVs of any given model will align with the actual orientation of the object. Instead, we'll want to use screen position.

In a surface shader it's very easy to get the screen position, we just need to add it to our Input struct like this:

Then we can substitute that for uv_MainTex.y in the scanline calculation like this:

Save and head back to Unity.

Well, that's...better, but there's some weird distortion, particularly on parts of the model not directly facing view.

We need to not just account for the vertical position of the pixel we're coloring, we need to account for its depth in the scene. Conveniently we've been provided that value in IN.screenPos, it's the fourth value in the float4, IN.screenPos.w. Tweak the shader code to do this:

Hey, it works! Egh, but the effect is overall hard to see. Since this shader is lit, the shadowed areas make it hard to distinguish the scanline. Instead of using _Color, let's add a new property, _ScanColor, and write the scanline to the emissive value instead.

Save and head back to Unity. Set your main color back to white and set the scanline color to something cool.

Yaa that looks good. Set your ambient light color to black and put on a dark skybox to really see the effect pop. For extra credit, throw on a post processing volume with a little bit of bloom.

Still, it gets me to thinkin, it's nice as a proof of concept, but what is the point even of putting scanlines on this 3d character when we have our perfectly nice post processing effect that we already made? Well, why don't we add some zazz that we can only do in 3d? Let's combine this with another 3d effect we already know how to make - the fresnel rim!

Scanline Rim

Let's add our properties for a fresnel rim. We've done this loads of times so I know you're an expert already!

Remember that a fresnel rim is based on view direction, so we'll need to add viewDir to our input struct.

Then all we need to do is calculate the rim and multiply that along with everything else when we write the emissive value.

(If you want to know how this rim calculation works, I explain it thoroughly in the lesson in which this technique was first introduced!)

Save and go back to Unity.

Hum, I don't see anything...

Well, let's increase that Rim Fill value.

Aw yeah! That's rad!!

The shader we made in this tutorial is attached as LitScanlines.shader. If you have any questions or want to share what you come up with, let me know in the comments here, on Twitter, or in Discord. And if this tutorial helped you out, please consider becoming a patron!   

This tutorial is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike4.0 International License. The code shared with this tutorial is licensed under a CreativeCommonsAttribution 4.0 International License. 


Team Dogpit released this post 3 days early for patrons. Become a patron

Become a patron to

40
Unlock 40 exclusive posts
Be part of the community
Connect via private message