- 8 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
👉 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
Category
📚
LearningTranscript
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
Comments