Depth only SSAO

So I was having a problem with the SSAO filter and made this based on: Pure Depth SSAO
I’m not sure if anyone else has made this before, its pretty common, but I could not find it for JME.

It’s SSAO without the normal pass that nehon’s SSAO uses. This results in lower quality, but higher fps. Figured it’s just nice to have another option. Here are some comparison shots:

(Since I do not fully understand the workings of the settings for both filters it wasn’t easy for me to provide fair comparisons)

The scene:

Depth only:


Original:

Depth only:


Original:

I think both could get better results if you are willing to play with the settings. Clearly though nehon’s filter with normal pass is nicer. The only benefit is the ~279 extra fps in this particular case, since you don’t need a normal pass.

Here is the code:

DepthSSAOFilter.java

package packagename;

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import java.util.ArrayList;

public class DepthSSAOFilter extends Filter 
{
    Material ssaoMat;
    Pass ssaoPass;
    
    Pass hpass;
    Material hBlurMat;
    Pass vpass;
    Material vBlurMat;
    
    private int screenWidth;
    private int screenHeight;   
    
    public DepthSSAOFilter() 
    {
        super("DepthSSAO");
    }

    @Override
    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) 
    {
        ///*
        screenWidth = w;
        screenHeight = h;
        postRenderPasses = new ArrayList<Pass>();
        //ssao Pass
        ssaoMat = new Material(manager, "MatDefs/Effects/DepthSSAO.j3md");
        Texture random = manager.loadTexture("Common/MatDefs/SSAO/Textures/random.png");
        random.setWrap(Texture.WrapMode.Repeat);
        ssaoMat.setTexture("RandomTexture", random);
        ssaoMat.setFloat("Intensity", 0.9f);//1 seems good, 2 was so dark
        ssaoMat.setFloat("Base", 0.15f);//at 0.9 barely any parts get darkened - only extreme corners
        ssaoMat.setFloat("Area", 0.0075f);
        ssaoMat.setFloat("Falloff", 0.000001f);
        ssaoMat.setFloat("Radius", 0.06f);//lower adds more?

        ssaoPass = new Pass() {
            public boolean requiresDepthAsTexture() {
                return true;
            }
        };
        float downSampleFactor = 1f;
        ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Image.Format.RGBA8, Image.Format.Depth, 1, ssaoMat);
        postRenderPasses.add(ssaoPass);

        //hblur
        hBlurMat = new Material(manager, "Common/MatDefs/Blur/HGaussianBlur.j3md");
        hpass = new Pass() {

            @Override
            public void beforeRender() {
                hBlurMat.setTexture("Texture", ssaoPass.getRenderedTexture());
                hBlurMat.setFloat("Size", screenWidth);
                hBlurMat.setFloat("Scale", 1.5f);
            }
        };
        hpass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, hBlurMat);
        postRenderPasses.add(hpass);

        //vblur
        vBlurMat = new Material(manager, "Common/MatDefs/Blur/VGaussianBlur.j3md");
        vpass = new Pass() {

            @Override
            public void beforeRender() {
                vBlurMat.setTexture("Texture", hpass.getRenderedTexture());
                vBlurMat.setFloat("Size", screenHeight);
                vBlurMat.setFloat("Scale", 1.5f);
            }
        };
        vpass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, vBlurMat);
        postRenderPasses.add(vpass);

        material = new Material(manager, "MatDefs/Effects/ApplySSAO.j3md");
        material.setTexture("SSAO", vpass.getRenderedTexture());
    }

    @Override
    protected Material getMaterial() 
    {
            return material;
    }
    
    protected boolean isRequiresSceneTexture() {
        return true;
    }
    
    protected boolean isRequiresDepthTexture() {
        return true;
    }
    
}

DepthSSAO.frag

uniform sampler2D m_Texture;
uniform sampler2D m_DepthTexture;
uniform sampler2D m_RandomTexture;
varying vec2 texCoord;

uniform float m_Intensity;
uniform float m_Base;
uniform float m_Area;
uniform float m_Falloff;
uniform float m_Radius;


vec3 normalFromDepth(float depth, vec2 texcoords) 
{
  
  vec2 offset1 = vec2(0.0,0.001);
  vec2 offset2 = vec2(0.001,0.0);
  
  float depth1 = texture2D(m_DepthTexture, texcoords + offset1).r;
  float depth2 = texture2D(m_DepthTexture, texcoords + offset2).r;
  
  
  vec3 p1 = vec3(offset1, depth1 - depth);
  vec3 p2 = vec3(offset2, depth2 - depth);
  
  vec3 normal = cross(p1, p2);
  normal.z = -normal.z;
  
  return normalize(normal);
}

vec3 reflection(vec3 v1,vec3 v2)
{
    vec3 result= 2.0 * dot(v2, v1) * v2;
    result=v1-result;
    return result;
}




void main() 
{
	
	 vec3 sample_sphere[16] = vec3[]( 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)
									);
									
	vec3 random = normalize( texture2D(m_RandomTexture, texCoord * 4.0).rgb );
	float depth = texture2D(m_DepthTexture, texCoord).r;
	vec3 position = vec3(texCoord.x,texCoord.y,depth);
	vec3 normal = normalFromDepth(depth, texCoord);
	float radiusDepth = m_Radius/depth;
  	float occlusion = 0.0;

	int iterations = 16;
   	for (int j = 0; j < iterations; ++j)
	{
		vec3 ray = radiusDepth * reflection(sample_sphere[j], random);
    		vec3 hemiRay = position + sign(dot(ray,normal)) * ray;
    
    		float occDepth = texture2D(m_DepthTexture, clamp(hemiRay.xy,0.0,1.0)).r;
    		float difference = depth - occDepth;
    
    		occlusion += step(m_Falloff, difference) * (1.0-smoothstep(m_Falloff, m_Area, difference));
	}

	float ao = 1.0 - m_Intensity * occlusion * (1.0 / iterations);
  	float final = clamp(ao + m_Base,0.0,1.0);

	gl_FragColor = vec4(final,final,final,1);
}

ApplySSAO.frag

uniform sampler2D m_Texture;
uniform sampler2D m_SSAO;
varying vec2 texCoord;

void main() 
{
	vec4 tv = 	texture2D(m_Texture, texCoord);
	vec4 ssaov = 	texture2D(m_SSAO, texCoord);
	gl_FragColor = vec4(tv.r*ssaov.r,tv.g*ssaov.r,tv.b*ssaov.r,1.0);
}

DepthSSAO.j3md

MaterialDef Fade {

    MaterialParameters {
        Int NumSamples
	Int NumSamplesDepth
        Texture2D Texture
        Texture2D DepthTexture
        Texture2D RandomTexture
	Float Intensity
	Float Base
	Float Area
	Float Falloff
	Float Radius
    }

    Technique {
        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert
        FragmentShader GLSL100: Shaders/Effects/DepthSSAO.frag

        WorldParameters {
            WorldViewProjectionMatrix
        }
    }
}

ApplySSAO.j3md

MaterialDef Fade {

MaterialParameters {
    Int NumSamples
	Int NumSamplesDepth
    Texture2D Texture
    Texture2D SSAO
    Texture2D DepthTexture
}

Technique {
    VertexShader GLSL100:   Common/MatDefs/Post/Post.vert
    FragmentShader GLSL100: Shaders/Effects/ApplySSAO.frag

    WorldParameters {
        WorldViewProjectionMatrix
    }
}
}

Note: The horizontal and vertical blur came from the jme3-effects jar, I extrected them since I’m using an older version without them, you can either extract them or just redirect the filter.java.

The reason I have an applySSAO material is because I am using that to do some custom stuff that is very specific to my project.

This bit:

vec3 sample_sphere[16] = vec3[](....loads of vec3

Worries me a little, I think it might not have worked on 3.1 (I’m using 3.0 atm) though I can’t remember exactly.

5 Likes

Pretty cool, but just for the record I added a way recently to get rid of the Normal pass also in the SSAO filter, and it comes as an option.

Though you must have set a very high radius for the engine’s ssao because you can see some sort of banding…
Yours looks actually better, except maybe for the grainy look…

Seems that the standard of the industry is now based on this paper from Morgan McGuire
http://graphics.cs.williams.edu/papers/SAOHPG12/

Intel recently published a paper and example code for a pretty neat SSAO which can scale in quality depending on the hardware.

Also this one with a temporal supersampling…

3 Likes

Oh cool, I will check out the SSAO option then and see if it works for my purpose, didn’t know about that.

One of the things I didn’t really follow/understand was the use of the noise texture, I feel like part of the issue with it is how I’ve used that - I got to that “it’s good enough for my purposes” stage and left it as is.

With the engine’s SSAO I tried default settings and could almost see nothing, I should probably have spent more time tweaking the settings while in game, but since I’m not using it I didn’t bother.

I’m only using SSAO on the character, and on the building only when caused by the character since my renders have SSAO from Blender Cycles. It’s going to be very subtle.

I’m going to make motion blur, rim lighting and lens flare next. I know some of these have been done but I find it very interesting having avoided shaders up till now.

Hm actually I kinda like your version, it is perfect for a darker game theme, were the added noise adds to the grafic style. (Think stalker, doom ect)