the project may have been deleted to free up space, luckily I still have the code, and so I can share it here:
//--------------- OUTLINE POST EFFECT V 0.5 ------------------------//
pc.extend(pc, function () {
var OutlinePostEffect = function (graphicsDevice) {
this.shader = new pc.Shader(graphicsDevice, {
attributes: {
aPosition: pc.SEMANTIC_POSITION
},
vshader: [
"attribute vec2 aPosition;",
"varying vec2 vUv0;",
"void main(void) {",
" gl_Position = vec4(aPosition, 0.0, 1.0);",
" vUv0 = (aPosition.xy + 1.0) * 0.5;",
"}"
].join("\n"),
fshader: [
"precision " + graphicsDevice.precision + " float;",
"uniform sampler2D uColorBuffer;",
"uniform sampler2D uDepthBuffer;",
"uniform vec2 uResolution;",
"uniform float uOutlineThickness;",
"uniform vec3 uOutlineColor;",
"uniform float uOutlineDepthThreshold;",
// NEW UNIFORMS
"uniform float uNearClip;",
"uniform float uFarClip;",
"uniform float uMaxOutlineDistance;",
"varying vec2 vUv0;",
// Helper: linearize depth from [0..1] to actual distance in camera space
"float linearizeDepth(float depth) {",
" // Convert [0..1] depth to clip space [-1..1].",
" float z = depth * 2.0 - 1.0;",
" // Then convert to camera (eye) space distance.",
" return (2.0 * uNearClip * uFarClip) / (uFarClip + uNearClip - z * (uFarClip - uNearClip));",
"}",
// Get depth with a small check to keep skybox from messing with outlines
"float getSceneDepth(vec2 uv) {",
" float d = texture2D(uDepthBuffer, uv).r;",
" // If depth is basically 1.0, well treat it as if its the same as center to avoid edges against the skybox.",
" return d;",
"}",
"void main() {",
" vec2 texel = vec2(1.0) / uResolution;",
" // Read center depth",
" float centerD = getSceneDepth(vUv0);",
// If center is near 1.0, it’s sky. Just output color, no outline.
" if (centerD >= 0.99999) {",
" gl_FragColor = texture2D(uColorBuffer, vUv0);",
" return;",
" }",
" float leftD = getSceneDepth(vUv0 + vec2(-texel.x * uOutlineThickness, 0.0));",
" float rightD = getSceneDepth(vUv0 + vec2( texel.x * uOutlineThickness, 0.0));",
" float upD = getSceneDepth(vUv0 + vec2(0.0, texel.y * uOutlineThickness));",
" float downD = getSceneDepth(vUv0 + vec2(0.0, -texel.y * uOutlineThickness));",
// If neighbors are essentially sky, treat them like center depth so they don't produce edges.
" if (leftD >= 0.99999) { leftD = centerD; }",
" if (rightD >= 0.99999) { rightD = centerD; }",
" if (upD >= 0.99999) { upD = centerD; }",
" if (downD >= 0.99999) { downD = centerD; }",
" float depthDiff = 0.0;",
" depthDiff += step(uOutlineDepthThreshold, abs(centerD - leftD));",
" depthDiff += step(uOutlineDepthThreshold, abs(centerD - rightD));",
" depthDiff += step(uOutlineDepthThreshold, abs(centerD - upD));",
" depthDiff += step(uOutlineDepthThreshold, abs(centerD - downD));",
// Convert center depth to actual distance in camera space, for fade-out logic
" float linearCenter = linearizeDepth(centerD);",
// 0 -> no fade at camera=0, then fades to 0 once we pass uMaxOutlineDistance
" float fade = clamp(1.0 - (linearCenter / uMaxOutlineDistance), 0.0, 1.0);",
// final edge = depthDiff * fade
" float edge = depthDiff * fade;",
" vec4 sceneColor = texture2D(uColorBuffer, vUv0);",
" if (edge > 0.0) {",
" // Outline color",
" gl_FragColor = vec4(uOutlineColor, 1.0);",
" } else {",
" gl_FragColor = sceneColor;",
" }",
"}"
].join("\n")
});
this.resolution = new Float32Array(2);
// User-adjustable defaults
this.outlineThickness = 1.0;
this.outlineColor = [0.0, 0.0, 0.0];
this.outlineDepthThreshold = 0.005;
// New defaults
this.nearClip = 0.1; // Will override in script
this.farClip = 1000.0; // Will override in script
this.maxOutlineDistance = 100; // Distance at which outlines fade out
};
OutlinePostEffect = pc.inherits(OutlinePostEffect, pc.PostEffect);
OutlinePostEffect.prototype = pc.extend(OutlinePostEffect.prototype, {
render: function (inputTarget, outputTarget, rect) {
var device = this.device;
var scope = device.scope;
scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
// Depth buffer
if (device.sceneDepthMap) {
scope.resolve("uDepthBuffer").setValue(device.sceneDepthMap);
} else {
scope.resolve("uDepthBuffer").setValue(inputTarget.colorBuffer); // fallback
}
this.resolution[0] = inputTarget.width;
this.resolution[1] = inputTarget.height;
scope.resolve("uResolution").setValue(this.resolution);
scope.resolve("uOutlineThickness").setValue(this.outlineThickness);
scope.resolve("uOutlineColor").setValue(this.outlineColor);
scope.resolve("uOutlineDepthThreshold").setValue(this.outlineDepthThreshold);
// Pass new uniforms
scope.resolve("uNearClip").setValue(this.nearClip);
scope.resolve("uFarClip").setValue(this.farClip);
scope.resolve("uMaxOutlineDistance").setValue(this.maxOutlineDistance);
pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.shader, rect);
}
});
return {
OutlinePostEffect: OutlinePostEffect
};
}());
//--------------- SCRIPT DEFINITION ------------------------//
var PostEffectOutline = pc.createScript('PostEffectOutline');
// Outline Thickness
PostEffectOutline.attributes.add('outlineThickness', {
type: 'number',
default: 1,
min: 1,
max: 4,
title: 'Outline Thickness',
description: 'The thickness of the outline.'
});
// Outline Color (RGB)
PostEffectOutline.attributes.add('outlineColor', {
type: 'rgb',
default: [0, 0, 0],
title: 'Outline Color',
description: 'The color of the outline.'
});
// Outline Depth Threshold
PostEffectOutline.attributes.add('outlineDepthThreshold', {
type: 'number',
default: 0.0001,
min: 0.0001,
max: 0.5,
step: 0.0001,
title: 'Depth Threshold',
description: 'The threshold for depth difference to trigger outlines.'
});
// Max distance at which outlines appear (to fade them out for far objects)
PostEffectOutline.attributes.add('maxOutlineDistance', {
type: 'number',
default: 100,
min: 1,
max: 10000,
title: 'Max Outline Distance',
description: 'Beyond this distance from the camera, outlines fade out.'
});
PostEffectOutline.prototype.initialize = function() {
var effect = new pc.OutlinePostEffect(this.app.graphicsDevice);
// Attach to the camera’s post-effect queue
var queue = this.entity.camera.postEffects;
queue.addEffect(effect);
// Initialize from attributes
effect.outlineThickness = this.outlineThickness;
effect.outlineColor = [this.outlineColor.r, this.outlineColor.g, this.outlineColor.b];
effect.outlineDepthThreshold = this.outlineDepthThreshold;
// Pass camera near/far so we can linearize depth properly
effect.nearClip = this.entity.camera.nearClip;
effect.farClip = this.entity.camera.farClip;
// Distance fade
effect.maxOutlineDistance = this.maxOutlineDistance;
// Live updates
this.on('attr:outlineThickness', function (value) {
effect.outlineThickness = value;
}, this);
this.on('attr:outlineColor', function (value) {
effect.outlineColor = [value.r, value.g, value.b];
}, this);
this.on('attr:outlineDepthThreshold', function (value) {
effect.outlineDepthThreshold = value;
}, this);
this.on('attr:maxOutlineDistance', function (value) {
effect.maxOutlineDistance = value;
}, this);
// Handle enable/disable
this.on('enable', function () {
queue.addEffect(effect);
}, this);
this.on('disable', function () {
queue.removeEffect(effect);
}, this);
};
//------------------------POST EFFECT BY XXALCHEMISTXX------------------------//
the main issue right now is depth buffer, and artifacts around shadows. I haven’t gotten around to fixing this, but, I did have a duct taped on solution. You can turn the depth slider down and it will reduce artifacts around shadows, but may cause darker objects to not be outlined. The depth falloff where the outlines no longer persist isn’t working as the depth buffer isn’t being grabbed correctly. Feel free to try and fix it or use the code in your project.