Reputation: 495
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?
Upvotes: 2
Views: 1156
Reputation: 211278
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);
Upvotes: 2