I’ve put together a basic SSAO shader for PC. It’s more “toonifying” than anything perhaps, but is the basics of a forward rendering depth based fast SSAO based on normal recreation.
Any how it makes this:
Look like this at 30%
Effect code:
var SSAOEffect = pc.inherits(function (graphicsDevice) {
this.device = graphicsDevice
this.strength = 0.4
this.ssaoShader = pc.shaderChunks.createShaderFromCode(graphicsDevice, pc.shaderChunks.fullscreenQuadVS, ssao, "ssao")
}, pc.PostEffect)
SSAOEffect.prototype.render = function(input, output, rect) {
var device = this.device
var scope = device.scope
scope.resolve("uInputTexture").setValue(input.colorBuffer)
scope.resolve("total_strength").setValue(this.strength)
pc.drawQuadWithShader(device, output, this.ssaoShader)
}
That code needs an ssao
variable set to the shader code:
uniform sampler2D uDepthMap;
uniform sampler2D uInputTexture;
varying vec2 vUv0;
uniform float total_strength;
float unpackFloat(vec4 rgbaDepth) {
const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);
float depth = dot(rgbaDepth, bitShift);
return depth;
}
vec3 normal_from_depth(float depth, vec2 texcoords) {
const vec2 offset1 = vec2(0.0,0.001);
const vec2 offset2 = vec2(0.001,0.0);
float depth1 = unpackFloat(texture2D(uDepthMap, texcoords + offset1));
float depth2 = unpackFloat(texture2D(uDepthMap, texcoords + offset2));
vec3 p1 = vec3(offset1, depth1 - depth);
vec3 p2 = vec3(offset2, depth2 - depth);
vec3 normal = cross(p1, p2);
normal.z = -normal.z;
return normalize(normal);
}
void main(void) {
const float area = 0.0075;
const float radius = 0.0017;
const int samples = 8;
const vec3 sample_sphere[samples] = vec3[samples](
vec3( 0.5381, 0.1856,-0.4319), vec3( 0.1379, 0.2486, 0.4430),
vec3( 0.3371, 0.5679,-0.0057), vec3(-0.6999,-0.0451,-0.0019),
vec3( 0.0689,-0.1598,-0.8547), vec3( 0.0560, 0.0069,-0.1843),
vec3(-0.0146, 0.1402, 0.0762), vec3( 0.0100,-0.1924,-0.0344)
// vec3(-0.3577,-0.5301,-0.4358), vec3(-0.3169, 0.1063, 0.0158),
// vec3( 0.0103,-0.5869, 0.0046), vec3(-0.0897,-0.4940, 0.3287),
// vec3( 0.7119,-0.0154,-0.0918), vec3(-0.0533, 0.0596,-0.5411),
// vec3( 0.0352,-0.0631, 0.5460), vec3(-0.4776, 0.2847,-0.0271)
);
float depth = unpackFloat(texture2D(uDepthMap, vUv0));
vec3 position = vec3(vUv0, depth);
vec3 normal = normal_from_depth(depth, vUv0);
float radius_depth = radius/depth;
float occlusion = 0.0;
float difference;
for(int i=0; i < samples; i++) {
vec3 ray = radius_depth * reflect(sample_sphere[i], normalize(vec3(0.1,0.5,0.3)));
vec3 hemi_ray = position + sign(dot(ray,normal)) * ray;
float occ_depth = unpackFloat(texture2D(uDepthMap, clamp(hemi_ray.xy,0.0,1.0)));
difference = (occ_depth - depth);
occlusion += smoothstep(0.0,area, clamp(difference,0.0,1.0));
}
float ao = total_strength * occlusion * (1.0 / float(samples));
gl_FragColor = mix(texture2D(uInputTexture, vUv0), vec4(0,0,0,1), vec4(ao, ao, ao, 1));
}
In this you can up the samples to 16 and uncomment the second half of the array initialization if you want a better, slower effect. You can make a script to apply it to the camera. Here’s my legacy script for that:
pc.script.attribute('startEnabled', 'boolean', true)
pc.script.attribute('strength', 'number', 0.5)
pc.script.create('ssao', function (app) {
var SSAO = function (entity) {
this.entity = entity
this._enableEffect = true
}
SSAO.prototype.initialize = function() {
this.entity.camera.camera.requestDepthMap()
this._enableEffect = this.startEnabled
}
SSAO.prototype.onEnable = function () {
this.effect = this.effect || new SSAOEffect(app.graphicsDevice)
this.effect.strength = this.strength
this.enableEffect = this._enableEffect
}
SSAO.prototype.onDisable = function() {
if(!this.enableEffect) return
this.enableEffect = false
}
Object.defineProperties(SSAO.prototype, {
enableEffect: {
get: function() {
return this._enableEffect
},
set: function(v) {
this._enableEffect = v
let queue = this.entity.camera.postEffects
if(v) {
queue.addEffect(this.effect)
} else {
queue.removeEffect(this.effect)
}
}
},
strength: {
get: function() {
return this._strength
},
set: function(v) {
this._strength = v
if(this.effect) {
this.effect.strength = v
}
}
}
})
return SSAO
})
Please note you must call requestDepthMap()
on the camera you will attach the effect to.
Adapted from this: http://theorangeduck.com/page/pure-depth-ssao