• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
ArKano22

the best ssao ive seen

238 posts in this topic

So, I tried running this (ArKano22's SSAO) in OpenGL since I'm using Cg for my shaders, and I'm not getting the same result. Anyone know why this might be, or things I can check?

On the left is what I get in Direct3D9, and on the right is OpenGL


EDIT:

I checked the positions they each recreate and OpenGL's doesn't look like Direct3D 9's, so I looked at the texture offset
I'm using:

float GetRandom(float2 uv)
{
return tex2D(randomTex, screenSize * uv / 32.0f).x;
}

float2 Rotate(float2 c, float ang)
{
const float cosAng = cos(ang);
const float sinAng = sin(ang);

return float2(cosAng*c.x-sinAng*c.y, sinAng*c.x+cosAng*c.y);
}

float offset = sampleRadius/max(40.0,p.z);
float2 texOffset = Rotate(vec[4], GetRandom(IN.uv)*8.0)*offset;


It's not the most recent version, but with the limited testing I did, it seemed to be slightly slower. Regardless, here are my results with Direct3D 9 (which works) and OpenGL (which doesn't)


I'm thinking this has something to do with it, but I'm not sure if this is the root of the problem. Any thoughts?

[Edited by - AgentSnoop on March 27, 2010 7:13:39 PM]
0

Share this post


Link to post
Share on other sites
Quote:
Original post by DudeMiester
Perhaps it's because in OpenGL z values will have the opposite sign (ex: p.z)?


I tried messing with stuff like that and the texture coordinates. Already, I'm setting opengl to use 1.0 - uv.y where Direct3d 9 uses just uv.y.

When I just output the random texture to the screen, they are more or less the same.. it seems like the opengl one is doing bilinear filtering for some reason even though it's set to do nearest. The only way I can set it to nearest seems to be when I set everything to nearest.

Anyway, It's at the point when I do Rotate where things really start being different. When I look at the direct3d 9 screen, it's mainly red dots with some black and occasionally some green dots. When I look at the opengl version, it's mainly black with some red dots (maybe a few green dots too, but you can't really tell)

If I do max(Rotate(...), 0), I get more red dots on opengl (still looks darker because of the bilinear filtering)

Any thoughts?

EDIT: So, it seems if I just skip the rotate part, I don't get a messed up screen in OpenGL. Also, I don't notice too much a difference compared to with the Rotate function with Direct3D 9.

[Edited by - AgentSnoop on March 29, 2010 5:44:30 PM]
0

Share this post


Link to post
Share on other sites
I actually see very little difference between the two in terms of the visual patterns. It may just be an effect of the bilinear filtering.
0

Share this post


Link to post
Share on other sites
I didn`t wanted to create another SSAO topic, so I am posting to the recent one.
This is a default AO technique that uses only depth texture, so nothing new here, but my part in this is the sample gathering - instead of box blur, I aligned them circularly thus making the occlusion softer and more natural. I also used high-passed luminance texture to discard AO on the highlighted areas, as in real life AO is noticeable only in shadows.

results (animated gifs):




and a YouTube video:




Quote:

uniform sampler2D DepthTexture;
uniform sampler2D RenderedTexture;
uniform sampler2D LuminanceTexture;
uniform float RenderedTextureWidth;
uniform float RenderedTextureHeight;

#define PI 3.14159265

float width = RenderedTextureWidth; //texture width
float height = RenderedTextureHeight; //texture height

float near = 1.0; //Z-near
float far = 1000.0; //Z-far

int samples = 3; //samples on the each ring (3-7)
int rings = 3; //ring count (2-8)

vec2 texCoord = gl_TexCoord[0].st;

vec2 rand(in vec2 coord) //generating random noise
{
float noiseX = (fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453));
float noiseY = (fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453));
return vec2(noiseX,noiseY)*0.004;
}

float readDepth(in vec2 coord)
{
return (2.0 * near) / (far + near - texture2D(DepthTexture, coord ).x * (far-near));
}

float compareDepths( in float depth1, in float depth2 )
{
float aoCap = 1.0;
float aoMultiplier = 100.0;
float depthTolerance = 0.0000;
float aorange = 60.0;// units in space the AO effect extends to (this gets divided by the camera far range
float diff = sqrt(clamp(1.0-(depth1-depth2) / (aorange/(far-near)),0.0,1.0));
float ao = min(aoCap,max(0.0,depth1-depth2-depthTolerance) * aoMultiplier) * diff;
return ao;
}

void main(void)
{
float depth = readDepth(texCoord);
float d;

float aspect = width/height;
vec2 noise = rand(texCoord);

float w = (1.0 / width)/clamp(depth,0.05,1.0)+(noise.x*(1.0-noise.x));
float h = (1.0 / height)/clamp(depth,0.05,1.0)+(noise.y*(1.0-noise.y));

float pw;
float ph;

float ao;
float s;

for (int i = -rings ; i < rings; i += 1)
{
for (int j = -samples ; j < samples; j += 1)
{
float step = PI*2.0 / float(samples*i);
pw = (cos(float(j)*step)*float(i));
ph = (sin(float(j)*step)*float(i))*aspect;
d = readDepth( vec2(texCoord.s+pw*w,texCoord.t+ph*h));
ao += compareDepths(depth,d);
s += 1.0;
}
}

ao /= s;
ao = 1.0-ao;

vec3 color = texture2D(RenderedTexture,texCoord).rgb;
vec3 luminance = texture2D(LuminanceTexture,texCoord).rgb;
vec3 white = vec3(1.0,1.0,1.0);
vec3 black = vec3(0.0,0.0,0.0);
vec3 treshold = vec3(0.2,0.2,0.2);

luminance = clamp(max(black,luminance-treshold)+max(black,luminance-treshold)+max(black,luminance-treshold),0.0,1.0);

gl_FragColor = vec4(color*mix(vec3(ao,ao,ao),white,luminance),1.0);
}


[Edited by - martinsh on April 10, 2010 5:20:21 PM]
0

Share this post


Link to post
Share on other sites
@Martinsh

that is one of the best depth based methods i´ve seen. The images with shadows+ao are impressive! well done!
0

Share this post


Link to post
Share on other sites
Thank You ArKano22, yeah I am pretty amazed how sample gathering make a noticeable visual difference.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by martinsh
I also used high-passed luminance texture to discard AO on the highlighted areas

Surely you mean bright-passed... A high-pass is a frequency-domain operation ;)
0

Share this post


Link to post
Share on other sites
Quote:
Original post by Prune
Quote:
I also used high-passed luminance texture to discard AO on the highlighted areas
Surely you mean bright-passed... A high-pass is a frequency-domain operation ;)
Last I checked, light had a frequency too - regardless, the term is in common use with regards to image filters.
1

Share this post


Link to post
Share on other sites
Heh, yeah well I work also with sound and it seemed a quite appropriate to use high-pass instead of bright-pass :), anyway the principle is the same.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by swiftcoder
Last I checked, light had a frequency too

With that usage, the images would be blue-violet hued ;D

In all seriousness, I was just joking around of course
0

Share this post


Link to post
Share on other sites
Another update to the algorithm. It feels wrong contributing to the lenght of an already big thread, but this could be useful to some.

RM Project


Two changes:
-2D sampling has been left behind. The sampling method is now interleaved in object space, like the original implementation by Crytek. This allows to use only 8 samples per pixel, and stil obtain very good quality. This also makes it faster (sponza +130fps, while the old method ran at 100 fps.)

-I´ve added a "self-occlusion" artist variable. It is possible to achieve the traditional grayish look with brightened edges, but without halo artifacts, and you can control the amount of self occlusion added to the result. In the image below, you can see the Hebe model with different self-occlusion values. A bit of self-occlusion usually adds contrast and makes the effect more noticeable.

This is achieved by initializating the occlusion with some positive value (the self occlusion value), and modifying the the occlusion function so that it not only darkens creases (adds occlusion), but also brightens edges (subtracts occlusion).


-Added cubemap lighting and a texture to achieve a more in-game look.

[Edited by - ArKano22 on April 19, 2010 9:58:14 AM]
0

Share this post


Link to post
Share on other sites
ArKano22:

First off thanks for sharing your method... Now

I'm looking over the code and have a problem with a few things... could you explain the reasoning?

First off I see that vec[8] is initialized to the corners of a cube with the dimensions of (2,2,2). And then you do some pseudo random sampling in those directions. The problem is you are sampling out in 2D so your +1 or -1 in Z of those values never matters. In fact you're only passing the vec2 value of this to your doAmbientOcclusion function anyway. Did I miss something here? Wouldn't you be better off sampling in an 8 way circle if this is the case?

Now for the random length it seems you're sampling a texture with 4 values. That work out to always be one of (1.0,0.72,0.46,0.23) .. is that correct?

Thanks!
0

Share this post


Link to post
Share on other sites
Quote:
Original post by REF_Cracker
ArKano22:

First off thanks for sharing your method... Now

I'm looking over the code and have a problem with a few things... could you explain the reasoning?

First off I see that vec[8] is initialized to the corners of a cube with the dimensions of (2,2,2). And then you do some pseudo random sampling in those directions. The problem is you are sampling out in 2D so your +1 or -1 in Z of those values never matters. In fact you're only passing the vec2 value of this to your doAmbientOcclusion function anyway. Did I miss something here? Wouldn't you be better off sampling in an 8 way circle if this is the case?

Now for the random length it seems you're sampling a texture with 4 values. That work out to always be one of (1.0,0.72,0.46,0.23) .. is that correct?

Thanks!



Hello REF:

I´m using 3d samples, the corners of a 2x2x2 cube as you say. Then by multiplying each sample vector with the view transform, i rotate it along with the view. Then i use them as 2d points. The Z location does not matter anymore, but because of the view transform, the x,y values are not the same. So the Z value of each sample is needed to calculate the new x,y values passed to the occlusion function.

This sampling method results in a completely view-independent sampling pattern, because the pattern moves with the camera , and each pixel samples always the same zone, no matter how you move around the scene. This is the original ssao sampling method. 100% 2D sampling was developed after Crysis.

About the sampling lengths, yes, they are always these values. That is because i´m using interleaved sampling, so in a 2x2 area, each pixel samples a different lenght, then the 4 lengths are averaged together using a blur filter. This is also the original method, except Crysis used 4x4 interleaved sampling instead of 2x2 (i think, not sure about this).

So in fact, this method is Crytek´s with a different occlusion function that helps with halos and improves accuracy, nothing more.

EDIT: btw, i forgot to scale the samples with the screen size:
doAmbientOcclusion(i.uv,coord, p, n );
should be
doAmbientOcclusion(i.uv,coord*g_inv_screen_size*1000.0, p, n );

I will correct that as soon as possible.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by Prune
I can't really make out any quality difference between this and the earlier approach. Great result!



The quality is more or less the same. Maybe a bit more contrast with the darken/brighten scheme.

Ths difference is in speed, because the sampling is less random, and increases cache coherency. the other approach used at least 16 samples while this can use 8 or 14 for the same quality.
0

Share this post


Link to post
Share on other sites
Awesome, thanks for sharing.
that is some clever blurring.
I ended up using this line instead of texture.

Quote:

float getRandom(in float2 uv)
{
return ((frac(uv.x * (g_screen_size.x/2.0))*0.25)+(frac(uv.y*(g_screen_size.y/2.0))*0.75));
}


Looks the same, but you don`t need external random texture and I gained like 2 fps :D
0

Share this post


Link to post
Share on other sites
Quote:
Original post by martinsh
Awesome, thanks for sharing.
that is some clever blurring.
I ended up using this line instead of texture.

Quote:

float getRandom(in float2 uv)
{
return ((frac(uv.x * (g_screen_size.x/2.0))*0.25)+(frac(uv.y*(g_screen_size.y/2.0))*0.75));
}


Looks the same, but you don`t need external random texture and I gained like 2 fps :D


it should be
return ((frac(uv.x * (g_screen_size.x/2.0))*0.25)+(frac(uv.y*(g_screen_size.y/2.0))*0.5));

0.5 instead of 0.75.
neat trick! :D
0

Share this post


Link to post
Share on other sites
@martinish

There's an error in the code you posted. With rings=3 and samples=3 it generates the following pattern consisting of 36 samples. Samples on the inner two rings are all double.
Photobucket

You probably want to do something like this:

int ringsamples;
for (int i = 1; i <= rings; i += 1)
{
ringsamples = i * samples;
for (int j = 0 ; j < ringsamples ; j += 1)
{
float step = PI*2.0 / float(ringsamples);
pw = (cos(float(j)*step)*float(i));
ph = (sin(float(j)*step)*float(i))*aspect;
d = readDepth( vec2(texCoord.s+pw*w,texCoord.t+ph*h));
ao += compareDepths(depth,d);
s += 1.0;
}
}

For for rings=3 and samples=3 this results in only 18 samples.
0

Share this post


Link to post
Share on other sites
Looks great ArKano22!

After inspecting the RM project I have a few questions.

I see that you've switched to using view space positions when calculating the height difference instead of just depth, how do you feel this compares quality wise to using just depth?

getPosition uses tex2Dlod but always pick from the top mip map, is there a trick to this is or could one simply just use no mip maps together with tex2D?

In the full screen pass you pass the world -> projection matrix from the hebe statue to transform the cube used for sampling ( if I'm not mistaken hehe ). I don't quite see how this would be done when there's multiple objects being rendered. Also, couldn't 14 vector3s used for the cube be pre transformed as they don't change in the pixel shader? I'm thinking it could be a major speed up to not have to do 14 matrix multiplications per pixel.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by Ylisaren
Looks great ArKano22!

After inspecting the RM project I have a few questions.

I see that you've switched to using view space positions when calculating the height difference instead of just depth, how do you feel this compares quality wise to using just depth?

getPosition uses tex2Dlod but always pick from the top mip map, is there a trick to this is or could one simply just use no mip maps together with tex2D?

In the full screen pass you pass the world -> projection matrix from the hebe statue to transform the cube used for sampling ( if I'm not mistaken hehe ). I don't quite see how this would be done when there's multiple objects being rendered. Also, couldn't 14 vector3s used for the cube be pre transformed as they don't change in the pixel shader? I'm thinking it could be a major speed up to not have to do 14 matrix multiplications per pixel.


Thanks :).

For this method i always used positions instead of depth. This is because i do not use only the distance/difference between samples but also angular difference between the receiver´s normal and the vector from the receiver to the occluder. To compute this vector, positions are needed. The quality is better, and the haloing almost disappears. With this method you can also control the amount of self-occlusion, from 0 to completely self occluded. Worth the extra space used to store position, in my oppinion. But as always, it´s a tradeoff quality vs speed.

About the matrix used for the cube, it is the view transform as you say. You could precompute all positions in the vertex shader or even better in the cpu then pass it to the shader, because it´s not having perspective into account. Using this transform is "wrong", but it works well for usual fovs. For extreme fovs it is not as accurate but still looks good.

I´m quite unsure about what method is better: pure 2D sampling or view space sampling. 2D usually requires more samples but it is simpler. It´s a matter of taste, i guess.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by REF_Cracker
Results - Depth Buffer Only
Sampling 8 points per pixel.


That´s quite impressive! If you could reduce haloing...that´s the only showstopper i see (around the arm, its too dark)
0

Share this post


Link to post
Share on other sites
Quote:
Original post by ArKano22
Thanks :).

For this method i always used positions instead of depth. This is because i do not use only the distance/difference between samples but also angular difference between the receiver´s normal and the vector from the receiver to the occluder. To compute this vector, positions are needed. The quality is better, and the haloing almost disappears. With this method you can also control the amount of self-occlusion, from 0 to completely self occluded. Worth the extra space used to store position, in my oppinion. But as always, it´s a tradeoff quality vs speed.

you could compute positions from depth and use that, then, too, right? (just asking for verification). so there's no real quality difference, just computation speed against storage space.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by davepermen
Quote:
Original post by ArKano22
Thanks :).

For this method i always used positions instead of depth. This is because i do not use only the distance/difference between samples but also angular difference between the receiver´s normal and the vector from the receiver to the occluder. To compute this vector, positions are needed. The quality is better, and the haloing almost disappears. With this method you can also control the amount of self-occlusion, from 0 to completely self occluded. Worth the extra space used to store position, in my oppinion. But as always, it´s a tradeoff quality vs speed.

you could compute positions from depth and use that, then, too, right? (just asking for verification). so there's no real quality difference, just computation speed against storage space.


Yes, that's how I'm going to do it. The view space reconstruction implementation I use depends on vector interpolation between vertex -> pixel shader however, which can't be used when you're sampling arbitrary texels. As long as one has the 4 frustum corners ( even less than that is required ) one can construct the far position very cheaply per sample though.
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0