To draw power spectral density of a signal (which is very similar to heatmap), I use this vertex shader program. It receives value of power at each vertex, takes logarithm to show result in dB, normalizes within the range of colormap array, and assigns a color to vertex.
#version 130
uniform float max_val;
uniform float min_val;
uniform int height;
attribute float val; // power-spectral-density value assigned to each vertex
// colormap values
const float r[512] = float[]( /* red values come here */ );
const float g[512] = float[]( /* green values come here */ );
const float b[512] = float[]( /* blue values come here */ );
void main() {
// set vertex position based on its ID
int x = gl_VertexID / height;
int y = gl_VertexID - x * height;
gl_Position = gl_ModelViewProjectionMatrix * vec4(x, y, -1.0, 1.0);
float e = log(max_val / min_val);
float d = log(val / min_val);
// set color
int idx = int(d * (512 - 1) / e); // find normalized index that falls in range [0, 512)
gl_FrontColor = vec4(r[idx], g[idx], b[idx], 1.0); // set color
}
Corresponding C++ code is here:
QOpenGLShaderProgram glsl_program;
// initialization code is omitted
glsl_program.bind();
glsl_program.setUniformValue(vshader_max_uniform, max_val);
glsl_program.setUniformValue(vshader_min_uniform, min_val);
glsl_program.setUniformValue(vshader_height_uniform, max_colormap_height);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, colormap); // colormap is a vector that contains value of power at each vertex
glDrawElements(GL_TRIANGLE_STRIP, vertices_length, GL_UNSIGNED_INT, nullptr); // vertex_length is size of colormap
glDisableVertexAttribArray(0);
glsl_program.release();
This program runs fast enough on Linux. But in Windows, it is very slow and takes a lot of CPU time. If I change this line of GLSL:
// int idx = int(d * (512 - 1) / e);
int idx = 0;
then the app runs fast on Windows too. So, It has to be a problem with GLSL code.
How should I fix it?
What you're doing there belongs into the fragment shader, not the vertex shader. And you submit both the color lookup table and the spectral density data as a texture. Although vertex setup is not that expensive, it comes with a certain overhead and in general you want to cover as many pixels with the least number of vertices possible.
Also learn logarithm calculation rules (e.g. log(a/b) = log(a) - log(b)) and avoid doing calculations that are uniform over the whole draw call and precalculate on the host.
/* vertex shader */
#version 130
varying vec2 pos;
void main() {
// set vertex position based on its ID
// To fill the viewport, we need just three vertices
// of a rectangular triangle of with and height 2
pos.x = gl_VertexID % 2;
pos.y = gl_VertexID / 2;
// screen position is controlled using glViewport/glScissor
gl_Position = vec4(2*pos, 0, 1.0);
}
-
/* fragment shader */
#version 130
varying vec2 pos;
uniform sampler2D values;
uniform sampler1D colors;
uniform float log_min;
uniform float log_max;
void main() {
float val = texture2D(values, pos).x;
float e = log_max - log_min;
float d = (log(val) - log_min) / e;
gl_FragColor = vec4(texture1D(colors, d).rgb, 1.0); // set color
}
In later versions of GLSL some keywords have changed. Varyings are defined using in and out instead of varying and texture access functions have been unified to cover all sampler types.
glsl_program.bind();
glsl_program.setUniformValue(vshader_log_max_uniform, log(max_val));
glsl_program.setUniformValue(vshader_log_min_uniform, log(min_val));
// specify where to draw in window pixel coordinates.
glEnable(GL_SCISSOR_TEST);
glViewport(x, y, width, height);
glScissor(x, y, width, height);
glBindTexture(GL_TEXTURE_2D, values_texture);
glTexSubImage2D(GL_TEXTURE_2D, ..., spectral_density_data);
glDrawArrays(GL_TRIANGLES, 0, 3);
glsl_program.release();
Related
I am trying to use a glsl shader with p5js to create a simulation like the game of life. To do that I want to create a shader which will take a texture as uniform and which will draw a new texture based on this previous texture. In a next iteration this new texture will be used as uniform and that should allow me create a simulation following the idea exposed here. I am experienced with p5.js but I'm completely new to shader programming so I'm probably missing something.
For now my code is as straightforward as possible:
In the preload() function, I create a texture using the createImage() function and setup some pixels to be white and the others to be black.
In the setup() function I use this texture to run the shader a first time to create a new texture. I also set a timer to run the shader at regular intervals and draw the result in a buffer.
In the draw() function I draw the buffer in the canvas.
To keep things simple I keep the canvas and the texture the same size.
My issue is that at some point the y coordinates in my code seems to get inverted and I don't understand why. My understanding is that my code should show a still image but each time I run the shader the image is inverted. Here is what I mean:
I am not sure if my issue comes from how I use glsl or how I use p5 or a mix of both. Can someone explain to me where this weird y inversion comes from?
Here is my minimal reproducible example (which is also in the p5 editor here):
The sketch file:
const sketch = (p5) => {
const D = 100;
let initialTexture;
p5.preload = () => {
// Create the initial image
initialTexture = p5.createImage(D, D);
initialTexture.loadPixels();
for (let i = 0; i < initialTexture.width; i++) {
for (let j = 0; j < initialTexture.height; j++) {
const alive = i === j || i === 10 || j === 40;
const color = p5.color(250, 250, 250, alive ? 250 : 0);
initialTexture.set(i, j, color);
}
}
initialTexture.updatePixels();
// Initialize the shader
shader = p5.loadShader('uniform.vert', 'test.frag');
};
p5.setup = () => {
const canvas = p5.createCanvas(D, D, p5.WEBGL);
canvas.parent('canvasDiv');
// Create the buffer the shader will draw on
graphics = p5.createGraphics(D, D, p5.WEBGL);
graphics.shader(shader);
/*
* Initial step to setup the initial texture
*/
// Used to normalize the frag coordinates
shader.setUniform('u_resolution', [p5.width, p5.height]);
// First state of the simulation
shader.setUniform('u_texture', initialTexture);
graphics.rect(0, 0, p5.width, p5.height);
// Call the shader each time interval
setInterval(updateSimulation, 1009);
};
const updateSimulation = () => {
// Use the previous state as a texture
shader.setUniform('u_texture', graphics);
graphics.rect(0, 0, p5.width, p5.height);
};
p5.draw = () => {
p5.background(0);
// Use the buffer on the canvas
p5.image(graphics, -p5.width / 2, -p5.height / 2);
};
};
new p5(sketch);
The fragment shader which for now only takes the color of the texture and reuses it (I tried using st instead of uv to no avail):
precision highp float;
uniform vec2 u_resolution;
uniform sampler2D u_texture;
// grab texcoords from vert shader
varying vec2 vTexCoord;
void main() {
// Normalize the position between 0 and 1
vec2 st = gl_FragCoord.xy/u_resolution.xy;
// Get the texture coordinate from the vertex shader
vec2 uv = vTexCoord;
// Get the color at the texture coordinate
vec4 c = texture2D(u_texture, uv);
// Reuse the same color
gl_FragColor = c;
}
And the vertex shader which I took from an example and does nothing excepted passing the coordinates:
/*
* vert file and comments from adam ferriss https://github.com/aferriss/p5jsShaderExamples with additional comments from Louise Lessel
*/
precision highp float;
// This “vec3 aPosition” is a built in shader functionality. You must keep that naming.
// It automatically gets the position of every vertex on your canvas
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
// We always must do at least one thing in the vertex shader:
// tell the pixel where on the screen it lives:
void main() {
// copy the texcoords
vTexCoord = aTexCoord;
// copy the position data into a vec4, using 1.0 as the w component
vec4 positionVec4 = vec4(aPosition, 1.0);
positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
// Send the vertex information on to the fragment shader
// this is done automatically, as long as you put it into the built in shader function “gl_Position”
gl_Position = positionVec4;
}
Long story short: the texture coordinates for a rectangle or a plane drawn with p5.js are (0, 0) in the bottom left, and (1, 1) in the top right, where as the coordinate system for sampling values from a texture are (0, 0) in the top left and (1, 1) in the bottom right. You can verify this by commenting out your color sampling code in your fragment shader and using the following:
float val = (uv.x + uv.y) / 2.0;
gl_FragColor = vec4(val, val, val, 1.0);
As you can see by the resulting image:
The value (0 + 0) / 2 results in black in the lower left, and (1 + 1) / 2 results in white in the upper right.
So, to sample the correct portion of the texture you just need to flip the y component of the uv vector:
texture2D(u_texture, vec2(uv.x, 1.0 - uv.y));
const sketch = (p5) => {
const D = 200;
let initialTexture;
p5.preload = () => {
// This doesn't actually need to go in preload
// Create the initial image
initialTexture = p5.createImage(D, D);
initialTexture.loadPixels();
for (let i = 0; i < initialTexture.width; i++) {
for (let j = 0; j < initialTexture.height; j++) {
// draw a big checkerboard
const alive = (p5.round(i / 10) + p5.round(j / 10)) % 2 == 0;
const color = alive ? p5.color('white') : p5.color(150, p5.map(j, 0, D, 50, 200), p5.map(i, 0, D, 50, 200));
initialTexture.set(i, j, color);
}
}
initialTexture.updatePixels();
};
p5.setup = () => {
const canvas = p5.createCanvas(D, D, p5.WEBGL);
// Create the buffer the shader will draw on
graphics = p5.createGraphics(D, D, p5.WEBGL);
// Initialize the shader
shader = graphics.createShader(vert, frag);
graphics.shader(shader);
/*
* Initial step to setup the initial texture
*/
// Used to normalize the frag coordinates
shader.setUniform('u_resolution', [p5.width, p5.height]);
// First state of the simulation
shader.setUniform('u_texture', initialTexture);
graphics.rect(0, 0, p5.width, p5.height);
// Call the shader each time interval
setInterval(updateSimulation, 100);
};
const updateSimulation = () => {
// Use the previous state as a texture
shader.setUniform('u_texture', graphics);
graphics.rect(0, 0, p5.width, p5.height);
};
p5.draw = () => {
p5.background(0);
// Use the buffer on the canvas
p5.texture(graphics);
p5.rect(-p5.width / 2, -p5.height / 2, p5.width, p5.height);
};
const frag = `
precision highp float;
uniform vec2 u_resolution;
uniform sampler2D u_texture;
// grab texcoords from vert shader
varying vec2 vTexCoord;
varying vec2 vPos;
void main() {
// Get the texture coordinate from the vertex shader
vec2 uv = vTexCoord;
gl_FragColor = texture2D(u_texture, vec2(uv.x, 1.0 - uv.y));
//// For debugging uv coordinate orientation
// float val = (uv.x + uv.y) / 2.0;
// gl_FragColor = vec4(val, val, val, 1.0);
}
`;
const vert = `
/*
* vert file and comments from adam ferriss https://github.com/aferriss/p5jsShaderExamples with additional comments from Louise Lessel
*/
precision highp float;
// This “vec3 aPosition” is a built in shader functionality. You must keep that naming.
// It automatically gets the position of every vertex on your canvas
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
// We always must do at least one thing in the vertex shader:
// tell the pixel where on the screen it lives:
void main() {
// copy the texcoords
vTexCoord = aTexCoord;
// copy the position data into a vec4, using 1.0 as the w component
vec4 positionVec4 = vec4(aPosition, 1.0);
// This maps positions 0..1 to -1..1
positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
// Send the vertex information on to the fragment shader
// this is done automatically, as long as you put it into the built in shader function “gl_Position”
gl_Position = positionVec4;
}`;
};
new p5(sketch);
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
I am using samplerCube for a point light shadow map. For multiple lights, I implemented samplerCube as an array in the following.
uniform samplerCube pointShadowMapTexture[MAX_LIGHT];
But somehow I can't index this samplerCube. Shader compiles and there is no problem. This is working for sampler2D arrays.
I tried indexing it with [0], [1] .. in the shader but always the same image. I am sending different cube textures for each light but somehow shader doesn't index it or doesn't accept it.
I am doing the same for directional lights as sampler2D array. But when it comes to samplerCubes it doesn't work.
The code sending sampler cubes to the shader
void ShaderProgram::bindTexture(GLenum target , const char * name , int id){
GLuint TextureID = glGetUniformLocation(programID, name);
glActiveTexture(GL_TEXTURE0 + textureOrder);
glBindTexture(target , id);
glUniform1i(TextureID, textureOrder);
textureOrder++;
App::checkErrors(__FILE__,__LINE__,name);
}
//depthMapTexture is member of Light class
std::string PointShadowMapTexture = "pointShadowMapTexture[" + std::to_string(LightNumber) + "]";
ShaderProgram::shaders["deferred"]->bindTexture(GL_TEXTURE_CUBE_MAP, PointShadowMapTexture.data(), depthMapTexture );
float SoftPointShadowCalculation(int index , vec3 fragPos ,vec3 lightPos){
vec3 fragToLight = fragPos - lightPos;
float currentDepth = length(fragToLight);
float shadow = 0.0;
float bias = 0.0;
int samples = 20;
float viewDistance = length(viewPos - fragPos);
float diskRadius = (1.0 + (viewDistance / farPlane)) / 25.0;
for(int i = 0; i < samples; ++i){
float closestDepth = texture(pointShadowMapTexture[index], fragToLight + gridSamplingDisk[i] * diskRadius).r;
closestDepth *= farPlane;//farplane
if(currentDepth - bias > closestDepth){
shadow += 0.5;
}
}
shadow /= float(samples);
return shadow;
}
Is this valid for samplerCube type? If not what should I do to have an array of samplerCubes?
I realized that all lights are showing the what last light sees. So when I render model I was using the last light projection*view matrix for each light. :) It took hours to realize.
Now each light rendered with its own matrix.
this might help someone if encounters the same problem
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
for(Light * light : Light::shadowCasterLights){
glBindFramebuffer(GL_FRAMEBUFFER, light->depthMapFrameBuffer);App::checkFrameBufferError(__FILE__,__LINE__);
glViewport(0,0,light->depthMapTextureSize,light->depthMapTextureSize);
ShaderProgram::shaders[light->depthMapShader]->use("ShadowCaster Model");
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("ModelMatrix", &scene->ModelMatrix[0][0]);
ShaderProgram::shaders[light->depthMapShader]->attributeBuffer("ModelSpaceVertexPosition", mesh->vertexBufferID, 3);
switch (light->lightType) {
case LightType::DIRECTIONAL:
ShaderProgram::shaders[light->depthMapShader]->use("Light");
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix",&light->lightSpaceMatrix[0][0]);
break;
case LightType::POINT:
ShaderProgram::shaders[light->depthMapShader]->use("Light");
ShaderProgram::shaders[light->depthMapShader]->uniform3f("LightPosition" , &positionVector[0]);
ShaderProgram::shaders[light->depthMapShader]->uniform1f("FarPlane" , light->farPlane);
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix[0]",&light->lightSpaceMatrixCube[0][0][0]);
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix[1]",&light->lightSpaceMatrixCube[1][0][0]);
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix[2]",&light->lightSpaceMatrixCube[2][0][0]);
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix[3]",&light->lightSpaceMatrixCube[3][0][0]);
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix[4]",&light->lightSpaceMatrixCube[4][0][0]);
ShaderProgram::shaders[light->depthMapShader]->uniformMatrix4("LightSpaceMatrix[5]",&light->lightSpaceMatrixCube[5][0][0]);
break;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->elementBufferID);
glDrawElements(
GL_TRIANGLES, // mode
mesh->indices.size(), // count
GL_UNSIGNED_SHORT, // type
(void *) 0 // element array buffer offset
);
ShaderProgram::shaders[light->depthMapShader]->reset();
}
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0,0,1920,1080);
Upon the power of shadows
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);
The goal is to take an arbitrary geometry and create a 3D texture containing the voxel approximation of the scene. However right now we only have cubes.
The scene looks as follows:
The 2 most important aspects of these scene are the following:
Each cube in the scene is supposed to correspond to a voxel in the 3D texture. The scene geometry becomes smaller as the height increases (similar to a pyramid). The scene geometry is hollow (i.e if you go inside one of these hills the interior has no cubes, only the outline does).
To voxelize the scene we render layer by layer as follows:
glViewport(0, 0, 7*16, 7*16);
glBindFramebuffer(GL_FRAMEBUFFER, FBOs[FBO_TEXTURE]);
for(int i=0; i<4*16; i++)
{
glFramebufferTexture3D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D,
vMap->textureID, 0, i);
glClearColor(0.f, 0.f, 0.f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
load_uniform((float)i, "level");
draw();
}
Where "level" corresponds to the current layer.
Then in the vertex shader we attempt to create a single layer as follows;
#version 450
layout(location = 0) in vec3 position; //(x,y,z) coordinates of a vertex
layout(std430, binding = 3) buffer instance_buffer
{
vec4 cubes_info[];//first 3 values are position of object
};
out vec3 normalized_pos;
out float test;
uniform float width = 128;
uniform float depth = 128;
uniform float height = 128;
uniform float voxel_size = 1;
uniform float level=0;
void main()
{
vec4 pos = (vec4(position, 1.0) + vec4(vec3(cubes_info[gl_InstanceID]),0));
pos.x = (2.f*pos.x-width)/(width);
pos.y = (2.f*pos.y-depth)/(depth);
pos.z = floor(pos.z);
test = pos.z;
pos.z -= level;
gl_Position = pos;
}
Finally the fragment shader:
#version 450
in vec3 normalized_pos;
in float l;
in float test;
out vec4 outColor;//Final color of the pixel
void main()
{
outColor = vec4(vec3(test)/10.f, 1.0);
}
Using renderdoc I have taken some screenshots of what the resulting texture looks like:
Layer 0:
Layer 2:
The immediate 2 noticeable problems are that:
A layer should not have multiple tones of gray, only one (since each layer corresponds to a different height there should not be multiple heights being rendered to the same layer)
The darkest section of layer 2 looks like what layer 0 should look like (i.e a filled shape with no "holes"). So not only does it seem I am rendering multiple heights to teh same layer, it also seems I have an offset of 2 when rendering, which should not happen.
Does anyone have any idea as to what the problem could be?
EDIT:
In case anyone is wondering the cubes have dimenions of [1,1,1] And their coordinate system is aligned with teh texture. i.e the bottom, left, front corner of the first cube is at (0,0,0)
EDIT 2:
Changing
pos.z = floor(pos.z);
To:
pos.z = floor(pos.z)+0.1;
Partially fixes the problem. The lowest layer is now correct however instead of 3 different colors (height values) there's now 2.
EDIT 3:
It seems the problem comes from drawing the geometry multiple times.
i.e my actual draw clal looks like:
for(uint i=0; i<render_queue.size(); i++)
{
Object_3D *render_data = render_queue[i];
//Render multiple instances of the current object
multi_render(render_data->VAO, &(render_data->VBOs),
&(render_data->types), render_data->layouts,
render_data->mesh_indices, render_data->render_instances);
}
void Renderer::multi_render(GLuint VAO, vector<GLuint> *VBOs,
vector<GLuint> *buffer_types, GLuint layout_num,
GLuint index_num, GLuint instances)
{
//error check
if(VBOs->size() != buffer_types->size())
{
cerr << "Mismatching VBOs's and buffer_types sizes" << endl;
return;
}
//Bind Vertex array object and rendering rpogram
glBindVertexArray(VAO);
glUseProgram(current_program);
//enable shader layouts
for(int i=0; i<layout_num;i++)
glEnableVertexAttribArray(i);
//Bind VBO's storing rendering data
for(uint i=0; i<buffer_types->size(); i++)
{
if((*buffer_types)[i]==GL_SHADER_STORAGE_BUFFER)
{
glBindBuffer((*buffer_types)[i], (*VBOs)[i]);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, (*VBOs)[i]);
}
}
//Draw call
glDrawElementsInstanced(GL_TRIANGLES, index_num, GL_UNSIGNED_INT, (void*)0, instances);
}
It seems then that due to rendering multiple subsets of the scene at a time I end up with different cubes being mapped to the same voxel in 2 different draw calls.
I have figured out the problem.
Since my geometry matches the voxel grid 1 to 1. Different layers could be mapped to the same voxel, causing them to overlap in the same layer.
Modifying the fragment shader to the following:
#version 450
layout(location = 0) in vec3 position; //(x,y,z) coordinates of a vertex
layout(std430, binding = 3) buffer instance_buffer
{
vec4 cubes_info[];//first 3 values are position of object
};
out vec3 normalized_pos;
out float test;
uniform float width = 128;
uniform float depth = 128;
uniform float height = 128;
uniform float voxel_size = 1;
uniform float level=0;
void main()
{
vec4 pos = (vec4(position, 1.0) + vec4(vec3(cubes_info[gl_InstanceID]),0));
pos.x = (2.f*pos.x-width)/(width);
pos.y = (2.f*pos.y-depth)/(depth);
pos.z = cubes_info[gl_InstanceID].z;
test = pos.z + 1;
pos.z -= level;
if(pos.z >=0 && pos.z < 0.999f)
pos.z = 1;
else
pos.z = 2;
gl_Position = pos;
normalized_pos = vec3(pos);
}
Fixes the issue.
The if statement check guarantees that geometry from a different layer that could potentially be mapped to the current layer is discarded.
There are probably better ways to do this. So I will accept as an answer anything that produces an equivalent result in a more elegant way.
This is what layer 0 looks like now:
And this is what layer 2 looks like:
Writing a simple compute shader in OpenGL to understand how it works, I can't manage to obtain the wanted result.
I want to pass to my compute shader an array of structures colourStruct to color an output texture.
I would like to have a red image when "wantedColor" = 0 in my compute shader and a green image "wantedColor" = 1, blue for 2.
But I actually have only red when "wantedColor" = 1 or 2 or 3 and black when "wantedColor" > 2...
If someone has an idea, or maybe I did not understand the compute shader inputs ideas.
Thank you for your help, here is the interesting part of my code.
My compute shader :
#version 430 compatibility
layout(std430, binding=4) buffer Couleureuh
{
vec3 Coul[3]; // array of structures
};
layout(local_size_x = 1, local_size_y = 1) in;
layout(rgba32f, binding = 0) uniform image2D img_output;
void main() {
// base pixel colour for image
vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);
// get index in global work group i.e x,y, position
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
ivec2 dims = imageSize (img_output);
int colorWanted = 0;
pixel = vec4(Coul[colorWanted], 1.0);
// output to a secific pixel in the image
imageStore (img_output, pixel_coords, pixel);
}
Compute shader and SSBO initialization:
GLuint structBuffer;
glGenBuffers(1, &structBuffer);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, structBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, 3*sizeof(colorStruct), NULL, GL_STATIC_DRAW);
GLint bufMask = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT; // invalidate makes a ig difference when re-writting
colorStruct *coul;
coul = (colorStruct *) glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 3*sizeof(colorStruct), bufMask);
coul[0].r = 1.0f;
coul[0].g = 0.0f;
coul[0].b = 0.0f;
coul[1].r = 0.0f;
coul[1].g = 1.0f;
coul[1].b = 0.0f;
coul[2].r = 0.0f;
coul[2].g = 0.0f;
coul[2].b = 1.0f;
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, structBuffer);
m_out_texture.bindImage();
// Launch compute shader
m_shader.use();
glDispatchCompute(m_tex_w, m_tex_h, 1);
// Prevent samplign before all writes to image are done
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
vec3 are always 16-byte aligned. As such, when they're in an array, they act like vec4s. Even with std430 layout.
Never use vec3 in interface blocks. You should either use an array of floats (individually access the 3 members you want) or an array of vec4 (with an unused element).