Custom PostEffects Shaders

Hi @Leonidas! Or anyone else browsing!

I think I made some additional progress with this shader. Looking through the blog post you provided, and finding this post on StackExchange: https://stackoverflow.com/questions/28095508/lwgl-reconstruct-position-in-fragment-shader , I found that some of the more obvious normal issues on large polygons have cleared up, but the outlines still don’t come out straight.

I was wondering if you might be able to tell me if I went wrong somewhere, or if there is something else I can search for to get additional ideas.

As always, your advice is appreciated! Here is the script as it sits now:

// --------------- POST EFFECT DEFINITION --------------- //
Object.assign(pc, function () {

    /**
     * @class
     * @name pc.ToonShadeEffect
     * @classdesc Implements the ToonShadeEffect post processing effect.
     * @description Creates new instance of the post effect.
     * @augments pc.PostEffect
     * @param {pc.GraphicsDevice} graphicsDevice - The graphics device of the application.
     */
    var ToonShadeEffect = function (graphicsDevice) {
        pc.PostEffect.call(this, graphicsDevice);

        this.needsDepthBuffer = true;
        this.matrix = new pc.Mat4();

        this.shader = new pc.Shader(graphicsDevice, {
            attributes: {
                aPosition: pc.SEMANTIC_POSITION
            },
            vshader: [
                "#version 300 es",
                "in vec4 aPosition;",
                
                "",
                "uniform mat4 matrix_viewProjectionInverse;",
                "uniform mat4 matrix_viewProjection;",
                "out vec3 WorldPos;",
                "out vec2 vUv0;",
                "",
                
                "void main(void)",
                "{",
                
                "gl_Position = vec4(aPosition.xy, 0.0, 1.0);",
                "vUv0 = (aPosition.xy + 1.0) * 0.5;",
                "WorldPos = (aPosition * matrix_viewProjection).xyz;",
                "}"
            ].join("\n"),
            fshader: [
                "#version 300 es",
                "precision " + graphicsDevice.precision + " float;",
                "",
                "in vec2 vUv0;",
                "in vec3 WorldPos;",
                "",
                "uniform sampler2D uColorBuffer;",
                "",
                "uniform sampler2D uDepthMap;",
                "uniform vec4 camera_params;",
                "uniform vec2 uResolution;",
                "uniform mat4 matrix_viewProjectionInverse;",
                "uniform mat4 matrix_viewProjection;",
                
                "float linearizeDepth(float z)", 
                "{",
                    "z = z * 2.0 - 1.0;",
                    "return 1.0 / (camera_params.z * z + camera_params.w);",
                "}",

                "float getLinearScreenDepth(const in vec2 uv)",
                "{",
                    "return linearizeDepth(texture(uDepthMap, uv).r) * camera_params.y;",
                "}",

                "vec3 getViewNormal( const in vec3 viewPosition )",
                "{",

                    "return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) );",
                "}",

                "vec3 getViewPositionFromDepth( const in vec2 screenPosition, const in float depth)",
                "{",
                  "vec3 normalizedDeviceCoordinatesPosition;",
                  "normalizedDeviceCoordinatesPosition.xy = (2.0 * screenPosition) / uResolution - 1.0;",
                  "normalizedDeviceCoordinatesPosition.z = 2.0 * depth - 1.0;",


                  "vec4 clipSpaceLocation;",

                  "clipSpaceLocation.w = matrix_viewProjection[3][2] / (normalizedDeviceCoordinatesPosition.z - (matrix_viewProjection[2][2] / matrix_viewProjection[2][3]));",
                  "clipSpaceLocation.xyz = normalizedDeviceCoordinatesPosition * clipSpaceLocation.w;",

                  "vec4 eyePosition = vec4(WorldPos , 0.0) - (matrix_viewProjectionInverse * clipSpaceLocation);",

                  "return eyePosition.xyz / eyePosition.w;",

                "}",

                "mat3 calcLookAtMatrix(const in vec3 origin, const in vec3 target, const in float roll)",
                "{",
                  "vec3 rr = vec3(sin(roll), cos(roll), 0.0);",
                  "vec3 ww = normalize(target - origin);",
                  "vec3 uu = normalize(cross(ww, rr));",
                  "vec3 vv = normalize(cross(uu, ww));",

                  "return mat3(uu, vv, ww);",
                "}",

                "vec3 getRay(const in vec3 origin, const in vec3 target, const in vec2 screenPos, const in float lensLength)", 
                "{",
                  "mat3 camMat = calcLookAtMatrix(origin, target, 0.0);",
                  "return normalize(camMat * vec3(screenPos, lensLength));",
                "}",

                "vec2 squareFrame(const in vec2 screenSize, const in vec2 coord)",
                "{",
                 "vec2 position = 2.0 * (coord.xy / screenSize.xy) - 1.0;",
                  "position.x *= screenSize.x / screenSize.y;",
                  "return position;",
                "}",

                "vec3 getDeltaNormal(const in vec2 uv)",
                "{",

                  "float depth = getLinearScreenDepth(uv.xy);",
                  "vec3 viewPosition = getViewPositionFromDepth( uv.xy, depth);",
                  "return getViewNormal(viewPosition);",
                "}",

                "vec2 getDeltas(const in vec2 uv)",
                "{",
                  "vec2 pixel = vec2(1. / uResolution.xy);",
                  "vec3 pole = vec3(-.75, 0, +.75);",
                  "float dpos = 0.0;",
                  "float dnor = 0.0;",

                  "float d0 = getLinearScreenDepth(uv + pixel.xy * pole.xx);",
                  "float d1 = getLinearScreenDepth(uv + pixel.xy * pole.yx);",
                  "float d2 = getLinearScreenDepth(uv + pixel.xy * pole.zx);",
                  "float d3 = getLinearScreenDepth(uv + pixel.xy * pole.xy);",
                  "float d4 = getLinearScreenDepth(uv + pixel.xy * pole.yy);",
                  "float d5 = getLinearScreenDepth(uv + pixel.xy * pole.zy);",
                  "float d6 = getLinearScreenDepth(uv + pixel.xy * pole.xz);",
                  "float d7 = getLinearScreenDepth(uv + pixel.xy * pole.yz);",
                  "float d8 = getLinearScreenDepth(uv + pixel.xy * pole.zz);",

                  "dpos = (",
                    "abs(d1 - d7) +",
                    "abs(d5 - d3) +",
                    "abs(d0 - d8) +",
                    "abs(d2 - d6)",
                  ") * 0.5;",

                  "vec3 s0 = getDeltaNormal(uv + pixel.xy * pole.xx);",
                  "vec3 s1 = getDeltaNormal(uv + pixel.xy * pole.yx);",
                  "vec3 s2 = getDeltaNormal(uv + pixel.xy * pole.zx);",
                  "vec3 s3 = getDeltaNormal(uv + pixel.xy * pole.xy);",
                  "vec3 s4 = getDeltaNormal(uv + pixel.xy * pole.yy);",
                  "vec3 s5 = getDeltaNormal(uv + pixel.xy * pole.zy);",
                  "vec3 s6 = getDeltaNormal(uv + pixel.xy * pole.xz);",
                  "vec3 s7 = getDeltaNormal(uv + pixel.xy * pole.yz);",
                  "vec3 s8 = getDeltaNormal(uv + pixel.xy * pole.zz);",

                  "dpos += (",
                    "max(0.0, 1.0 - dot(s1.rgb, s7.rgb)) +",
                    "max(0.0, 1.0 - dot(s5.rgb, s3.rgb)) +",
                    "max(0.0, 1.0 - dot(s0.rgb, s8.rgb)) +",
                    "max(0.0, 1.0 - dot(s2.rgb, s6.rgb))",
                  ");",

                  "dpos = pow(max(dpos - 0.5, 0.0), 5.0);",

                  "return vec2(dpos, dnor);",
                "}",
                
                "out vec4 fragColor;",
                "void main(void)",
                "{",
                
                  "vec4 buf = texture(uColorBuffer, vUv0.xy);",
                  "float depth = getLinearScreenDepth(vUv0.xy);",

                  "float t = depth;",

                    "vec3 viewPosition = getViewPositionFromDepth( vUv0, depth);",
                    "vec3 nor = getViewNormal(viewPosition);",
                  "nor += vec3(.75);",

                  "vec3 col = texture(uColorBuffer, vUv0).rgb;",

                  "vec2 deltas = getDeltas(vUv0);",

                  "if (t > -0.5) {",
                    "col *= max(0.3, 0.5 + dot(nor, normalize(vec3(0, 1, 0.5))));",
                    "col *= vec3(1, 0.8, 0.35);",

                  "}",
                  "col.r = smoothstep(0.1, 1.0, col.r);",
                  "col.g = smoothstep(0.1, 1.1, col.g);",
                  "col.b = smoothstep(-0.1, 1.0, col.b);",
                  "col = pow(col, vec3(.75));",
                  "col -= deltas.x - deltas.y;",
                
                "",
                "fragColor = vec4(col, 1.0);",
                "}" 
            ].join("\n")
        });

    };

    ToonShadeEffect.prototype = Object.create(pc.PostEffect.prototype);
    ToonShadeEffect.prototype.constructor = ToonShadeEffect;

    Object.assign(ToonShadeEffect.prototype, {
        render: function (inputTarget, outputTarget, rect) {
            var device = this.device;
            var scope = device.scope;

             scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
             scope.resolve("uResolution").setValue(this.resolution);

             
            
            var matrixValue = scope.resolve("matrix_viewProjection").getValue();
            this.matrix.set(matrixValue);
            this.matrix.invert();
            scope.resolve("matrix_viewProjectionInverse").setValue(this.matrix.data);

            pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.shader, rect);
        }
    });

    return {
        ToonShadeEffect: ToonShadeEffect
    };
}());

// ----------------- SCRIPT DEFINITION ------------------ //
var ToonShadingv1 = pc.createScript('toonShadingv1');


ToonShadingv1.prototype.initialize = function () {
    this.effect = new pc.ToonShadeEffect(this.app.graphicsDevice);
    this.effect.time = 0;
    this.effect.resolution = [this.app.graphicsDevice.width, this.app.graphicsDevice.height];

    this.on('attr', function (name, value) {
        this.effect[name] = value;
    }, this);

    var queue = this.entity.camera.postEffects;

    queue.addEffect(this.effect);

    this.on('state', function (enabled) {
        if (enabled) {
            queue.addEffect(this.effect);
        } else {
            queue.removeEffect(this.effect);
        }
    });

    this.on('destroy', function () {
        queue.removeEffect(this.effect);
    });
};



1 Like