[WIP] BasicSSAO – Added optional smoothing

I finally decided on a method for smoothing out the rough edges that has the least impact on performance and gives good results. I decided to go with shader generated noise, so no noise texture to sample from. It has 2 modes… UseSmoothing & SmoothMore. The second smoothing pass is optional via SmoothMore, but double the texture lookups from 4 - 8. Even just using the basic 1 pass, the results are awesome… the second only slightly improves the smoothing… so not sure if it is worth the performance hit.



Here are the screen caps… the code below has been updated. Oh… did I mention the performance hit is minimal? Just in case I didn’t… the performance hit is minimal!



Without smoothing:





1 pass smoothing:





2 pass smoothing:





To use smoothing:

[java]ssao.setUseSmoothing(true);

ssao.setSmoothMore(true);

// OR //

ssao.toggleSmoothing(); // toggles no smoothing, 1 pass, 2 pass

[/java]



Since I already updated this, I am going to use the OP for maintaining the code.



Changes:

Fixed to work with older drivers/hardware

Added performance optimization which close to doubles the performance by default.

Changed the falloff to stop ambient occlusion on objects outside of the falloff range.

Added global settings scaling.

Updated javadoc info



Basic Usage:

[java]BasicSSAO ssao = new BasicSSAO();

ssao.scaleSettings(0.25); // or whatever works for your model scale

[/java]



Advanced Usage:

[java]// In vars: reflection-radius, intensity, scale, bias

BasicSSAO ssao = new BasicSSAO(3.0f, 10.5f, 0.5f, 0.025f);

// Add in detail pass - this doubles the number of samples taken and halves performance. But, allows for smoothing artifacting while keeping detail

ssao.setUseDetailPass(true);

// Add distance falloff and set distance/rate of falloff

ssao.setUseDistanceFalloff(true);

ssao.setFalloffStartDistance(250f);

ssao.setFalloffRate(4.0f);

[/java]



Here is the (as close as I can tell) final code:



BasicSSAO.java (updated)

[java]/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.

    */

    package mygame;



    import com.jme3.asset.AssetManager;

    import com.jme3.export.InputCapsule;

    import com.jme3.export.JmeExporter;

    import com.jme3.export.JmeImporter;

    import com.jme3.export.OutputCapsule;

    import com.jme3.material.Material;

    import com.jme3.math.Vector2f;

    import com.jme3.math.Vector3f;

    import com.jme3.post.Filter;

    import com.jme3.renderer.RenderManager;

    import com.jme3.renderer.Renderer;

    import com.jme3.renderer.ViewPort;

    import com.jme3.renderer.queue.RenderQueue;

    import com.jme3.shader.VarType;

    import com.jme3.texture.Image.Format;

    import com.jme3.texture.Texture;

    import java.io.IOException;

    import java.util.ArrayList;



    public class BasicSSAO extends Filter{

    private Pass normalPass;

    private Vector3f frustumCorner;

    private Vector2f frustumNearFar;

    private Vector3f[] samples = {

    new Vector3f(1.0f, 0.0f, 1.0f),

    new Vector3f(-1.0f, 0.0f, 1.0f),

    new Vector3f(0.0f, 1.0f, 1.0f),

    new Vector3f(0.0f, -1.0f, 1.0f),

    new Vector3f(1.0f, 0.0f, 0.0f),

    new Vector3f(-1.0f, 0.0f, 0.0f),

    new Vector3f(0.0f, 1.0f, 0.0f),

    new Vector3f(0.0f, -1.0f, 0.0f),

    new Vector3f(1.0f, 0.0f, -1.0f),

    new Vector3f(-1.0f, 0.0f, -1.0f),

    new Vector3f(0.0f, 1.0f, -1.0f),

    new Vector3f(0.0f, -1.0f, -1.0f)

    };

    // Wide area occlusion settings

    private float sampleRadius = 3.0f;

    private float intensity = 10.2f;

    private float scale = 3.15f;

    private float bias = 0.025f;



    // Fine detail occlusion settings

    private boolean enableFD = false;

    private float sampleRadiusFD = 0.55f;

    private float intensityFD = 2.5f;

    private float scaleFD = 1.15f;

    private float biasFD = 0.025f;



    // Distance falloff

    private boolean useDistanceFalloff = false;

    private float falloffStartDistance = 800f, falloffRate = 2.0f;



    private boolean useSmoothing = false;

    private boolean smoothMore = false;

    private boolean useOnlyAo = false;

    private boolean useAo = true;

    private Material ssaoMat;

    private Pass ssaoPass;



    private float downSampleFactor = 1f;

    RenderManager renderManager;

    ViewPort viewPort;



    /**
  • Create a Screen Space Ambient Occlusion Filter

    */

    public BasicSSAO() {

    super("BasicSSAO");

    }



    /**
  • Create a Screen Space Ambient Occlusion Filter
  • @param sampleRadius The radius of the area where random samples will be picked. default 5.1f
  • @param intensity intensity of the resulting AO. default 1.2f
  • @param scale distance between occluders and occludee. default 0.2f
  • @param bias the width of the occlusion cone considered by the occludee. default 0.1f

    */

    public BasicSSAO(float sampleRadius, float intensity, float scale, float bias) {

    this();

    this.sampleRadius = sampleRadius;

    this.intensity = intensity;

    this.scale = scale;

    this.bias = bias;

    }



    @Override

    protected boolean isRequiresDepthTexture() {

    return true;

    }



    @Override

    protected void postQueue(RenderQueue renderQueue) {

    Renderer r = renderManager.getRenderer();

    r.setFrameBuffer(normalPass.getRenderFrameBuffer());

    renderManager.getRenderer().clearBuffers(true, true, true);

    renderManager.setForcedTechnique("PreNormalPass");

    renderManager.renderViewPortQueues(viewPort, false);

    renderManager.setForcedTechnique(null);

    renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());

    }



    @Override

    protected Material getMaterial() {

    return material;

    }



    @Override

    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {

    this.renderManager = renderManager;

    this.viewPort = vp;



    int screenWidth = w;

    int screenHeight = h;

    postRenderPasses = new ArrayList<Pass>();



    normalPass = new Pass();

    normalPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth);



    frustumNearFar = new Vector2f();



    float farY = (vp.getCamera().getFrustumTop() / vp.getCamera().getFrustumNear()) * vp.getCamera().getFrustumFar();

    float farX = farY * ((float) screenWidth / (float) screenHeight);

    frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar());

    frustumNearFar.x = vp.getCamera().getFrustumNear();

    frustumNearFar.y = vp.getCamera().getFrustumFar();



    //ssao Pass

    ssaoMat = new Material(manager, "MatDefs/BasicSSAO.j3md");

    ssaoMat.setTexture("Normals", normalPass.getRenderedTexture());



    ssaoPass = new Pass() {

    @Override

    public boolean requiresDepthAsTexture() {

    return true;

    }

    };

    ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);

    ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);

    ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);

    postRenderPasses.add(ssaoPass);



    float xScale = 1.0f / w;

    float yScale = 1.0f / h;

    float blurScale = 6.75f;



    material = new Material(manager, "MatDefs/BasicSSAOBlur.j3md");

    material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());

    material.setVector2("FrustumNearFar", frustumNearFar);

    material.setBoolean("UseAo", useAo);

    material.setBoolean("UseOnlyAo", useOnlyAo);

    material.setBoolean("UseSmoothing", useSmoothing);

    material.setBoolean("SmoothMore", smoothMore);

    material.setFloat("XScale", blurScale * xScale);

    material.setFloat("YScale", blurScale * yScale);



    ssaoMat.setFloat("SampleRadius", sampleRadius);

    ssaoMat.setFloat("Intensity", intensity);

    ssaoMat.setFloat("Scale", scale);

    ssaoMat.setFloat("Bias", bias);



    ssaoMat.setBoolean("EnableFD", enableFD);

    ssaoMat.setFloat("SampleRadiusFD", sampleRadiusFD);

    ssaoMat.setFloat("IntensityFD", intensityFD);

    ssaoMat.setFloat("ScaleFD", scaleFD);

    ssaoMat.setFloat("BiasFD", biasFD);



    ssaoMat.setBoolean("UseDistanceFalloff", useDistanceFalloff);

    ssaoMat.setFloat("FalloffStartDistance", falloffStartDistance);

    ssaoMat.setFloat("FalloffRate", falloffRate);



    ssaoMat.setVector3("FrustumCorner", frustumCorner);

    ssaoMat.setVector2("FrustumNearFar", frustumNearFar);

    ssaoMat.setParam("Samples", VarType.Vector3Array, samples);

    }



    /**
  • Enables fine detail pass for help in blending out artifacting in the wider area pass without losing detail
  • @param useDetailPass

    */

    public void setUseDetailPass(boolean useDetailPass) {

    this.enableFD = useDetailPass;

    if (ssaoMat != null) {

    ssaoMat.setBoolean("EnableFD", useDetailPass);

    }

    }



    /**
  • Returns the use fine detail setting
  • @return enableFD

    */

    public boolean getUseDetailPass() {

    return this.enableFD;

    }



    /**
  • Return the wide area bias<br>
  • see {@link #setBias(float bias)}
  • @return bias

    */

    public float getBias() {

    return bias;

    }



    /**
  • Sets the the width of the wide area occlusion cone considered by the occludee default is 0.025f
  • @param bias

    */

    public void setBias(float bias) {

    this.bias = bias;

    if (ssaoMat != null) {

    ssaoMat.setFloat("Bias", bias);

    }

    }



    /**
  • Return the fine detail bias<br>
  • see {@link #setDetailBias(float biasFD)}
  • @return biasFD

    */

    public float getDetailBias() {

    return biasFD;

    }



    /**
  • Sets the the width of the fine detail occlusion cone considered by the occludee default is 0.025f
  • @param biasFD

    */

    public void setDetailBias(float biasFD) {

    this.biasFD = biasFD;

    if (ssaoMat != null) {

    ssaoMat.setFloat("BiasFD", biasFD);

    }

    }



    /**
  • returns the ambient occlusion intensity
  • @return intensity

    */

    public float getIntensity() {

    return intensity;

    }



    /**
  • Sets the Ambient occlusion intensity default is 10.2f
  • @param intensity

    */

    public void setIntensity(float intensity) {

    this.intensity = intensity;

    if (ssaoMat != null) {

    ssaoMat.setFloat("Intensity", intensity);

    }



    }



    /**
  • returns the fine detail ambient occlusion intensity
  • @return intensityFD

    */

    public float getDetailIntensity() {

    return intensityFD;

    }



    /**
  • Sets the fine detail ambient occlusion intensity default is 2.5f
  • @param intensityFD

    */

    public void setDetailIntensity(float intensityFD) {

    this.intensityFD = intensityFD;

    if (ssaoMat != null) {

    ssaoMat.setFloat("IntensityFD", intensityFD);

    }

    }



    /**
  • returns the sample radius<br>
  • see {link setSampleRadius(float sampleRadius)}
  • @return the sample radius

    */

    public float getSampleRadius() {

    return sampleRadius;

    }



    /**
  • Sets the radius of the area where random samples will be picked dafault 3.0f
  • @param sampleRadius

    */

    public void setSampleRadius(float sampleRadius) {

    this.sampleRadius = sampleRadius;

    if (ssaoMat != null) {

    ssaoMat.setFloat("SampleRadius", sampleRadius);

    }



    }



    /**
  • returns the sample radius<br>
  • see {link setDetailSampleRadius(float sampleRadiusFD)}
  • @return the sample radius for detail pass

    */

    public float getDetailSampleRadius() {

    return sampleRadiusFD;

    }



    /**
  • Sets the radius of the area where random samples will be picked for fine detail pass dafault 0.55f
  • @param sampleRadiusFD

    */

    public void setDetailSampleRadius(float sampleRadiusFD) {

    this.sampleRadiusFD = sampleRadiusFD;

    if (ssaoMat != null) {

    ssaoMat.setFloat("SampleRadiusFD", sampleRadiusFD);

    }



    }



    /**
  • returns the scale<br>
  • see {@link #setScale(float scale)}
  • @return scale

    */

    public float getScale() {

    return scale;

    }



    /**

    *
  • Returns the distance between occluders and occludee. default 3.15f
  • @param scale

    */

    public void setScale(float scale) {

    this.scale = scale;

    if (ssaoMat != null) {

    ssaoMat.setFloat("Scale", scale);

    }

    }



    /**
  • returns the detail pass scale<br>
  • see {@link #setDetailScale(float scaleFD)}
  • @return scaleFD

    */

    public float getDetailScale() {

    return scaleFD;

    }



    /**

    *
  • Returns the distance between detail pass occluders and occludee. default 1.55f
  • @param scaleFD

    */

    public void setDetailScale(float scaleFD) {

    this.scaleFD = scaleFD;

    if (ssaoMat != null) {

    ssaoMat.setFloat("ScaleFD", scaleFD);

    }

    }



    public void scaleSettings(float aoScale) {

    setBias(getBias()*aoScale);

    setDetailBias(getDetailBias()*aoScale);

    setIntensity(getIntensity()*aoScale);

    setDetailIntensity(getDetailIntensity()*aoScale);

    setScale(getScale()*aoScale);

    setDetailScale(getDetailScale()*aoScale);

    setScale(getScale()*aoScale);

    setDetailScale(getDetailScale()*aoScale);

    setSampleRadius(getSampleRadius()*aoScale);

    setDetailSampleRadius(getDetailSampleRadius()*aoScale);

    }



    /**
  • debugging only , will be removed
  • @return Whether or not

    */

    public boolean isUseAo() {

    return useAo;

    }



    /**
  • debugging only

    */

    public void setUseAo(boolean useAo) {

    this.useAo = useAo;

    if (material != null) {

    material.setBoolean("UseAo", useAo);

    }



    }



    /**
  • debugging only , will be removed
  • @return useOnlyAo

    */

    public boolean isUseOnlyAo() {

    return useOnlyAo;

    }



    /**
  • debugging only

    */

    public void setUseOnlyAo(boolean useOnlyAo) {

    this.useOnlyAo = useOnlyAo;

    if (material != null) {

    material.setBoolean("UseOnlyAo", useOnlyAo);

    }

    }



    public void setUseSmoothing(boolean useSmoothing) {

    this.useSmoothing = useSmoothing;

    if (material != null) {

    material.setBoolean("UseSmoothing", useSmoothing);

    }

    }



    public boolean isUseSmoothing() {

    return useSmoothing;

    }



    public void setSmoothMore(boolean smoothMore) {

    this.smoothMore = smoothMore;

    if (material != null) {

    material.setBoolean("SmoothMore", smoothMore);

    }

    }



    public boolean isSmoothMore() {

    return smoothMore;

    }



    /**
  • Enable distance falloff
  • @param useDistanceFalloff

    */

    public void setUseDistanceFalloff(boolean useDistanceFalloff) {

    this.useDistanceFalloff = useDistanceFalloff;

    if (ssaoMat != null) {

    ssaoMat.setBoolean("UseDistanceFalloff", useDistanceFalloff);

    }

    }



    /**
  • Returns distance falloff setting
  • @return useDistanceFalloff

    */

    public boolean getUseDistanceFalloff() {

    return this.useDistanceFalloff;

    }



    /**
  • Sets the start distance for distance falloff. Default is 800f
  • @param falloffStartDistance

    */

    public void setFalloffStartDistance(float falloffStartDistance) {

    this.falloffStartDistance = falloffStartDistance;

    if (ssaoMat != null) {

    ssaoMat.setFloat("FalloffStart", falloffStartDistance);

    }

    }



    /**
  • Returns the start distance for distance falloff.
  • @return falloffStartDistance

    */

    public float getFalloffStartDistance() {

    return this.falloffStartDistance;

    }



    /**
  • Sets the rate at which distance falloff increases past the start distance. Default is 2.0f
  • @param falloffRate

    */

    public void setFalloffRate(float falloffRate) {

    this.falloffRate = falloffRate;

    if (ssaoMat != null) {

    ssaoMat.setFloat("FalloffAmount", falloffRate);

    }

    }



    /**
  • Returns the rate at which distance falloff increases past the start distance.
  • @return falloffRate

    */

    public float getFalloffRate() {

    return this.falloffRate;

    }



    /**
  • Used for debugging. toggles between shadowmap, colormap & colormap+shadowmap

    */

    public void toggleSSAO() {

    if (!useOnlyAo && useAo) { // BasicSSAO Disabled

    useOnlyAo = false;

    useAo = false;



    } else if (useOnlyAo && useAo) { // BasicSSAO Map Only

    useOnlyAo = false;

    useAo = true;

    } else if (!useOnlyAo && !useAo) { // BasicSSAO Blended

    useOnlyAo = true;

    useAo = true;

    }

    if (material != null) {

    material.setBoolean("UseAo", useAo);

    material.setBoolean("UseOnlyAo", useOnlyAo);

    }

    }



    /**
  • Used for debugging. toggles between no smoothing, 1 pass smoothing & 2 pass smoothing

    /

    public void toggleSmoothing() {

    if (smoothMore && useSmoothing) { // Smoothing Disabled

    useSmoothing = false;

    smoothMore = false;



    } else if (useSmoothing && !smoothMore) { // 2 pass Smoothing

    useSmoothing = true;

    smoothMore = true;

    } else if (!useSmoothing && !smoothMore) { // 1 pass Smoothing

    useSmoothing = true;

    smoothMore = false;

    }

    if (material != null) {

    material.setBoolean("UseSmoothing", useSmoothing);

    material.setBoolean("SmoothMore", smoothMore);

    }

    }



    @Override

    public void write(JmeExporter ex) throws IOException {

    super.write(ex);



    OutputCapsule oc = ex.getCapsule(this);

    oc.write(sampleRadius, "sampleRadius", 3.0f);

    oc.write(intensity, "intensity", 10.2f);

    oc.write(scale, "scale", 3.15f);

    oc.write(bias, "bias", 0.025f);

    oc.write(sampleRadiusFD, "sampleRadiusFD", 0.55f);

    oc.write(intensityFD, "intensityFD", 2.5f);

    oc.write(scaleFD, "scaleFD", 1.15f);

    oc.write(biasFD, "biasFD", 0.025f);

    }



    @Override

    public void read(JmeImporter im) throws IOException {

    super.read(im);

    InputCapsule ic = im.getCapsule(this);

    sampleRadius = ic.readFloat("sampleRadius", 3.0f);

    intensity = ic.readFloat("intensity", 10.2f);

    scale = ic.readFloat("scale", 3.15f);

    bias = ic.readFloat("bias", 0.025f);

    sampleRadiusFD = ic.readFloat("sampleRadiusFD", 0.55f);

    intensityFD = ic.readFloat("intensityFD", 2.5f);

    scaleFD = ic.readFloat("scaleFD", 1.15f);

    biasFD = ic.readFloat("biasFD", 0.025f);

    }

    }[/java]



    Material Definitions (x2):



    BasicSSAO.j3md

    [java]MaterialDef BasicSSAO {



    MaterialParameters {

    Int NumSamples

    Int NumSamplesDepth

    Texture2D DepthTexture

    Texture2D Texture

    Texture2D Noise

    Texture2D Normals

    Vector3 FrustumCorner

    Float SampleRadius

    Float Intensity

    Float Scale

    Float Bias

    Boolean EnableFD

    Float SampleRadiusFD

    Float IntensityFD

    Float ScaleFD

    Float BiasFD

    Boolean UseDistanceFalloff

    Float FalloffStartDistance

    Float FalloffRate

    Vector2 FrustumNearFar

    Vector3Array Samples

    }



    Technique {

    VertexShader GLSL100: Common/MatDefs/Post/Post.vert

    FragmentShader GLSL100: Shaders/BasicSSAO.frag



    WorldParameters {

    WorldViewProjectionMatrix

    WorldViewMatrix

    Resolution

    }

    }

    }[/java]



    BasicSSAOBlur.j3md (updated)

    [java]MaterialDef BasicSSAOBlur {



    MaterialParameters {

    Int NumSamples

    Int NumSamplesDepth

    Texture2D Texture

    Texture2D SSAOMap

    Texture2D DepthTexture

    Vector2 FrustumNearFar

    Boolean UseAo

    Boolean UseOnlyAo

    Boolean UseSmoothing

    Boolean SmoothMore

    Float XScale

    Float YScale

    }



    Technique {

    VertexShader GLSL100: Common/MatDefs/Post/Post.vert

    FragmentShader GLSL100: Shaders/BasicSSAOBlur.frag



    WorldParameters {

    WorldViewProjectionMatrix

    WorldViewMatrix

    Resolution

    }



    Defines {

    USE_AO : UseAo

    USE_ONLY_AO : UseOnlyAo

    RESOLVE_MS : NumSamples

    RESOLVE_DEPTH_MS : NumSamplesDepth

    }

    }

    }[/java]



    Shaders (x2):



    BasicSSAO.frag

    [java]uniform vec2 g_Resolution;

    uniform vec2 m_FrustumNearFar;

    uniform sampler2D m_Texture;

    uniform sampler2D m_Normals;

    uniform sampler2D m_Noise;

    uniform sampler2D m_DepthTexture;

    uniform vec3 m_FrustumCorner;

    uniform float m_SampleRadius;

    uniform float m_Intensity;

    uniform float m_Scale;

    uniform float m_Bias;

    uniform bool m_EnableFD;

    uniform float m_SampleRadiusFD;

    uniform float m_IntensityFD;

    uniform float m_ScaleFD;

    uniform float m_BiasFD;

    uniform vec3[12] m_Samples;



    uniform bool m_UseDistanceFalloff;

    uniform float m_FalloffStartDistance;

    uniform float m_FalloffRate;



    varying vec2 texCoord;



    float depthv;

    float shadowFactor;



    vec3 getPosition(in vec2 uv){

    depthv = texture2D(m_DepthTexture,uv).r;

    float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - depthv
    (m_FrustumNearFar.y-m_FrustumNearFar.x));

    float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, uv.x);

    float y = mix(-m_FrustumCorner.y, m_FrustumCorner.y, uv.y);

    return depth* vec3(x, y, m_FrustumCorner.z);

    }



    vec3 getNormal(in vec2 uv){

    return normalize(texture2D(m_Normals, uv).xyz * 2.0 - 1.0);

    }



    vec3 getRandom(in vec2 uv){

    float rand=(fract(uv.x*(g_Resolution.x/2.0))0.25)+(fract(uv.y(g_Resolution.y/2.0))0.5);

    return normalize(vec3(rand,rand,rand));

    }



    vec3 getNoise(in vec2 uv){

    vec4 noise = texture2D(m_Noise, uv
    25.0);

    return (noise.xyz);

    }



    float doAmbientOcclusion(in vec2 tc, in vec3 pos, in vec3 norm){

    vec3 diff = getPosition(tc)- pos;

    vec3 v = normalize(diff);

    float d = length(diff) * m_Scale;

    return step(0.00002,d)max(0.0, dot(norm, v) - m_Bias) * ( 1.0/(1.0 + d) ) * (m_Intensity+shadowFactor) * smoothstep(0.00002,0.0027,d);

    }



    float doAmbientOcclusionFD(in vec2 tc, in vec3 pos, in vec3 norm){

    vec3 diff = getPosition(tc)- pos;

    vec3 v = normalize(diff);

    float d = length(diff) * m_ScaleFD;

    return step(0.00002,d)max(0.0, dot(norm, v) - m_BiasFD) * ( 1.0/(1.0 + d) ) * (m_IntensityFD+shadowFactor) * smoothstep(0.00002,0.0027,d);

    }



    vec3 reflection(in vec3 v1,in vec3 v2){

    vec3 result= 2.0 * dot(v2, v1) * v2;

    result=v1-result;

    return result;

    }



    void main(){

    float result;

    vec3 position = getPosition(texCoord);

    if(depthv==1.0){

    gl_FragColor=vec4(1.0);

    return;

    }

    vec3 normal = getNormal(texCoord);

    vec3 rand = getRandom(texCoord);



    float ao = 0.0;

    shadowFactor = 0.075;//0.0;//(position.z
    0.002);

    float rad = m_SampleRadius/position.z+shadowFactor;

    float radFD = m_SampleRadiusFD/position.z+shadowFactor;



    int iterations = 12;

    if (m_UseDistanceFalloff) {

    float LOG2 = 1.442695;

    vec2 m_DistanceFrustum = vec2(1.0,m_FalloffStartDistance);

    float depth= (m_DistanceFrustum.x / 4.0) /

    (m_DistanceFrustum.y - depthv

    (m_DistanceFrustum.y));



    float falloffFactor = exp2( -m_FalloffRate * m_FalloffRate * depth * depth * LOG2 );

    falloffFactor = clamp(falloffFactor, 0.0, 1.0);



    if (falloffFactor < 1.0) {

    for (int j = 0; j < iterations; ++j) {

    vec3 coord1 = reflection(vec3(m_Samples[j]), rand) * vec3(rad);

    ao += doAmbientOcclusion(texCoord + coord1.xy * 0.125, position, normal) - shadowFactor;

    // Fine Detail

    if (m_EnableFD) {

    vec3 coord2 = reflection(vec3(m_Samples[j]), rand) * vec3(radFD
    0.5);

    ao += doAmbientOcclusionFD(texCoord + coord2.xy * 0.05, position, normal) * (0.25-shadowFactor);

    }

    }

    ao /= float(iterations) * (2.35-shadowFactor);

    result = 1.0-ao;

    result = mix(result,1.0,1.0-falloffFactor);

    } else {

    result = 1.0;

    }

    } else {

    for (int j = 0; j < iterations; ++j) {

    vec3 coord1 = reflection(vec3(m_Samples[j]), rand) * vec3(rad);

    ao += doAmbientOcclusion(texCoord + coord1.xy * 0.125, position, normal) - shadowFactor;

    // Fine Detail

    if (m_EnableFD) {

    vec3 coord2 = reflection(vec3(m_Samples[j]), rand) * vec3(radFD
    0.5);

    ao += doAmbientOcclusionFD(texCoord + coord2.xy * 0.05, position, normal) * (0.25-shadowFactor);

    }

    }

    ao /= float(iterations) * (2.35-shadowFactor);

    result = 1.0-ao;

    }



    gl_FragColor = vec4(vec3(result),1.0);

    }[/java]



    BasicSSAOBlur.frag (updated)

    [java]uniform sampler2D m_Texture;

    uniform sampler2D m_DepthTexture;

    uniform sampler2D m_SSAOMap;

    uniform vec2 g_Resolution;

    uniform bool m_UseOnlyAo;

    uniform bool m_UseAo;

    uniform bool m_UseSmoothing;

    uniform bool m_SmoothMore;

    uniform float m_XScale;

    uniform float m_YScale;

    uniform vec2 m_FrustumNearFar;

    const float epsilon = 0.005;

    varying vec2 texCoord;



    float random (vec4 seed4) {

    float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));

    return fract(sin(dot_product) * 43758.5453);

    }



    void main(){

    vec4 ssao = vec4(0.0);//texture2D( m_SSAOMap,texCoord);

    if (m_UseSmoothing) {

    for (int i = 0; i < 4; i++) {

    ssao += texture2D(m_SSAOMap,texCoord + (random(vec4(texCoord,-texCoord)*vec4(float(-i)))/200.0));

    if (m_SmoothMore) ssao += texture2D(m_SSAOMap,texCoord + (random(vec4(-texCoord,texCoord)vec4(float(i)))/100.0));

    }

    if (m_SmoothMore) ssao /= 8.0;

    else ssao /= 4.0;

    } else {

    ssao = texture2D(m_SSAOMap,texCoord);

    }

    vec4 color = texture2D(m_Texture,texCoord);



    if (!m_UseAo && !m_UseOnlyAo)

    gl_FragColor = color;

    else if (m_UseAo && m_UseOnlyAo)

    gl_FragColor = ssao;

    else

    gl_FragColor = color
    ssao;

    }[/java]



    Here is a video of what it does (prior to optimization):



    http://youtu.be/2-Cxi1dsWtg
9 Likes

Bump for update.

@t0neg0d said:
Bump for update.


I think I understand why you edited the original post but it sure does make for really strange threads now that a bunch of people are commenting on something that doesn't exist. I wonder if simply posting a reply with the new information would have been just as good... maybe edit the original with a pointer to the new one.

Really nice stuff, though. Looking forward to see where this is going.

Wow! looks cool! I think such a filter wants everyone.

Would you like to share with this filter with the community? :slight_smile:



even if it’s wip…

Little bit more clean up… and I’ll post the code for everyone. :slight_smile:

2 Likes

Very nice! :slight_smile:

this is probably not what you want.

[java]

float fogVal = texture2D(m_DepthTexture,texCoord).r / m_FogDistance;

[/java]

dividing non linear depth by the distance makes no sense.



[java]

//getting the render depth (non linear depth)

float fogVal = texture2D(m_DepthTexture,texCoord).r ;

//‘linearize’ depth in view frustum understand “depth will grow linearly from 0 to,1 0 being nearfrustum and 1 farFrustum”

float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - fogVal* (m_FrustumNearFar.y-m_FrustumNearFar.x));

[/java]



what you need to do after that is to get the linear proportion of your m_FogDistance in the frustum

[java]

float dist = (m_FogDistance- m_FrustumNearFar.x) / (m_FrustumNearFar.y-m_FrustumNearFar.x);

[/java]

then going depth -=dist should do the trick i guess.



also be careful with that

[java]

1-fogFactor

[/java]

this won’t work on many hardware.

The correct syntax is

[java]

1.0-fogFactor

[/java]



Also you can save this extra calculation just by reversing the mix attributes

[java]

ssao.rgb = mix(vec3(1.0),ssao.rgb,fogFactor);

[/java]

Pretty!

Nice, this looks cool.

I would like to try this one when it becomes available! Good work! :slight_smile:

@nehon said:
this is probably not what you want.
[java]
float fogVal = texture2D(m_DepthTexture,texCoord).r / m_FogDistance;
[/java]
dividing non linear depth by the distance makes no sense.

[java]
//getting the render depth (non linear depth)
float fogVal = texture2D(m_DepthTexture,texCoord).r ;
//'linearize' depth in view frustum understand &quot;depth will grow linearly from 0 to,1 0 being nearfrustum and 1 farFrustum&quot;
float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - fogVal* (m_FrustumNearFar.y-m_FrustumNearFar.x));
[/java]

what you need to do after that is to get the linear proportion of your m_FogDistance in the frustum
[java]
float dist = (m_FogDistance- m_FrustumNearFar.x) / (m_FrustumNearFar.y-m_FrustumNearFar.x);
[/java]
then going depth -=dist should do the trick i guess.

also be careful with that
[java]
1-fogFactor
[/java]
this won't work on many hardware.
The correct syntax is
[java]
1.0-fogFactor
[/java]

Also you can save this extra calculation just by reversing the mix attributes
[java]
ssao.rgb = mix(vec3(1.0),ssao.rgb,fogFactor);
[/java]



Thanks so much for the help there.... quick not about the 1-fogFactor... that's cut & pasted out of the fog shader in JME.

I've been experimenting with different approaches to SSAO and found a couple things in the current one that I wanted to pass along:

1. The normal map is storing the depth of the pixel already... You can read the depth out of .a and turn off passing in the DepthTexture all together.
2. The ssao frag shader is passing in a noise texture and never using it. You can either get rid of this.... or, even better.... use it to scatter the shadow map.
3. There is a method in the ssao frag shader called reflection... GLSL already has a function called reflect that is optimized and would probably speed things up a little if utilized.
4. The convolution filter in the ssaoBlur is what is killing frame rate. I would suggest changing the following to both speed up the SSAOFilter and make it look a ton better:
4a. Up the number of samples to 10(ish) and use the noise map to scatter.
4b. Ditch the use of the texture map in ssao frag altogether... it's already being blended in ssaoBlur.
4c. If you do the above, you can completely remove the convolution filter in ssaoBlur and you will see a SUBSTANTIAL increase in framerate when using this filter.

Anyways... hope this is helpful.

I'm working on a completely different approach to SSAO that will hopefully bring fine detail into the process by removing samples that are behind the original reflection normal, adjusting the sample radius according to depth and also add a distance falloff that will gradually stop adding AO after a set distance.

Will post stuff as I get it.

One other quick note:



The reflection routine in the current SSAO is only using x & y to do reflection, which means… it’s actually not reflecting faces that are inside the sample sphere (which isn’t a sample sphere). This produces a cartoon edge filter more than it actually produces AO. The effect is nice looking… but this is the reason that convolution is needed to begin with.

  1. I really don’t recommend doing this because you are restraining to a 8 bits depth buffer, which can lead to very bad artifacts due to poor depth precision. Unless you use a RGBA32F texture format, but that can be too heavy for low hardware and will clutter your GPU bandwidth.

    I use hardware depth, this depth buffer is rendered even if you don’t use it, so in your case, you save a texture fetch but you have to compute depth yourself along with the normal, and hardware depth is still rendered


  2. The noise texture is used to randomize the ssao samples. I can assure you it’s used (in getRandom or something like that if i recall). If it’s not used someone changed something in the SSAO shader and i’m not aware of it.



    3.This is a workaround for OSX. I don’t remember which version of GLSL on osx, doesn’t have the reflect method. So i made my own. I made some micro bench and the overhead compared to the built in reflect is so small it worth the workaround.


  3. Those are good ideas, but usually convolutions are here to avoid the “grainyness” of SSAO. For now i use a bilateral blur to avoid the SSAO to bleed over edges. Using any other blurring will lead to that kind of artifact.

    I did not spend a lot of time on this blur filter over SSAO, and it’s been a while i did not update this filter for a reason.

    There is a particular implementation that i want in, and this one will much likely disappear. That’s why I won’t make any update to this filter, if i spend time on it it will be to remake it from scratch. But that won’t be for 3.0

    FYI this is the implementation that i want to try

    http://www.comp.nus.edu.sg/~duong/

    The visual are just completely awesome and the perfs looks great.
1 Like

@pspeed Sorry about that… really didn’t mean to do that :slight_smile: Won’t happen again, for sure.

Very nice result.



One thing though about performance.

Your scene looks clearly advanced and your computer looks like it does suck indeed. But you should test your effect on a simple scene.

There can be a lot of stuff lowering your framerate in your game, and it’s hard to find the bottleneck. If your bottleneck is CPU side (animations, physics maybe), SSAO won’t take that much fps because the GPU is literally waiting for the CPU.

You have to make a scene with the GPU as bottleneck to evaluate the real impact.

You can make some kind of Cornell box scene, or use the scene from the SDK SSAO test.

Also it could be nice to compare with the built in filter.

@nehon



Here are some initial Benchmark comparisons. They are exciting! To me… anyways :slight_smile:



The original running the JME test scene: 26 fps.







The new filter running the same scene: 41 fps.







A 57% increase in performance and the detail difference is insane.

4 Likes

Another using the Stanford Dragon with a blank material as a test…



The original: 32 fps.







The new filter running the same: 58 fps.







Here is another ss with the filter set up for high detail.



3 Likes

It would seem I have some room to work with… so… on to some form of blurring/noise to smooth out the final output.



@nehon Here is where I could really use your help. I have tried multiple methods of blurring/convolution/etc… and honestly… when the filter is setup for fine detail, I like the final results better without any form of blurring. Do you have any suggestions for things to try?



I’m wondering if the blur method shouldn’t be part of the ssao map creation and only applied to the larger of the two passes for occlusion?



Anyways… any and all help would be MOST appreciated as I am sort of (completely) at a loss for how to proceed with this.

@nehon @pspeed Sooo… I COMPLETELY spaced that I added in falloff code to dull down detail. I wanted to share what the full detail SSAO effect looks like. The LOD is adjustable of course, though does nothing for the performance… low detail is the same process as high detail:







nehon is away and won’t respond that often.



also nice! :slight_smile: