Skip to playerSkip to main content
  • 6 months ago
In this tutorial, I will show you how to get full screen outlines using "Scharr operator" Edge detection algorithm using Unity 6 in Universal Render Pipeline (URP).

👉 Resources!
Project files https://www.patreon.com/posts/132406000/

👉 Do you want to support my work? The best way to support this channel is to buy my game on Steam. https://store.steampowered.com/app/2527340/Cosmic_roads/

👉 Alternate ways to support!
https://www.patreon.com/DigvijaysinhG
https://github.com/sponsors/DigvijaysinhGohil
Transcript
00:00in this video we will talk about full screen outlines in unity again because my previous
00:05take was not good enough it simply breaks apart in a more complex scene so in this video instead
00:11of coming up with my own solution i will use an established edge detection technique like
00:17serval filter char robots cross etc for the tutorial i'm using unity 6 and my project is
00:24set up in urp so from the asset store i've downloaded a basic interior pack by unity technologies
00:32and it has this nice scene now first let's create a shader so create shader graph urp
00:43and then select full screen shader graph and i'm going to call it outlines
00:48then we need material for our shaders so let's create a material for that
00:57again i'm going to call it outlines then to apply this material select your renderer asset and then
01:07hit add render feature then select full screen pass render feature then in this past material slot let's
01:16apply our newly created material and i'm going to select the inject point before rendering post
01:23processing and if you've never created a full screen shader before i have an entire video about it
01:29where i've talked about all of this in much more details you should definitely check it out
01:35then in the shader graph open up the graph inspector and in the graph setting set the blend mode to
01:42alpha otherwise this alpha plot won't work now here i will implement an edge detection technique to find
01:48discontinuities in our scene we can find discontinuities in many different ways like
01:54based on the color data based on normals based on depth values etc and the operator i'm going to use
02:02is known as char operator and to understand it better let's first talk about some theory
02:07char operators are simply three by three kernels one for horizontal axis and one for vertical axis
02:16then let's say we have our depth texture and we are currently evaluating for this pixel so we will
02:22take our horizontal kernel put it on top of our depth texture like this then we will evaluate for this
02:29pixel so basically we will read the depth value and multiply it with our kernel value and we will do the
02:36same thing for all nine pixels and add the results together
02:43this will be our horizontal gradient we will call it gx then we will take the vertical kernel and again
02:51repeat the same process
02:56and this time we will call the value gy for vertical gradient then to calculate the actual gradient we
03:03we will simply use good old pythagoras and of course we will repeat the same process for every pixel on
03:10our screen then to get the outlines we will simply evaluate our gradient value against some threshold
03:18and this technique is a lot similar to gaussian blur unfortunately i don't have a detailed theory video
03:25on either of those topics so if you wish to learn more i can recommend captain disillusion's blur video
03:31all right now here i'm going to write some code and due to that i was really conflicted on whether
03:39to go for shader graph or just completely write the custom shader i've decided to stick with shader graph
03:46because in my opinion it would be easier to understand for beginners especially if you don't have any
03:51prior experience in writing shaders because unity's documentation for the topic lacks severely all right
03:59now first i'm going to create an hlsl file
04:09i'm going to also call it outlines and i'll change the extension to hlsl
04:22now in the hlsl file first i'm going to define a structure which will contain both our horizontal and
04:27vertical kernels so that we can access them easily so i'll go struct then let's call it char operators
04:39and then here i will go matrix three by three so float three by three let's call it x and one more for the
04:50y then our kernels will look like this so let me define a method to initialize them
04:56so first it will return our structure so char operators then i'll call the method get edge detection
05:07kernels and it won't take any parameters and here
05:15i'll go char operator kernels then kernel dot x equals flow three by three
05:22and in glsl and hlsl and in glsl both matrices are defined in column major order so
05:32for our horizontal kernel the first column is minus three minus 10 minus three so i'll go
05:38minus three minus 10 minus three then zero zero zero then three and three and three and our vertical
05:49kernel is just the transpose of our horizontal kernel so the first column is the first row for that
05:58second column second row third column third row so i could write kernels dot y equals transpose
06:06of kernels dot x and it would work just fine but in shaded land if you already know the value of a
06:14variable and it won't going to change it is a good idea to hard code that to avoid any extra calculation
06:21to save any kind of performance of course gpus are obese nowadays but still it's generally a good
06:29practice to follow again i'm going to hard code the vertical kernel as well so three by three
06:36and the first column is minus three zero three and second column minus ten zero ten third column minus three
06:49zero three and now that we have our kernels initialized let's return our structure just like that
06:58and now it's time to implement our actual edge detection logic to find discontinuities in our scene
07:05we can find discontinuities many way like with scene color scene normals depth texture but
07:14let's use depth texture for now so i'm going to go void then i'll call the method depth based outlines
07:26and i will use this method as my custom function node so i also have to specify this precision
07:32so underscore float and it will take two input parameters first being screen uv so float2
07:42screen uv and another float2 for pixel size then the function will output outlines as float so i'll go out
07:52float float outlines now here let's first get our kernels so charoperative kernels equals get edge detection kernels
08:04then i will specify my horizontal gradient so float gx equals zero and same thing for vertical gradient so for
08:14g y equals zero and now for my current pixel i need to evaluate the depth value for all adjacent pixels
08:21so i'm going to write two for loops
08:26four and i equals minus one i less than equals one i plus plus and one more with j
08:37and now if we take a look at our kernels for middle pixel it will be zero for both kernels anything
08:48multiply with zero will be zero so i can safely skip one iteration so i will go if i and j both are zero
08:57then i need to simply skip the iteration so continue
09:03then i'll define an offset so float2 offset equals float2 of i and j
09:11multiply with our pixel size now i can read the depth value so float d for depth equals sample
09:21theme depth with screen uv plus offset now i have this error because this function is not a built-in function
09:34i'm going to fix that in a bit but first let's go
09:38over horizontal gradient plus equals over depth multiply over horizontal kernel and then go i plus one
09:49and j plus one because index starts with zero and not d minus one and again this index is for our column
10:00and this index is for our row then same thing for the vertical gradient gy equals depth into kernel y
10:10then outside the loop let's calculate our actual gradient so float g equals good old spythagoras so
10:20square root of zx square plus gy square and now for the outlines i can evaluate the g value against some
10:27threshold so if g value is greater than that threshold i want one value otherwise i point zero so i can simply go
10:35outlines equal step function and let's try 0.02 and pass in the g all right now it's time to fix this error
10:48and as i mentioned earlier this is not a built-in method it is defined in declare depth texture dot hlsl file
10:56that too under the unity declare depth texture included keyword so i have to wrap this entire thing with
11:05hash if defined then unity declare depth texture included and then here i'll go hash and if and this is called
11:25conditional compilation so unity might go that hey you are not returning the outline values for every possible
11:33path so before i do all of this i'm simply going to initialize my outline values with zero
11:40then in our shader let's create a custom function node open up the graph inspector then this custom function
11:48needs two input parameters so let's define them first being screen uv and it will be of type vector 2
11:58and another vector 2 pixel size then it will output float value let's call it outlines then set the type to
12:12file and for the source let's select our hlsl file and for the function name i have to write the exact name
12:20of my method excluding that precision so depth based outlines
12:30and now we have our own custom function pretty cool now let's define both this input so first let me create
12:39screen position node
12:40it will be our screen uv's now this pixel size is basically our outline thickness and obviously i want
12:50to access that from the inspector so let me create a new float property let me call it outline thickness
12:59thickness and i messed up the spelling and let me set the mode to slider that goes from 0 to 3 and let me set
13:11the default value to 1 drag it in now i need to convert this outline thickness value to pixels
13:18and i can do that by simply dividing it with my screen resolution
13:22so i'll go divide then i will divide it with my screen resolution so i can use screen node
13:33i can access width and height let's combine them in vector 2
13:38and use it in the divide node all right now we have the pixel size here let's speed it here
13:48and let's visualize our outlines and now i have that phase outline but there's one issue
13:54like for example this plant so for this leaf fragment and this table fragment there's not much depth
14:03difference so i'm not getting the outline here same thing with this floor and this vase you can of
14:11course mitigate the issue by playing around with the threshold value in the step function but still
14:17depth based outline has its own issues so let's try to create the outlines based on scene normals
14:25all right now i can basically copy paste this function but i'm going to write it by hand
14:31so that you have some time to figure out what is going on now i'm not calling you slow by the way
14:37it's just that in any tutorial if the tutor copy paste and tweak some code it is actually
14:44hard to follow and put a lot of us off including myself so i'm going to write the new function so
14:53here i will go void this time normal based outlines
15:00and again the fluid precision will be flowed so 32-bit flow precision then
15:10same input parameters and output parameters then here again let's grab our kernel so
15:16i like this then let's define our gradients
15:26then loops
15:32and rider is pretty fast love that then i can again safely skip penetration so if i
15:40i equal equal equal zero i equal equal zero and j equal equal zero simply continue
15:50then let's define our offset then instead of reading depth let's read normals
15:58so flow three normals so flow three normals n equal sample
16:07scene normal
16:12with screen uv plus offset and now here i have a problem because i simply can't add
16:21need normal values on top of my gradient value because normals are float three while our horizontal
16:30gradient is float so what i'm going to do is i'm going to use the dot product between this normal
16:36and normal for our current pixel so before the loop let's go
16:41flow flow three cn for current normal equals again sample the normals
16:53green uv then here i will go float dp dot product equals dot of cn and n and now i can again go gx
17:06plus equals dp multiply over horizontal kernel and for gy over dp multiply over vertical kernel
17:17then again outside the loop let's calculate our gradient so float g equals square root of gx
17:24square plus gy square and then same thing outlines equal step with 0.02 and g and this time let's
17:34change this threshold value to 2 and again to fix these errors i have to go
17:44hash if defined then for this one i have to go unity declare normals texture
17:56included included and then i have to go hash and if then let's also initialize our outlines with zero so
18:09like that then let me copy this function name and let's head over to unity in our shader i'm going
18:16to duplicate this custom function node
18:21and this time i'm going to use normal based outlines function so we have a nice custom function for
18:31normal based outlines as well let's visualize that so now we have nice normal based outlines and it
18:39fix a lot of issues with the death but it also introduced another issue so here i have a plank floor
18:46and this is the bed now if i look at it from this angle here i don't get outline at the edge of the bed
18:55because this plank normals is pointing straight up and this bed normals are also pointing straight up so
19:02both of them are exactly parallel so so there is no angle difference between them hence i'm not getting
19:11this outlines so you can see that both this technique have its pros and cons like almost every other
19:18thing in graphics programming so to tackle this issue let's combine both these outlines so what i want is
19:27i want the depth based outlines where i'm not getting the normal based outlines so for that i'm going to
19:35use this normal based outlines and go one minus it will just invert the values so let's just visualize
19:44it so now i have value of white where the normal based outline doesn't exist and then i'm simply going
19:53to take to take its output and multiply it with the depth based outlines so here i will have depth based
20:01outlines only where there is no normal based outlines let's just visualize that so now you can see that
20:10here i have depth based outlines and here and here then let's add this on top of our normal based outline so
20:19here we go simply go add add if it does
20:31and now we have nice outlines now i also want to control the color of this outline so i'm going to
20:38create another property color let me call it outline color and i messed up the spelling again
20:49let's default it to black drag it in and multiply it with our outlines
21:05take its output and use it in the base color block so now i have black outlines which we can't see
21:12currently and now i obviously want to see the actual scene as well so i'll simply use the alpha
21:20with the swizzle node
21:24and use it in the alpha slot and now as you can see we have nice outlines which i can control from the
21:32inspector and i can also control the alpha or opacity
21:40pretty cool
21:41and this outline will work in more complex scene like this one as well now obviously like every
21:50technique there are some drawbacks first being that you can't go crazy with the outline thickness so let
21:57me just show you let's set this slider range to 30
22:04and then if i go put in some
22:10big thickness you can see that our effect is breaking apart
22:14so you have to be mindful of that the second drawback is this technique is expensive because
22:24let's head back to our code so here you can see that we are sampling full screen textures
22:31or like multiple times 8 and 8 16 and 17 times in total so that's bad
22:43then the third drawback is this approach won't work with transparent objects
22:48and i'm sure i will get questions about is there any way to omit the objects from the outlines
22:57well i could try with render queue but honestly speaking i don't think there is
23:05any easy way to omit the particular objects with this full screen approach
23:11for that you probably have to go back to per material or per object outlines with like
23:18inverted hoe method which i also have a video about it and i might rework that in the future as well
23:25let me know in the comments hopefully you find this video helpful if you do hit that like button
23:31and if you haven't already please consider subscribing it would massively help me secure potential sponsor
23:37deals in the future also you can get the project files from my patreon that's it from me and i'll see you in the next one
Be the first to comment
Add your comment

Recommended