I am having a weird problem with compute shaders since I changed the structure size of a buffer I was passing in to the shader.
struct Particle
{
vec3 position;
vec2 uv;
vec3 accumulated_normal;
int id;
int flattened_id;
int movable;
// can the particle move or not ? used to pin parts of the cloth
float mass;
// the mass of the particle (is always 1 in this example)
vec3 old_pos;
// the position of the particle in the previous time step, used as part of the verlet numerical integration scheme
vec3 acceleration;
// a vector representing the current acceleration of the particle
};
Defined like this. I was having problems trying to use the flattened id so I wanted to write the id's back to the buffer I was passing in. The shader looks something like this.
layout (local_size_x = 16, local_size_y = 1, local_size_z = 1) in;
void main()
{
unsigned int flattened_id = gl_LocalInvocationIndex;
particleBuffer.particles[0].id = 16;
particleBuffer.particles[1].id = 17;
particleBuffer.particles[2].id = 18;
particleBuffer.particles[3].id = 19;
//particleBuffer.particles[4].id = 20;
}
So till this point it is fine but the moment I uncomment the last line which is the particleBuffer.particles[4], the mesh vanishes from the screen. I have managed to change the position data from this mesh before but this seems strange. I verified that I have indeed 16 elements of the buffer which I am passing in so it should not go out of bounds either (i.e. cloth1.particles.size() = 16). Any ideas ??
The entire code is here..
https://github.com/ssarangi/OpenGL_Cloth/tree/master/cloth_4_3_compute
glUseProgram(computeShader);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, cloth1.vertex_vbo_storage);
glBufferData(GL_SHADER_STORAGE_BUFFER, cloth1.particles.size() * sizeof(Particle), &(cloth1.particles[0]), GL_DYNAMIC_COPY);
glDispatchCompute(1, 1, 1);
{
GLenum err = gl3wGetError();
}
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, cloth1.vertex_vbo_storage);
Particle * ptr = reinterpret_cast<Particle *>(glMapBufferRange(GL_ARRAY_BUFFER, 0, cloth1.particles.size() * sizeof(Particle), GL_MAP_READ_BIT));
memcpy(&cloth1.particles[0], ptr, cloth1.particles.size()*sizeof(Particle));
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
*********************** EDITED with Andon's comments ******************************
This is the new layout from the C++ side.
struct Particle
{
vec4 position;
vec2 uv;
vec4 accumulated_normal;
vec4 old_pos; // the position of the particle in the previous time step, used as part of the verlet numerical integration scheme
vec4 acceleration; // a vector representing the current acceleration of the particle
int id;
int flattened_id;
int movable; // can the particle move or not ? used to pin parts of the cloth
float mass; // the mass of the particle (is always 1 in this example)
The GLSL side definition. What I am unsure about is if the padding elements need to be included in the glsl structure or not. It still doesn't update the ID's.
struct Particle
{
vec4 position;
vec2 uv;
vec4 accumulated_normal;
vec4 old_pos; // the position of the particle in the previous time step, used as part of the verlet numerical integration scheme
vec4 acceleration; // a vector representing the current acceleration of the particle
int id;
int flattened_id;
int movable; // can the particle move or not ? used to pin parts of the cloth
float mass; // the mass of the particle (is always 1 in this example)
};
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>
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();
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:
In my application I add two lights. One at (0,0,2) and the second one at (2,0,0). Here's what I get (the x,y,z axes are represented respectively by the red, green & blue lines):
Notice how only the first light is working and the second is not. I made my application core-profile compliant to inspect the buffers with various tools like RenderDoc and NSight and both show me that the second light's data is present in the buffer (picture taken while running Nsight):
The positions seem to be correctly transfered to the gpu memory buffer. Here's the implementation of my fragment shader that uses a SSBO to handle multiple lights in my application:
#version 430
struct Light {
vec3 position;
vec3 color;
float intensity;
float attenuation;
float radius;
};
layout (std140, binding = 0) uniform CameraInfo {
mat4 ProjectionView;
vec3 eye;
};
layout (std430, binding = 1) readonly buffer LightsData {
Light lights[];
};
uniform vec3 ambient_light_color;
uniform float ambient_light_intensity;
in vec3 ex_FragPos;
in vec4 ex_Color;
in vec3 ex_Normal;
out vec4 out_Color;
void main(void)
{
// Basic ambient light
vec3 ambient_light = ambient_light_color * ambient_light_intensity;
int i;
vec3 diffuse = vec3(0.0,0.0,0.0);
vec3 specular = vec3(0.0,0.0,0.0);
for (i = 0; i < lights.length(); ++i) {
Light wLight = lights[i];
// Basic diffuse light
vec3 norm = normalize(ex_Normal); // in this project the normals are all normalized anyway...
vec3 lightDir = normalize(wLight.position - ex_FragPos);
float diff = max(dot(norm, lightDir), 0.0);
diffuse += diff * wLight.color;
// Basic specular light
vec3 viewDir = normalize(eye - ex_FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
specular += wLight.intensity * spec * wLight.color;
}
out_Color = ex_Color * vec4(specular + diffuse + ambient_light,1.0);
}
Note that I've read the section 7.6.2.2 of the OpenGL 4.5 spec and that, if I understood correctly, my alignment should follow the size of the biggest member of my struct, which is a vec3 and my struct size is 36 bytes so everything should be fine here. I also tried different std version (e.g. std140) and adding some padding, but nothing fixes the issue with the second light. In my C++ code, I have those definitions to add the lights in my application:
light_module.h/.cc:
struct Light {
glm::f32vec3 position;
glm::f32vec3 color;
float intensity;
float attenuation;
float radius;
};
...
constexpr GLuint LIGHTS_SSBO_BINDING_POINT = 1U;
std::vector<Light> _Lights;
...
void AddLight(const Light &light) {
// Add to _Lights
_Lights.push_back(light);
UpdateSSBOBlockData(
LIGHTS_SSBO_BINDING_POINT, _Lights.size()* sizeof(Light),
static_cast<void*>(_Lights.data()), GL_DYNAMIC_DRAW);
}
shader_module.h/.cc:
using SSBOCapacity = GLuint;
using BindingPoint = GLuint;
using ID = GLuint;
std::map<BindingPoint, std::pair<ID, SSBOCapacity> > SSBO_list;
...
void UpdateSSBOBlockData(GLuint a_unBindingPoint,
GLuint a_unSSBOSize, void* a_pData, GLenum a_eUsage) {
auto SSBO = SSBO_list.find(a_unBindingPoint);
if (SSBO != SSBO_list.end()) {
GLuint unSSBOID = SSBO->second.first;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, unSSBOID);
glBufferData(GL_SHADER_STORAGE_BUFFER, a_unSSBOSize, a_pData, a_eUsage);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); //unbind
}
else
// error handling...
}
Basically, I'm trying to update/reallocate the SSBO size with glBufferData each time a light is added in my app.
Now, since I'm having issues processing the second light data, I changed my fragment shader code to only execute the second light in my SSBO array by forcing i = 1 and looping until i < 2, but I get the following errors:
(50) : error C1068: ... or possible array index out of bounds
(50) : error C5025: lvalue in field access too complex
(56) : error C1068: ... or possible array index out of bounds
(56) : error C5025: lvalue in field access too complex
Lines 50 and 56 refer to diffuse += diff * wLight.color; and specular += wLight.intensity * spec * wLight.color; respectively. Is there really an out of bounds access even if I add my lights before the first draw call? Why is the shader compiling correctly when I'm using lights.length() instead of 2?
Finally, I've added a simple if (i == 1) in my for-loop to see if lights.length() is equal to 2, but it doesn't go in it. Yet the initial size of my buffer is 0 and then I add a light that sets the buffer size to 36 bytes and we can see that the first light works fine. Why is the update/reallocate not working the second time?
So what I did was to add some padding at the end of the declaration of my struct on the C++ side only. The padding required was float[3] or 12 bytes, which sums up to 48 bytes. I'm still not sure why this is required, since the specifications state (as highlighted in this post)
If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its
members, and rounded up to the base alignment of a vec4. The
individual members of this sub-structure are then assigned offsets by
applying this set of rules recursively, where the base offset of the
first member of the sub-structure is equal to the aligned offset of
the structure. The structure may have padding at the end; the base
offset of the member following the sub-structure is rounded up to the
next multiple of the base alignment of the structure.
[...]
When using the std430 storage layout, shader storage blocks will be
laid out in buffer storage identically to uniform and shader storage
blocks using the std140 layout, except that the base alignment and
stride of arrays of scalars and vectors in rule 4 and of structures in
rule 9 are not rounded up a multiple of the base alignment of a vec4.
My guess is that structures such as vec3 and glm::f32vec3 defined by glm are recursively rounded up to vec4 when using std430 and therefore my struct must follow the alignment of a vec4. If anyone can confirm this, it would be interesting since the linked post above deals with vec4 directly and not vec3.
Picture with both lights working :
EDIT:
After more investigation, it turns out that the last 3 fields of the Light struct (intensity, attenuation and radius) were not usable. I fixed this by changing the position and color from glm::f32vec3 to glm::vec4 instead. More information can be found in a similar post. I also left a single float for padding, because of the alignment mentioned earlier.
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).