I am trying to implement Variance Shadow Mapping for directional shadows in my rendering engine with OpenGL.
I have read multiple articles such as - https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-8-summed-area-variance-shadow-maps, https://graphics.stanford.edu/~mdfisher/Shadows.html to develop this.
The basic flow of the algorithm is as follows:
Store the depth, and depth^2 in the depth texture.
Apply two pass Gaussian blur with a 5 x 5 kernel and 10 passes.
Sample a depth value, calculate the fragment's distance from the light, and
Put them in the Chebyshev inequality to determine the maximum probability of the fragment being in shadow
Use the result to make the fragment dark.
Here's my Depth Shader for the directional light with a orthographic projection matrix:
#version 440 core
uniform float farPlane;
uniform vec3 lightPos;
uniform mat4 directional_light_space_matrix;
in vec4 FragPos;
out vec2 depth;
void main()
{
vec4 FragPosLightSpace = directional_light_space_matrix * FragPos;
float d = FragPosLightSpace.z / FragPosLightSpace.w;
d = d * 0.5 + 0.5;
float m1 = d;
float m2 = d * d;
float dx = dFdx(depth.x);
float dy = dFdx(depth.y);
m2 += 0.25 * (dx * dx + dy * dy);
depth.r = m1;
depth.g = m2;
}
Here's the snippet of the fragment shader that check's how much a fragment is lit.
float linstep(float mi, float ma, float v)
{
return clamp ((v - mi)/(ma - mi), 0, 1);
}
float ReduceLightBleeding(float p_max, float Amount)
{
return linstep(Amount, 1, p_max);
}
float chebyshevUpperBound(float dist, vec2 moments)
{
float p_max;
if(dist <= moments.x)
{
return 1.0;
}
float variance = moments.y - (moments.x * moments.x);
variance = max(variance, 0.1);
float d = moments.x - dist;
p_max = variance / (variance + d * d);
return ReduceLightBleeding(p_max, 1.0);
}
float CheckDirectionalShadow(float bias, vec3 lightpos, vec3 FragPos)
{
vec3 projCoords = FragPosLightSpace.xyz / FragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
vec2 closest_depth = texture(shadow_depth_map_directional, projCoords.xy).rg;
return chebyshevUpperBound(projCoords.z, closest_depth);
}
Here's the Two Pass Gaussian Blur shader.
#version 440 core
layout (location = 0) out vec2 out_1;
in vec2 TexCoords;
uniform sampler2D inputTexture_1;
uniform bool horizontal;
float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
vec2 tex_offset = 1.0 / textureSize(inputTexture_1,0);
vec2 o1 = texture(inputTexture_1, TexCoords).rg * weights[0];
if(horizontal)
{
for(int i=1; i<4; i++)
{
o1 += texture(inputTexture_1, TexCoords + vec2(tex_offset.x * i, 0.0)).rg * weights[i];
o1 += texture(inputTexture_1, TexCoords - vec2(tex_offset.x * i, 0.0)).rg * weights[i];
}
}
else
{
for(int i=1; i<4; i++)
{
o1 += texture(inputTexture_1, TexCoords + vec2(0.0, tex_offset.y * i)).rg * weights[i];
o1 += texture(inputTexture_1, TexCoords - vec2(0.0, tex_offset.y * i)).rg * weights[i];
}
}
out_1 = o1;
}
I am putting my framebuffer generation code for information about how I store the moments.
// directional ----------------------------------------------------------------------------------------------------------------------------------------------
glGenFramebuffers(1, &directional_shadow_framebuffer);
glGenTextures(1, &directional_shadow_framebuffer_depth_texture);
glBindTexture(GL_TEXTURE_2D, directional_shadow_framebuffer_depth_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, shadow_map_width, shadow_map_height, 0, GL_RG, GL_FLOAT, NULL);
float border_color[] = { 0.0f,0.0f,0.0f,1.0f };
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
glBindFramebuffer(GL_FRAMEBUFFER, directional_shadow_framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, directional_shadow_framebuffer_depth_texture, 0);
glGenRenderbuffers(1, &directional_shadow_framebuffer_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, directional_shadow_framebuffer_renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, shadow_map_width, shadow_map_height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, directional_shadow_framebuffer_renderbuffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
LOGGER->log(ERROR, "Renderer : createShadowMapBuffer", "Directional Shadow Framebuffer is incomplete!");
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// ----------------------------------------------------------------------------------------------------------------------------------------------
The results of the above operations is far from expectations. Instead of getting soft penumbra shadows, I get blob like sharp shadows.
Here's how the First moment (depth) looks like, and the second moment is pretty much the same but darker.
I have tried experimenting with the minimum variance, shadow kernel size, gaussian samples, blur passes.. but I haven't come any closer to the solution.
I have a feeling I maybe doing something wrong with how I have set the texture filtering parameters in the Framebuffer generation code given above.
My final questions are :
Is my implementation of VSMs incorrect?
Why do I not see soft penumbras?
I don't have a good feeling about how my texture is filtered, is there anything wrong in the Framebuffer generation code?
So, I had solved the problem.
The implementation is perfectly fine, but the min variance and the amount parameter of the ReduceLightBleeding required tuning.
I discovered that reducing the minimum variance parameter would soften the shadows more, but would greatly increase Light Bleeding.
To counter this side effect we can tune the p_max value to become 0 when below a certain threshold, otherwise rescale between 0 and 1. This is exactly what the ReduceLightBleeding function does, which is also described in the same site linked above.
But, increasing the amount parameter in ReduceLightBleeding would make the shadows look blob-like, which can be seen in the screenshots that I posted above.
I managed to tweak the min variance and the light bleeding reduction amounts to find an optimal spot. However, I could never completely get rid of this artifact.
A better alternative to Variance Shadow Mapping is its extension - Exponential Variance Shadow Maps.
I do not understand the math properly, but I still managed to implement it quite easily.
Check this question on gamedev.stackexchange for hints - EVSM.
ESVM did a great job by reducing bleeding to the point that it can either not be noticed or just ignored.
Related
While working on a project, I encountered the common problem of pixel bleeding when trying to draw subregions of my sprite sheet. This caused sort of "seams" to appear at the edges of my sprites. You can see the issue here, on the right and top of the sprite.
Doing some searching, I found others with a similar problem, and a suggested solution (here, and here for example) was to offset my texture coordinates by a bit, such as 0.5. I tried this, and it seemed to work. But I have noticed that sometimes, depending on where the sprite or camera is, I get a bit of distortion on the sprites. Here, the left side appears to be cut off, and here, the bottom seems to have expanded. (I should note, the distortion happens on all sides, I just happened to take screenshots of it happening on the bottom and left.) It may be a little difficult to see in screenshots, but it is definitely noticeable in motion.
For reference, here is the part of the sprite sheet that is being displayed here
Does anybody have any idea what is going on here? I didn't actually notice this issue until recently. I originally set out to resolve the pixel bleeding when I saw it occurring between my tile sprites. This new issue does not occur with them using my current half-pixel offset solution (or if it does, it's not noticeable).
Code:
Texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
Texture coordinate calculation
std::vector<glm::vec4> Texture2D::GetUVs(int w, int h)
{
std::vector<glm::vec4> uvs;
int rows = Width/ w;
int columns = Height / h;
for(int c = 0; c < columns; c ++)
{
for(int i = 0; i < rows; i ++)
{
float offset = 0.5;
uvs.emplace_back(glm::vec4(float(((i) * w + offset))/Width,
float(((1 + i) * w - offset))/Width,
float(((c) * h + offset))/Height,
float(((1 + c) * h - offset))/Height));
}
}
return uvs;
Where Width and Height are the dimensions of the sprite sheet, and w and h are the dimensions of the subregion, in this case 32 and 32.
How I pass the uvs to the shader
GLfloat verticies[] =
{
uv.x, uv.w,
uv.y, uv.z,
uv.x, uv.z,
uv.x, uv.w,
uv.y, uv.w,
uv.y, uv.z
};
this->shader.Use().SetVector2fv("uvs", 12, verticies);
Where uv is the uv at an index in the uvs vector that was returned above in the GetUVs function.
Vertex shader
#version 330 core
layout (location = 0) in vec2 vertex;
out vec2 TextureCoordinates;
uniform vec2 uvs[6];
uniform mat4 model;
uniform mat4 projection;
void main()
{
const vec2 position [6] = vec2[]
(
vec2(0.0f, 1.0f),
vec2(1.0f, 0.0f),
vec2(0.0f, 0.0f),
vec2(0.0f, 1.0f),
vec2(1.0f, 1.0f),
vec2(1.0f, 0.0f)
);
TextureCoordinates = uvs[gl_VertexID];
gl_Position = projection * model * vec4(position[gl_VertexID], 0.0, 1.0);
}
Fragment shader
#version 330 core
in vec2 TextureCoordinates;
out vec4 color;
uniform sampler2D image;
uniform vec4 spriteColor;
void main()
{
color = vec4(spriteColor) * texture(image, TextureCoordinates);
}
Thanks for reading, any help is appreciated.
I'm trying to use an off-screen framebuffer to replicate a scene that renders wonderfully to the default framebuffer. There seem to be differences in the rendering that I can't sort out.
For context, I am visualizing the Earth with an atmospheric shader. I am using a QT QOpenGLWidget, but mostly raw GL calls because I'm not a fan of QT's abstractions. I need to render this scene to an off-screen framebuffer because I would like to implement some post-processing effects in my visualization, for which I need to be able to sample the scene as a texture. I've gotten to the point where I am successfully creating a framebuffer and rendering its color texture to a quad on the screen.
My understanding is that alpha blending behaves differently when rendering to an off-screen framebuffer compared to the default. I haven't been able to find any resources online that indicate a way to produce identical results without a major refactor. The methodologies I've seen involve either manually rendering objects in order from back to front, or baking in the alpha values to the colors that are sent to the framebuffer. I've tried an often suggested alternative, which is using glBlendFuncSeparate to control things more manually:
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
But that hasn't led to any noticeable improvement in my results (nor would I expect it to, since the math here wouldn't resolve the blending issues that I'm seeing).
So enough rambling, onto some actual code. My code-base is monstrous so I unfortunately can't share all of it, as there are a number of proprietary drawing routines, but I can start with how I generate my framebuffer:
// Create the framebuffer object
glGenFramebuffers(1, &m_fbo);
// Bind the framebuffer to the current context
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
// generate texture to attach as a color attachment to the current frame buffer
m_texColorUnit = 4;
// Set to width and height of window, and leave data uninitialized
glGenTextures(1, &m_texColorBuffer);
glActiveTexture(GL_TEXTURE0 + m_texColorUnit);
glBindTexture(GL_TEXTURE_2D, m_texColorBuffer);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB8_OES,
m_navigation->renderContext()->getWidth(),
m_navigation->renderContext()->getHeight(),
0,
GL_RGB8_OES,
GL_UNSIGNED_BYTE,
NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// attach texture to currently bound framebuffer object
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
m_texColorBuffer,
0);
glBindTexture(GL_TEXTURE_2D, 0); //unbind the texture
glActiveTexture(GL_TEXTURE0); // Reset active texture to default
// Create renderBuffer object for depth and stencil checking
glGenRenderbuffers(1, &m_rbo);
glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); // bind rbo
glRenderbufferStorage(GL_RENDERBUFFER,
GL_DEPTH24_STENCIL8_OES,
m_navigation->renderContext()->getWidth(),
m_navigation->renderContext()->getHeight()
); // allocate memory
// Attach rbo to the depth and stencil attachment of the fbo
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_DEPTH_STENCIL_OES,
GL_RENDERBUFFER,
m_rbo);
And the shaders for the atmosphere:
// vert
#ifndef GL_ES
precision mediump int;
precision highp float;
#endif
attribute vec3 posAttr;
uniform highp mat4 matrix;
uniform highp mat4 modelMatrix;
uniform vec3 v3CameraPos; // The camera's current position
uniform vec3 v3LightPos; // The direction vector to the light source
uniform vec3 v3InvWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels
uniform float fCameraHeight; // The camera's current height
uniform float fCameraHeight2; // fCameraHeight^2
uniform float fOuterRadius; // The outer (atmosphere) radius
uniform float fOuterRadius2; // fOuterRadius^2
uniform float fInnerRadius; // The inner (planetary) radius
uniform float fInnerRadius2; // fInnerRadius^2
uniform float fKrESun; // Kr * ESun
uniform float fKmESun; // Km * ESun
uniform float fKr4PI; // Kr * 4 * PI
uniform float fKm4PI; // Km * 4 * PI
uniform float fScale; // 1 / (fOuterRadius - fInnerRadius)
uniform float fScaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found)
uniform float fScaleOverScaleDepth; // fScale / fScaleDepth
const int nSamples = 5;
const float fSamples = 5.0;
varying vec3 col;
varying vec3 colatten;
varying vec3 v3Direction;
varying vec3 vertexWorld;
float scale(float fCos)
{
float x = 1.0 - fCos;
return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}
void main(void)
{
// Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)
vec3 v3Pos = posAttr;
vec3 vertexWorld = posAttr;
vec3 v3Ray = v3Pos - v3CameraPos;
float fFar = length(v3Ray);
v3Ray /= fFar;
// Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray passing through the atmosphere)
float B = 2.0 * dot(v3CameraPos, v3Ray);
float C = fCameraHeight2 - fOuterRadius2;
float fDet = max(0.0, B*B - 4.0 * C);
float fNear = 0.5 * (-B - sqrt(fDet));
// Calculate the ray's starting position, then calculate its scattering offset
vec3 v3Start = v3CameraPos + v3Ray*fNear;
fFar -= fNear;
float fStartAngle = dot(v3Ray, v3Start) / fOuterRadius;
float fStartDepth = exp(-1.0 / fScaleDepth);
float fStartOffset = fStartDepth*scale(fStartAngle);
// Initialize the scattering loop variables
float fSampleLength = fFar / fSamples;
float fScaledLength = fSampleLength * fScale;
vec3 v3SampleRay = v3Ray * fSampleLength;
vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5;
// Now loop through the sample rays
vec3 v3FrontColor = vec3(0.0, 0.0, 0.0);
for(int i=0; i<nSamples; i++)
{
float fHeight = length(v3SamplePoint);
float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight));
float fLightAngle = dot(v3LightPos, v3SamplePoint) / fHeight;
float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight;
float fScatter = (fStartOffset + fDepth*(scale(fLightAngle) - scale(fCameraAngle)));
vec3 v3Attenuate = exp(-fScatter * (v3InvWavelength * fKr4PI + fKm4PI));
v3FrontColor += v3Attenuate * (fDepth * fScaledLength);
v3SamplePoint += v3SampleRay;
}
// Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader
colatten = v3FrontColor * fKmESun;
col = v3FrontColor * (v3InvWavelength*fKrESun);
v3Direction = v3CameraPos - v3Pos;
gl_Position = matrix * modelMatrix * vec4(posAttr,1);
}
// frag
#ifdef GL_ES
precision highp float;
precision mediump int;
#endif
varying vec3 col;
varying vec3 colatten;
varying vec3 v3Direction;
varying vec3 vertexWorld;
uniform vec3 v3LightPos;
uniform float g;
uniform float g2;
uniform float fExposure;
void main (void)
{
//float fCos = dot(normalize(lPos), normalize(v3Direction));
float fCos = dot(v3LightPos, v3Direction) / length(v3Direction);
float fRayleighPhase = 0.75 * (1.0 + fCos*fCos);
float fMiePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos*fCos) / pow(1.0 + g2 - 2.0*g*fCos, 1.5);
//vec3 result = clamp(col + fMiePhase * colatten, vec3(0,0,0), vec3(1,1,1));
//gl_FragColor = vec4(result, result.b);
gl_FragColor.rgb = 1.0 - exp(-fExposure * (fRayleighPhase * col + fMiePhase * colatten));
//gl_FragColor.a = 1.0;
gl_FragColor.a = gl_FragColor.b;
}
As I've said, my results are less than stellar. The first image is what I get when rendering to the off-screen framebuffer, and the second image is when I render directly to the screen. Any ideas on how to resolve these two?
The depth render buffer is not attached to the framebuffer. The 2nd parameter of glFramebufferRenderbuffer has to be the attachment point.
GL_DEPTH_STENCIL_OES is not a valid value for a attachment point. So
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_DEPTH_STENCIL_OES,
GL_RENDERBUFFER,
m_rbo);
will cause GL_INVALID_ENUM error, which can be get by glGetError.
The enumerator constant which specifies the depth and stencil buffer is GL_DEPTH_STENCIL_ATTACHMENT:
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER,
m_rbo);
Note, the depth/stencil buffer is not attached to the framebuffer, but the framebuffer is still complete, without a depth and stencil buffer.
Alternatively you can use a depth buffer only attachment. Create a depth render buffer (GL_DEPTH_COMPONENT) add use the attachment type GL_DEPTH_ATTACHMENT.
The issue is caused, because the texture, which is attached to the color plane of the framebuffer has no alpha channel. The format GL_RGB8_OES provides the 3 color channels (RGB) but no alpha channel.
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB8_OES,
m_navigation->renderContext()->getWidth(),
m_navigation->renderContext()->getHeight(),
0,
GL_RGB8_OES,
GL_UNSIGNED_BYTE,
NULL);
You've to use the format and internal format GL_RGBA8_OES rather than GL_RGB8_OES, which is included in OES_required_internalformat, too. See also __gles2_gl2ext_h_:
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA8_OES,
m_navigation->renderContext()->getWidth(),
m_navigation->renderContext()->getHeight(),
0,
GL_RGBA8_OES,
GL_UNSIGNED_BYTE,
NULL);
I have a problem in which when the camera gets closer to the model the performance drops.
I figured out that it was todo with the ssao sample kernels, but I cant seem to figure out why these are causing performance issues when close to a mesh.
When I comment out the samples for loop in the ssao render code that is when the performance goes back to how it should be, so this for loop is obviously some how causing the issue. I orginally thought it might be a shader problem but I cant find any problems in there either.
Any ideas? Here is all the code that you need...
SSAO Setup Code
// Create two frame buffers, one for ssao colour and another for ssao blur
_fbos.push_back(new Fbo(width, height, { new FboAttachment(width, height, GL_RED, GL_RGB, GL_FLOAT, GL_COLOR_ATTACHMENT0) }, false));
_fbos.push_back(new Fbo(width, height, { new FboAttachment(width, height, GL_RED, GL_RGB, GL_FLOAT, GL_COLOR_ATTACHMENT0) }, false));
//////////////////////////////////////////////////////////////////////////////////////////////////////////
std::uniform_real_distribution<GLfloat> rand_floats(0.0f, 1.0f); // Generate random floats between 0.0 and 1.0
std::default_random_engine rand_generator; // A generator for randomising floats
// Create temp iterator var
for (unsigned int i = 0; i < 64; ++i) // Iterate through each sample...
{
glm::vec3 sample(rand_floats(rand_generator) * 2.0f - 1.0f, rand_floats(rand_generator) * 2.0f - 1.0f, rand_floats(rand_generator)); // the third parameter was wrong on this line
sample = glm::normalize(sample); // Normalise the sample
sample *= rand_floats(rand_generator); // Seed the randomisation
float scale = static_cast<float>(i) / 64.0f; // Get pixel position in NDC about the resolution size
scale = Math::lerpf(0.1f, 1.0f, scale * scale); // Interpolate the scale
sample *= scale; // Scale the s and t values
_ssao_kernals.push_back(sample); // Assign sample to the kernal array
_u_samples.push_back(glGetUniformLocation(shader_programs[0], ("samples[" + std::to_string(i) + "]").c_str())); // Get each sample uniform location
}
for (unsigned int i = 0; i < 16; i++) // For each sample / 4...
{
glm::vec3 noise(rand_floats(rand_generator) * 2.0f - 1.0f, rand_floats(rand_generator) * 2.0f - 1.0f, 0.0f); // Randomly generate a noise pixel
_ssao_noise.push_back(noise); // Assign noise pixel to noise array
}
/*
* Create a noise texture to remove any banding from the ssao
*/
glGenTextures(1, &_noise_texture); // generate the texture
glBindTexture(GL_TEXTURE_2D, _noise_texture); // bind data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, 4, 4, 0, GL_RGB, GL_FLOAT, &_ssao_noise[0]); // set texture data
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // texture filtering
SSAO Render Function
_fbos[0]->Bind(); // bind ssao texture
glClear(GL_COLOR_BUFFER_BIT); // clear colour data on the screen
glUseProgram(_shader_programs[0]); // Use the first shader pass
for (unsigned int i = 0; i < SSAO_SAMPLE_RESOLUTION; ++i) // For each ssao sample...
glUniform3fv(_u_samples[i], 1, glm::value_ptr(_ssao_kernals[i])); // Assign kernal uniform data
glUniformMatrix4fv(_u_projection, 1, GL_FALSE, glm::value_ptr(Content::_map->GetCamera()->GetProjectionMatrix())); // Assign camera projection uniform data
glActiveTexture(GL_TEXTURE0); // Set active texture to index 0
glBindTexture(GL_TEXTURE_2D, _g_buffer_data->GetAttachments()[0]->_texture); // Bind positions
glActiveTexture(GL_TEXTURE1); // Set active texture to index 1
glBindTexture(GL_TEXTURE_2D, _g_buffer_data->GetAttachments()[1]->_texture); // Bind normals
glActiveTexture(GL_TEXTURE2); // Set active texture to index 2
glBindTexture(GL_TEXTURE_2D, _noise_texture); // Bind the noise texture
_screen_rect->Render(1); // Render to screen rectangle
// Blur ssao texture
_fbos[1]->Bind();
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(_shader_programs[1]); // Use the second shader pass
glActiveTexture(GL_TEXTURE0); // Bind active texture to index 0
glBindTexture(GL_TEXTURE_2D, _fbos[0]->GetAttachments()[0]->_texture); // Bind the final colour
_screen_rect->Render(1); // Render to screen rectangle
SSAO Fragment Shader
#version 330 core
out float FragColor;
in vec2 _texcoord;
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D texNoise;
uniform vec3 samples[64];
int kernelSize = 64;
float radius = 0.3;
float bias = 0.025;
const vec2 noiseScale = vec2(1920.0 / 4.0, 1080.0 / 4.0);
uniform mat4 proj;
void main()
{
vec3 fragPos = texture(gPosition, _texcoord).xyz;
vec3 normal = normalize(texture(gNormal, _texcoord).rgb);
vec3 randomVec = normalize(texture(texNoise, _texcoord * noiseScale).xyz);
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal);
float occlusion = 0.0;
for(int i = 0; i < kernelSize; ++i)
{
// get sample position
vec3 sample = TBN * samples[i]; // from tangent to view-space
sample = fragPos + sample * radius;
// project sample position (to sample texture) (to get position on screen/texture)
vec4 offset = vec4(sample, 1.0);
offset = proj * offset; // from view to clip-space
offset.xyz /= offset.w; // perspective divide
offset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0
// get sample depth
float sampleDepth = texture(gPosition, offset.xy).z; // get depth value of kernel sample
// range check & accumulate
float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0) * rangeCheck;
}
occlusion = 1.0 - (occlusion / kernelSize);
FragColor = pow(occlusion, 3.0);
}
This is the expected performance characteristic of SSAO.
The closer the texel you're calculating the AO for is to the camera the farther away the sample points around it will be in screen-space, and the less likely it is that those neighboring texels will be in the GPU's texture cache - which causes a massive performance hit.
I'm trying to implement ray casting based volume rendering and therefore I'd need to pass a float Array to the fragment shader as a Texture (Sampler3D).
I've got a volume datastructure containing all the voxels. Each voxel contains a density value. So for processing I stored the values into a float Array.
//initialize glew, initialize glfw, create window, etc.
float* density;
density = new float[volume->size()];
for (int i = 0; i < volume->size(); i++){
density[i] = volume->voxel(i).getValue();
}
Then I tried creating and binding the textures.
glGenTextures(1, &textureHandle);
glBindTexture(GL_TEXTURE_3D, textureHandle);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE, volume->width(),
volume->height(), volume->depth(), 0, GL_LUMINANCE, GL_FLOAT, density);
In my render loop I try to load the Texture to the uniform Sampler3D.
glClearColor(0.4f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
GLint gSampler = glGetUniformLocation(shader->shaderProgram, "volume");
glUniform1i(gSampler, 0);
cube->draw();
So the basic idea is to calculate the current position and direction for ray casting in the Vertex Shader.
in vec3 position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform vec4 cameraPos;
out vec3 pos;
out vec3 dir;
void main(){
gl_Position = projection * view * model * vec4(position, 1.0);
pos = position;
dir = pos - (inverse(model) * cameraPos).xyz;
}
That seems to work well, so far so good. The fragment shader looks like this. I take some samples along the ray and the one with the largest density value will be taken as a color for red, green and blue.
#version 330 core
in vec3 pos;
in vec3 dir;
uniform sampler3D volume;
out vec4 color;
const float stepSize = 0.008;
const float iterations = 1000;
void main(){
vec3 rayDir = normalize(dir);
vec3 rayPos = pos;
float src;
float dst = 0;
float density = 0;
for(int i = 0; i < iterations; i++){
src = texture(volume, rayPos).r;
if(src > density){
density = src;
}
rayPos += rayDir * stepSize;
//check whether rays are within bounds. if not -> break.
}
color = vec4(density, density, density, 1.0f);
}
Now I've tried inserting some small debug assertions.
if(src != 0){
rayPos = vec3(1.0f);
break;
}
But src seems to be 0 at every iteration of every pixel. Which gets me to the conclusion that the Sampler isn't correctly set. Debugging the C++ code I get the correct values for the density array right before I pass it to the shader, so I guess there must be some opengl function missing. Thanks in advance!
glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE, volume->width(), volume->height(), volume->depth(), 0, GL_LUMINANCE, GL_FLOAT, density);
Unless this density is on the range [0, 1], then this is almost certainly not doing what you intend.
GL_LUMINANCE, when used as an internal format (the third parameter to glTexImage3D, means that each pixel in OpenGL's texture data will contain a single normal integer value. So if you want a floating-point value, you're kinda out of luck.
The proper way to do this is to explicitly declare the type and pixel size of the data. Luminance was removed from the core OpenGL profile back in 3.1, so the way to do that today is to use GL_R32F as your internal format. That declares that each pixel contains one value, and that value is a 32-bit float.
If you really need to broadcast the value across the RGB channels, you can use texture swizzling to accomplish that. You can set a swizzle mask to broadcast the red component to any other channel you like.
glActiveTexture(GL_TEXTURE0);
GLint gSampler = glGetUniformLocation(shader->shaderProgram, "volume");
glUniform1i(gSampler, 0);
I've heard that binding the texture is also a good idea. You know, if you actually want to read from it ;)
I have the correct inverse projection matrix. But nothing seem to be right!
The reconstructed position ist totally deformed und the z-Value ist much to small? Anyone a suggestion?
vec3 calculatePosition(vec2 coord, float depth)
{
vec4 clipSpaceLocation;
clipSpaceLocation.x = coord.x * 2.0 - 1.0;
clipSpaceLocation.y = coord.y * 2.0 - 1.0;
clipSpaceLocation.z = depth * 2.0 - 1.0;
clipSpaceLocation.w = 1.0;
vec4 homogenousPosition = uProjectionInverse * clipSpaceLocation;
return homogenousPosition.xyz / homogenousPosition.w;
}
the z-Value is round about -0,001; Why that?
coord und depth is:
vec2 coord = vec2(gl_FragCoord.x / width, gl_FragCoord.y / height);
float currentDepth = texture(depthBuffer, coord).r;
I have to implement SSAO for university and i use Eclipse and Java with the lwjgl-plugin.
Please i need help. I got less than one week.
EDIT:
I have tried this now... but still not lenearized:
float camera_space_z_from_depth(sampler2D depthbuffer, vec2 uv) {
float depth = texture(depthbuffer, uv).x;
return (2 * uNearPlane) / (uFarPlane + uNearPlane - depth * (uFarPlane - uNearPlane));
}
vec3 calculatePosition(vec2 coord, float depth)
{
vec4 clipSpaceLocation;
clipSpaceLocation.x = coord.x * 2.0 - 1.0;
clipSpaceLocation.y = coord.y * 2.0 - 1.0;
clipSpaceLocation.z = depth * 2.0 - 1.0;
clipSpaceLocation.w = 1.0;
vec4 homogenousPosition = uProjectionInverse * clipSpaceLocation;
return homogenousPosition.xyz / homogenousPosition.w;
}
vec3 getPosition(vec2 coord){
float currentDepth = camera_space_z_from_depth(depthBuffer,coord);
vec3 position = calculatePosition(coord, currentDepth);
return position;
}
I suppose you need to linearize your depth value, like said here:
https://www.opengl.org/wiki/Compute_eye_space_from_window_space
Just try this.
float camera_space_z_from_depth(sampler2D depthbuffer, vec2 uv) {
const float depth = texture(depthbuffer, uv).x;
return ( camera_near / (camera_far - depth * (camera_far - camera_near)) ) * camera_far;
}
EDIT:
Try that. Variable meaning should be self explanatory (no need for linearization) :)
vec3 GetPosition(vec2 fragmentCoordinates, float depth)
{
vec3 normalizedDeviceCoordinatesPosition;
normalizedDeviceCoordinatesPosition.xy = (2.0 * fragmentCoordinates) / uScreenSize - 1;
normalizedDeviceCoordinatesPosition.z = 2.0 * depth - 1.0;
vec4 clipSpacePosition;
clipSpacePosition.w = uProjection[3][2] / (normalizedDeviceCoordinatesPosition.z - (uProjection[2][2] / uProjection[2][3]));
clipSpacePosition.xyz = normalizedDeviceCoordinatesPosition * clipSpacePosition.w;
vec4 eyePosition = uInverseProjection * clipSpacePosition;
return eyePosition.xyz / eyePosition.w;
}
Note: So far I'm assuming your depth texture is a GL_DEPTH_COMPONENT and is attached to the framebuffer as a GL_DEPTH_ATTACHMENT.
I.e.:
glBindTexture(GL_TEXTURE_2D, mDepthTextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, mrScreenWidth, mrScreenHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, mDepthTextureId, 0);