Outline shader (a bit iffy but it works)

Hello people. I noticed that the old shaders for cell shading/outline effects didn’t work for the new playcanvas updates and most of the githubs code for them are now redacted/deprecated. I decided I wanted a outline post effect shader for my game, so I made one. There are issues with it, but my hope is releasing it here other people could adapt the code and make it better. See my current results here:



Basically the script/shader adds an outline to objects in a scene by comparing the depth of pixels from the cameras depth buffer, If there’s a big enough difference in depth between neighboring pixels (controlled by the Depth Threshold which is an attribute) an outline is drawn using the Outline Color and Thickness number attributes. basically, it detects edges based on how far apart surfaces are in 3D space and highlights them with a outline.
see the launch here: PlayCanvas | HTML5 Game Engine
for this result and best results, I reccomend not going over 2.5 outline thickness since outline artifacts occur if you do… this is the best settings for my scene, but play with it as you like:
Screenshot 2024-12-21 8.57.01 AM
since we do use the depth of the scene, for the camera with the script you apply it to you have to tick renderSceneDepthMap or it wont work, seen here:
image
the script is here, if you do fork my project, and adapt it please share your results and what you updated or any recommendations for future updates.
Anyways, hope someone finds this useful. :grin:

4 Likes

Forgot to mention, since I use the depth buffer, shadow outlines tend to “bleed” like spilled ink, and look horrible up close, the best I can say is play with the attributes until you find something you’re comfortable with, if someone knows how to fix this please let me know. Thanks!

1 Like

Actually the issue is that the shader doesn’t use the depth buffer. I’m currently trying to fix it but for some reason the camera isn’t providing a depth buffer despite depthbuffer grabpass being enabled.

1 Like

Weird, maybe the api is different and so I may be using deprecated functions. I’ve been away for some time so I’m not 100% on all the code, if you find it please let me know! :grin:

Yeah in this block of code

if (this.device.sceneDepthMap) {
    scope.resolve("uDepthBuffer").setValue(this.device.sceneDepthMap);
} else {
    console.warn("Depth buffer not available! Make sure the camera has 'Render Depth Map' enabled.");
    scope.resolve("uDepthBuffer").setValue(inputTarget.colorBuffer); // Fallback
}

The this.device.sceneDepthMap is so depreciated that the property isnt even listed under the graphicsDevice docs.

1 Like

see how the depth buffer is enabled and used in a shader here:
https://playcanvas.vercel.app/#/graphics/ground-fog

you don’t assign anything to the scope, you simply call this in shader to use it. The linked examples does this:

float sceneDepth = getLinearScreenDepth(screenCoord);

this gives you depth between near and far plane of the camera.

Also see how this is added to the fragment shader for this function to be available there: pc.shaderChunks.screenDepthPS

1 Like

Thank you for this, it definitely pointed me in the right direction. However, the shader now doesn’t render any outlines.

uniform sampler2D uColorBuffer;
uniform sampler2D uDepthBuffer;
uniform vec2 uResolution;
uniform float uOutlineThickness;
uniform vec3 uOutlineColor;
uniform float uOutlineDepthThreshold;

varying vec2 vUv0;
                
float getDepth(vec2 uv) {
    return getLinearScreenDepth(uv);
}
                
void main() {
    vec2 texel = vec2(1.0) / uResolution;
                
    float center = getDepth(vUv0);
    float left = getDepth(vUv0 + vec2(-texel.x * uOutlineThickness 0));
    float right = getDepth(vUv0 + vec2(texel.x * uOutlineThickness 0));
    float up = getDepth(vUv0 + vec2(0 texel.y * uOutlineThickness));
    float down = getDepth(vUv0 + vec2(0 -texel.y * uOutlineThickness));
    
    float edge = step(uOutlineDepthThreshold abs(center - left)) +
                 step(uOutlineDepthThreshold abs(center - right)) +
                 step(uOutlineDepthThreshold abs(center - up)) +
                 step(uOutlineDepthThreshold abs(center - down));
                
    vec4 sceneColor = texture2D(uColorBuffer vUv0);
                
    if (edge > 0.0) {
        gl_FragColor = vec4(uOutlineColor 1.0);
    } else {
        gl_FragColor = sceneColor;
    }
}

If you could provide any input on why that may be the case, it would be amazing.

1 Like

is this the full script? It looks lacking in most of the definitions and attributes + the actual usage inside the same script, the shader definition + the script definition to apply it to the camera. If it does have the full script could you send that so I could test it out? I’m a bit busy today and the shader isn’t 100% my top priority so I’ll check it out when I can, thanks!

Its not the full script, just the fragment shader.

1 Like

I mean to me it looks right, but it doesn’t work for me when I test it either so I have no clue what’s going wrong. The shader api is also not well documented for custom shaders since the last update to my knowledge.

In my code I just deleted the call for the depthbuffer, so it doesn’t loop and call it over and over.