I am working on a small particle system using OpenGL.
The problem is that updating the positions in the compute shader do not seem to work.
Here's the code:
Buffers
struct ParticleInfo {
Vec4f position; // w: s coordinate
Vec4f normal; // w: t coordinate
float materialIndex;
Vec3f oldPosition;
};
Init buffers
glGenVertexArrays(1, &mParticleVAO);
glBindVertexArray(mParticleVAO);
glGenBuffers(1, &mParticleVBO);
glBindBuffer(GL_ARRAY_BUFFER, mParticleVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(ParticleInfo) * mNumParticles, particleData.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(ParticleInfo), (void*)NULL);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ParticleInfo), (void*)(NULL + sizeof(Vec4f)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(ParticleInfo), (void*)(NULL + 2*sizeof(Vec4f)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
update buffers with a compute shader
gl->setUniform(mParticleMoveProgram->getUniformLoc("numParticles"), mNumParticles);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, mParticleVBO);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, mAttractorSSBO);
int localSizeX = 64*8;
int groupSizeX = (mNumParticles + localSizeX - 1) / localSizeX;
glDispatchCompute(groupSizeX, 1, 1);
glMemoryBarrier(GL_ALL_BARRIER_BITS);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
shader code
#version 450
layout(local_size_x = 64) in;
struct ParticleInfo {
vec4 position; // modify only the position;
vec4 normal;
float materialIndex;
vec3 oldPosition;
};
struct Attractor {
vec3 position;
float mass;
};
layout(binding = 0, std430) buffer ParticleArray {
ParticleInfo particles[];
};
layout(binding = 1, std430) buffer AttractorArray {
Attractor attractors[];
};
uniform int numParticles;
vec3 verlet(in vec3 a, in vec3 x, in vec3 xOld, in float dt) {
return 2.0 * x - xOld + a * dt*dt;
}
void main() {
const int PARTICLES_PER_THREAD = 8;
int index = int(gl_LocalInvocationIndex)*PARTICLES_PER_THREAD;
if (index >= numParticles) return;
Attractor attr = attractors[0];
const float G = 9.8;
for (int i = 0; i < PARTICLES_PER_THREAD; ++i)
{
particles[i+index].position = vec4(0.0);
particles[i+index].normal = vec4(0.0);
particles[i+index].oldPosition = vec3(0.0);
}
}
It is as #derhass commented. The memory structure didn't match. There seemed to be also a problem with proper indexing per thread. I fixed it by setting the index per thread as:
index = PARTICLES_PER_THREAD * int(gl_WorkGroupSize.x * gl_WorkGroupID.x + gl_LocalInvocationID.x);
Thanks for the help.
Related
I'm currently drawing a sphere but I would like to have options to have 3D coloring and textures on the sphere. I'm aware that my code right know allows me to insert textures because I have u, v coordinates but I'm not sure how to do this.
Sphere Calculations
const float pi = 3.1414927f;
GLint layers = 100;
GLint circumferenceTiles = 100;
std::vector<float> va;
std::vector<int> ia;
// create the vertex attributes
va.reserve((layers + 1)* (circumferenceTiles + 1) * 5); // 5 floats: x, y, z, u, v
for (int il = 0; il <= layers; ++il)
{
float layer_rel = (float)il / (float)layers;
float layer_ang = (1.0f - 2.0f * layer_rel) * pi / 2.0f;
float layer_sin = std::sin(layer_ang);
float layer_cos = std::cos(layer_ang);
for (int ic = 0; ic <= circumferenceTiles; ic++)
{
float circum_rel = (float)ic / (float)circumferenceTiles;
float cricum_ang = circum_rel * 2.0f * pi - pi;
float circum_sin = std::sin(cricum_ang);
float circum_cos = std::cos(cricum_ang);
va.push_back(layer_cos * circum_cos); // x
va.push_back(layer_cos * circum_sin); // y
va.push_back(layer_sin); // z
va.push_back(circum_rel); // u
va.push_back(1.0f - layer_rel); // v
}
}
// create the face indices
ia.reserve(layers * circumferenceTiles * 6);
for (int il = 0; il < layers; ++il)
{
for (int ic = 0; ic < circumferenceTiles; ic++)
{
int i0 = il * (circumferenceTiles + 1) + ic;
int i1 = i0 + 1;
int i3 = i0 + circumferenceTiles + 1;
int i2 = i3 + 1;
int faces[]{ i0, i1, i2, i0, i2, i3 };
ia.insert(ia.end(), faces + (il == 0 ? 3 : 0), faces + (il == layers - 1 ? 3 : 6));
}
}
Binding
// Vertex Array
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Vertex Buffer
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, va.size() * sizeof(*va.data()), va.data(), GL_STATIC_DRAW);
// Index Buffer
GLuint ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ia.size() * sizeof(*ia.data()), ia.data(), GL_STATIC_DRAW);
GLuint v_attr_inx = 0;
glVertexAttribPointer(v_attr_inx, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
glEnableVertexAttribArray(v_attr_inx);
GLuint t_attr_inx = 1;
glVertexAttribPointer(t_attr_inx, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)(3 * sizeof(float)));
glEnableVertexAttribArray(t_attr_inx);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
Drawing
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, (GLsizei)ia.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
Fragment Shader
#version 330 core
// Interpolated values from the vertex shaders
in vec3 fragmentColor;
// Ouput data
layout(location = 0) out vec4 color;
uniform vec4 u_Color;
void main(){
// Output color = color specified in the vertex shader,
// interpolated between all 3 surrounding vertices
color = u_Color;
}
Vertex Shader
#version 330 core
// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec3 texcoord;
// Output data ; will be interpolated for each fragment.
out vec3 fragmentColor;
// Values that stay constant for the whole mesh.
uniform mat4 MVP;
void main(){
// The color of each vertex will be interpolated
// to produce the color of each fragment
fragmentColor = texcoord;
// Output position of the vertex, in clip space : MVP * position
gl_Position = MVP * vec4(vertexPosition_modelspace,1);
}
Currently when I write vec3 texcoord I can change the colors but cannot show the the sphere in wire mode. But when I change it to vec2 I'm able to change it to wire mode but unable to change the colors.
I've been trying OpenGL recently and are stuck again in an issue.
If in my program I set colors via uniforms, I can draw multiple vertex arrays with any color of my choice. But passing of two buffers to be generated for an vertex array object results in weird coloration, where 0 is for vertex location and 1 is for color.
My main function :
int main(){
Window window(960,540);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Reader read1("src/shaders/test.vert");
Reader read2("src/shaders/test.frag");
char * r1 = read1.getData();
char * r2 = read2.getData();
GLfloat vert[] = {
0, 0, 0,
0, 3, 0,
8, 3, 0,
8, 0, 0
};
GLushort indices[] = {
0,1,2,
2,3,0
};
GLfloat colors[] = {
1, 0, 1, 1,
1, 0, 1, 1,
1, 0, 1, 1,
1, 0, 1, 1,
};
VertexArray vao;
Buffer* vbo = new Buffer(vert, 4 * 4, 3);
vao.addBuffer(vbo, 0);
vao.addBuffer(new Buffer(colors,4 * 4 , 4), 1);
indexBuffer ibo(indices, 6);
Shader shader(r1, r2);
shader.enable();
shader.setUniformMat4("pr_matrix", mat4::orthographic(0.0f, 16.0f, 0.0f, 9.0f, -1.0f, 1.0f));
shader.setUniformMat4("ml_matrix", mat4::translation(vec3(4, 3, 0)));
shader.setUniform2f("light_pos", vec2(8.0f, 4.5f));
shader.setUniform4f("colour", vec4(0.2, 0.3, 0.8, 1));
while (!window.closed()){
window.clear();
double x, y;
x = window.getX();
y = window.getY();
shader.setUniform2f("light_pos", vec2((float)((x)*16.0f / 960.0f), (float)(9 - 9 * (y) / 540.0f)));
vao.bind();
ibo.bind();
shader.setUniform4f("colour", vec4(0.2, 0.3, 0.8, 1));
shader.setUniformMat4("ml_matrix", mat4::translation(vec3(4, 3, 0)));
glDrawElements(GL_TRIANGLES, ibo.getCount(), GL_UNSIGNED_SHORT, 0);
ibo.unbind();
vao.unbind();
window.update();
}
return 0;
}
My vertex shaders :
#version 410 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec4 color;
uniform mat4 pr_matrix ;
uniform mat4 vw_matrix = mat4(1.0f);
uniform mat4 ml_matrix = mat4(1.0f);
out DATA{
vec4 position;
vec4 color;
} vs_out;
out vec4 pos;
void main(){
gl_Position = pr_matrix * vw_matrix * ml_matrix * vec4(position,1) ;
vs_out.position = ml_matrix * vec4(position,1);
vs_out.color = color;
}
My fragment shaders :
#version 410 core
layout(location = 0) out vec4 color ;
uniform vec4 colour;
uniform vec2 light_pos;
in DATA{
vec4 position;
vec4 color;
} fs_in;
void main(){
float intensity = 1.0f / length(fs_in.position.xy - light_pos);
//color = fs_in.color * intensity;
color = fs_in.color * intensity;
}
My buffer class in case its needed to be corrected:
Buffer::Buffer(GLfloat *data, GLsizei count, GLuint compCountExt) : compCount (compCountExt) {
glGenBuffers(1, &bufferId);
glBindBuffer(GL_ARRAY_BUFFER,bufferId);
glBufferData(GL_ARRAY_BUFFER, count* sizeof(GLfloat), data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void Buffer::bind() const {
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
}
void Buffer::unbind() const {
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
EDIT:
code of the vertexArray Class:
VertexArray::VertexArray(){
glGenVertexArrays(1,&arrayID);
}
void VertexArray::bind() const{
glBindVertexArray(arrayID);
}
void VertexArray::unbind() const{
glBindVertexArray(0);
}
VertexArray::~VertexArray(){
}
void VertexArray::addBuffer(Buffer* buffer, GLuint index){
bind();
glBindBuffer(GL_ARRAY_BUFFER, arrayID);
glEnableVertexAttribArray(index);
glVertexAttribPointer(index, buffer->getComCount(), GL_FLOAT, GL_FALSE, 0, 0);
buffer->unbind();
unbind();
}
there are calls to vertex attrib pointer in this class.
glVertexAttribPointer refers to the currently bound array buffer. This means you have to bind the array buffer befor you use glVertexAttribPointer:
void VertexArray::addBuffer(Buffer* buffer, GLuint index){
bind();
// glBindBuffer(GL_ARRAY_BUFFER, arrayID); <---- skip
buffer->bind(); // <---- bind the array buffer
glEnableVertexAttribArray(index);
glVertexAttribPointer(index, buffer->getComCount(), GL_FLOAT, GL_FALSE, 0, 0);
buffer->unbind();
unbind();
}
See OpenGL 4.6 Specification - 10.3.9 Vertex Arrays in Buffer Objects:
A buffer object binding point is added to the client state associated with each
vertex array index. The commands that specify the locations and organizations of vertex arrays copy the buffer object name that is bound to ARRAY_BUFFER to
the binding point corresponding to the vertex array index being specified. For example, the VertexAttribPointer command copies the value of ARRAY_BUFFER_BINDING.
For everyone unfamiliar, OpenGL instanced drawing is where many objects are drawn with one shader call, so glDrawArrays is only called once for a thousand objects on the screen instead of once for every object.
Now the question is: how do I implement instanced rendering in OpenGL 3 for objects which have constantly changing vertices? Creating an array or specifying a position on the vertex shader dedicated specifically to where the objects are won't work, as I'm dealing with a constantly changing vector of objects which shift coordinates in different velocities every frame.
The header for the object class I'm working with, and the vertex shader I have, are described below for reference.
//CLASS
class Laser {
public:
GLfloat x, y, xVelocity, yVelocity;
GLuint texture;
GLfloat angle;
GLfloat velocity;
GLfloat width, height;
GLfloat drawWidth = 16;
GLfloat drawHeight = 16;
GLfloat damage;
GLint actsToDissapear = -1;
GLint actsExisting = 0;
GLboolean expired = false;
GLboolean isRotated = false;
GLboolean variableColor = false;
glm::vec3 color;
std::string type = "Laser";
Laser(GLfloat damage, GLfloat width, GLfloat height, GLuint texture, GLfloat x, GLfloat y, GLfloat xVelocity, GLfloat yVelocity, GLfloat drawWidth, GLfloat drawHeight, GLfloat actsToDissapear, GLboolean isRotated, GLfloat angle, GLboolean variableColor, glm::vec3 color);
virtual void draw(SpriteRenderer* s);
virtual void move(Rachel* player);
};
//VERTEX SHADER
#version 330 core
layout (location = 0) in vec4 vertex;
uniform mat4 model;
uniform mat4 projection;
out vec2 TexCoords;
void main() {
TexCoords = vec2(vertex.z, vertex.w);
gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0);
}
The concept you look for is attribute divisor. See glVertexAttribDivisor.
In a few words: you change your model matrix from uniform to an instanced attribute that's read from a buffer. Each frame you update that buffer with the new positions of each instance. One thing to consider when implementing this is to use (vec3 offset, quat4 orientation) representation for the model matrix in order to reduce the number of consumed attributes by half. Also, depending on the exact problem you have at hand, you can update that buffer directly on the GPU with compute shaders.
Heres a code example of what I think you're looking for. I used instanced rendering for my particle system, it supports textures, colors and movement. Works both on android opengl es and windows opengl. This code requires some work to run, but it should be fairly easy to get going.
#include "ParticleSystem.h"
#include "Engine.h"
#include "Transform.h"
#include "Shader.h"
#include "Texture.h"
#include "Mesh.h"
#include "ShaderHandler.h"
ParticleSystem::ParticleSystem()
{
}
ParticleSystem::~ParticleSystem()
{
shader = nullptr;
texture = nullptr;
glDeleteVertexArrays(1, &vertexArrayObject);
}
void ParticleSystem::init(Engine * engine, float size, Texture * texture, float maxVelocity, bool gravity)
{
this->maxVelocity = maxVelocity;
this->gravity = gravity;
this->size = size;
vertex =
{
-size, -size, 0.0f,
-size, size, 0.0f,
size, size, 0.0f,
size, -size, 0.0f
};
indices =
{
1, 0, 2, 3
};
this->shader = engine->getShaderHandler()->loadShader("res/shaders/texturedInstancedShader");
this->texture = texture;
glGenVertexArrays(1, &this->vertexArrayObject);
glBindVertexArray(this->vertexArrayObject);
glGenBuffers(ParticleSystem::NUM_BUFFERS, this->vertexArrayBuffer);
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->VERTEX_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * this->vertex.size(), &this->vertex[0], GL_STATIC_DRAW); //send model to GPU
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->TEXTURE_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * this->texCoords.size(), &this->texCoords[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->vertexArrayBuffer[this->INDEX_VB]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * indices.size(), &this->indices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->POSITION_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * this->positions.size(), NULL, GL_STREAM_DRAW); //NULL (empty) buffer
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->COLOR_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec4) * this->colors.size(), NULL, GL_STREAM_DRAW); //NULL (empty) buffer
glBindVertexArray(0);
}
void ParticleSystem::createPoint(float pps, float deltaTime, glm::vec3 position, float maxLife, glm::vec4 color, glm::vec3 velocity)
{
Particle particle;
float amountPerSecond = pps * deltaTime;
for (float i = 0; i < amountPerSecond; i++)
{
particle.life = (rand() % static_cast<int>(maxLife * 100)) / 100.f;
particle.velocity =
{
((rand() % 200 / 100.f) - 1.f) * velocity.x,
((rand() % 200 / 100.f) - 1.f) * velocity.y,
((rand() % 200 / 100.f) - 1.f) * velocity.z
};
particles.emplace_back(particle);
positions.emplace_back(position);
colors.emplace_back(color);
}
}
void ParticleSystem::draw(glm::mat4 view)
{
if (particles.size() > 0)
{
Transform transform;
this->shader->bind();
this->shader->loadTransform(transform, view);
this->shader->loadInt(U_TEXTURE0, 0);
this->texture->bind(0);
glBindVertexArray(vertexArrayObject);
glVertexAttribDivisor(0, 0);
glVertexAttribDivisor(1, 1);
glVertexAttribDivisor(2, 1);
glVertexAttribDivisor(3, 0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->VERTEX_VB]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->POSITION_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * positions.size(), &positions[0], GL_STREAM_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->COLOR_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec4) * colors.size(), &colors[0], GL_STREAM_DRAW);
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, this->vertexArrayBuffer[this->TEXTURE_VB]);
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
glDrawElementsInstanced(GL_TRIANGLE_STRIP, indices.size(), GL_UNSIGNED_INT, 0, positions.size());
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindVertexArray(0);
}
}
void ParticleSystem::update(float deltaTime)
{
for (std::size_t i = 0; i < particles.size(); i++)
{
particles[i].life -= (1.f * deltaTime); //decrease life with 1 per second
if (particles[i].life <= 0.f) //dead
{
particles.erase(particles.begin() + i);
colors.erase(colors.begin() + i);
positions.erase(positions.begin() + i);
continue;
}
if (this->gravity == true)
{
if (particles[i].velocity.y > -maxVelocity)
{
particles[i].velocity.y -= maxVelocity * deltaTime; //1 second to reach maximum velocity
}
else
{
particles[i].velocity.y = -maxVelocity;
}
}
positions[i] += (particles[i].velocity * deltaTime);
}
}
Heres the shader:
vertex shader:
#version 330 core
layout(location = 0) in vec3 vertex;
layout(location = 1) in vec3 positions;
layout(location = 2) in vec4 colors;
layout(location = 3) in vec2 texCoords;
out vec2 texCoord;
out vec4 color;
uniform mat4 transform;
void main()
{
color = colors;
texCoord = texCoords;
gl_Position = transform * vec4(vertex + positions, 1.0);
}
fragment shader:
#version 330 core
in vec4 color;
in vec2 texCoord;
out vec4 colors;
uniform sampler2D texture0;
void main()
{
vec4 texel = texture2D(texture0, texCoord);
if (texel.a <= 0.5)
{
discard;
}
colors = color * texel;
}
I'm trying to render lots of stuff with OpenGL 3.3 Am i missing some tricks to make this faster?
Does it matter if I use glBufferData or glBufferSubData?
I have coded OpenGL for 5 days now, so I know that there are lots of unkown uknowns to me. And those are what i'm looking for, can you point me to any ways of making this even faster?
I think i'm using what's called "Instanced Rendering". All my stuff is rendered via a single glDrawElementsInstancedBaseVertex call.
Did I miss any relevant code? There's so much of it that I can't really paste it all here.
I'v gotten as far as 20000 objects with 24 vertices using the following code:
Called once per mesh at start, not during frames.
void Mesh::initMesh(IndexedModel const & p_model)
{
d->drawCount = p_model.indices.size();
glGenVertexArrays(1, &(d->vertexArrayObject));
glBindVertexArray(d->vertexArrayObject);
glGenBuffers(eNumBuffers, d->vertexArrayBuffers);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[ePosition_Vb]);
glBufferData(GL_ARRAY_BUFFER, sizeof(p_model.positions[0]) * p_model.positions.size(), p_model.positions.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eTexCoord_Vb]);
glBufferData(GL_ARRAY_BUFFER, sizeof(p_model.texCoords[0]) * p_model.texCoords.size(), p_model.texCoords.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eNormal_Vb]);
glBufferData(GL_ARRAY_BUFFER, sizeof(p_model.normals[0]) * p_model.normals.size(), p_model.normals.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, d->vertexArrayBuffers[eIndex_Vb]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * p_model.indices.size(), p_model.indices.data(), GL_STATIC_DRAW);
GLint mat4_pos0 = 3;
GLint shinyPos = 7;
GLint materialPos = 8;
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eModel_Vb]);
for (unsigned int i = 0; i < 4; i++)
{
glEnableVertexAttribArray(mat4_pos0 + i);
glVertexAttribPointer(mat4_pos0 + i, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4),
(const GLvoid*)(sizeof(GLfloat) * i * 4));
glVertexAttribDivisor(mat4_pos0 + i, 1);
}
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eShiny_Vb]);
glEnableVertexAttribArray(shinyPos);
glVertexAttribPointer(shinyPos, 1, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(shinyPos, 1);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eSpecular_Vb]);
glEnableVertexAttribArray(materialPos);
glVertexAttribPointer(materialPos, 1, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(materialPos, 1);
}
Called once per frame.
void Mesh::draw(std::vector<Object*> const & p_objects, GLuint p_program)
{
std::vector<glm::mat4> models;
std::vector<glm::float32> shinies;
std::vector<glm::vec3> specularColors;
models.reserve(p_objects.size());
shinies.reserve(p_objects.size());
specularColors.reserve(p_objects.size());
for (int index = 0;
index < p_objects.size();
index++)
{
models.push_back(p_objects[index]->getTransform());
shinies.push_back(p_objects[index]->getShininess());
specularColors.push_back(p_objects[index]->getSpecularColor());
}
unsigned int bytesOfModels = models.size() * sizeof(models[0]);
unsigned int bytesOfShinies = shinies.size() * sizeof(shinies[0]);
unsigned int bytesOfSpecularColors = specularColors.size() * sizeof(specularColors[0]);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eModel_Vb]);
glBufferData(GL_ARRAY_BUFFER, bytesOfModels, models.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eShiny_Vb]);
glBufferData(GL_ARRAY_BUFFER, bytesOfShinies, shinies.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, d->vertexArrayBuffers[eSpecular_Vb]);
glBufferData(GL_ARRAY_BUFFER, bytesOfSpecularColors, specularColors.data(), GL_DYNAMIC_DRAW);
// glDrawElementsInstanced(GL_TRIANGLES, d->drawCount, GL_UNSIGNED_SHORT, 0, models.size());
// glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, d->drawCount, models.size());
glDrawElementsInstancedBaseVertex(GL_TRIANGLES,
d->drawCount,
GL_UNSIGNED_INT,
0,
p_objects.size(),
0);
}
Called once per frame
void GenericRenderer::renderObjects(std::vector<Object*> p_objects)
{
if (p_objects.empty())
{
return;
}
m_texture->bind(0);
m_shader->bind();
m_shader->updateCamera(m_camera);
m_shader->updateLightSource(*m_light);
m_shader->updateObjects(p_objects);
m_mesh->bind();
for (size_t index = 0;
index < p_objects.size();
index++)
{
p_objects[index]->setOrigin(m_camera);
p_objects[index]->updateTransform();
}
m_mesh->draw(p_objects, m_shader->getProgram());
m_mesh->unbind();
}
Vertex Shader
#version 330
uniform mat4 camera;
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 normal;
layout (location = 3) in mat4 model;
layout (location = 7) in float materialShininess;
layout (location = 8) in vec3 materialSpecularColor;
out vec3 fragVert;
out vec2 fragTexCoord;
out vec3 fragNormal;
out mat4 fragModel;
out float fragMaterialShininess;
out vec3 fragMaterialSpecularColor;
void main()
{
fragModel = model;
fragTexCoord = texCoord;
fragNormal = normal;
fragVert = position;
fragMaterialShininess = materialShininess;
fragMaterialSpecularColor = materialSpecularColor;
gl_Position = camera * model * vec4(position, 1);
}
Fragment Shader
#version 150
uniform vec3 cameraPosition;
uniform float exposure;
uniform float lightDistanceModifier;
uniform sampler2D tex;
uniform struct Light {
vec3 position;
vec3 intensities; //a.k.a the color of the light
float attenuation;
float ambientCoefficient;
} light;
in vec2 fragTexCoord;
in vec3 fragNormal;
in vec3 fragVert;
in mat4 fragModel;
in float fragMaterialShininess;
in vec3 fragMaterialSpecularColor;
out vec4 finalColor;
void main() {
vec3 normal = normalize(transpose(inverse(mat3(fragModel))) * fragNormal);
vec3 surfacePos = vec3(fragModel * vec4(fragVert, 1));
vec4 surfaceColor = texture(tex, fragTexCoord);
vec3 surfaceToLight = normalize(light.position - surfacePos);
vec3 surfaceToCamera = normalize(cameraPosition - surfacePos);
//ambient
vec3 ambient = light.ambientCoefficient * surfaceColor.rgb * light.intensities;
//diffuse
float diffuseCoefficient = max(0.0, dot(normal, surfaceToLight));
vec3 diffuse = diffuseCoefficient * surfaceColor.rgb * light.intensities;
//specular
float specularCoefficient = 0.0;
if(diffuseCoefficient > 0.0)
specularCoefficient = pow(max(0.0, dot(surfaceToCamera, reflect(-surfaceToLight, normal))), fragMaterialShininess);
vec3 specular = specularCoefficient * fragMaterialSpecularColor * light.intensities;
//attenuation
float distanceToLight = length(light.position - surfacePos);
distanceToLight *= lightDistanceModifier;
float attenuation = 1.0 / (1.0 + light.attenuation * pow(distanceToLight, 2));
//linear color (color before gamma correction)
vec3 linearColor = ambient + attenuation*(diffuse + specular);
//final color (after gamma correction)
vec3 gamma = vec3(1.0/2.2);
vec3 mapped = vec3(1.0) - exp(-linearColor * exposure);
mapped = pow(mapped, vec3(1.0 / gamma));
finalColor = vec4(mapped, surfaceColor.a);
}
OpenGL state changes are very expensive. If you are rendering 20000 objects individually per frame then you re most likely CPU bound. Your goal should be to render as many vertices as possible with as few state changes as possible.
If your 20000 objects are all using the same model then your situation is a prime candidate for instanced rendering. Instanced rendering lets you render the same model thousands of times in one draw call. If you couple this with a separate vertex buffer that contains WVP matrices for each model then you can render each of those model instances at a unique location within the world.
Be warned though, instanced rendering isn't some sort of panacea to all your draw call woes. It has it's own unique overhead with constructing a buffer of MVP matrices on the CPU each frame. If the number of instances you're rendering isn't at least in the hundreds you'll likely see worse performance than your current rendering method.
EDIT: You already using instanced rendering, my apologies.
After reading your code more thoroughly you are likely right in your assumption that you're GPU bound. However, it's not currently clear why you are constructing specular and shininess buffers once per frame when these attributes tend to remain constant for a material.
I'm having difficulty in understanding why gl_VertexID is not properly incrementing for each new vertex in the code below for rendering "debug text". Hints/tips?
(Original code is referenced at the bottom of this post)
Hereafter is the vertex shader:
#version 430 core
layout( location = 0 ) in int Character;
out int vCharacter;
out int vPosition;
void main()
{
vPosition = gl_VertexID;
vCharacter = Character;
gl_Position = vec4(0, 0, 0, 1);
}
The geometry shader:
#version 430 core
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
in int vCharacter[1];
in int vPosition[1];
out vec2 gTexCoord;
uniform sampler2D Sampler;
uniform vec2 CellSize;
uniform vec2 CellOffset;
uniform vec2 RenderSize;
uniform vec2 RenderOrigin;
void main()
{
// Determine the final quad's position and size:
float x = RenderOrigin.x + float(vPosition[0]) * RenderSize.x * 2.0f;
float y = RenderOrigin.y;
vec4 P = vec4(x, y, 0, 1);
vec4 U = vec4(1, 0, 0, 0) * RenderSize.x;
vec4 V = vec4(0, 1, 0, 0) * RenderSize.y;
// Determine the texture coordinates:
int letter = vCharacter[0];
letter = clamp(letter - 32, 0, 96);
int row = letter / 16 + 1;
int col = letter % 16;
float S0 = CellOffset.x + CellSize.x * col;
float T0 = CellOffset.y + 1 - CellSize.y * row;
float S1 = S0 + CellSize.x - CellOffset.x;
float T1 = T0 + CellSize.y;
// Output the quad's vertices:
gTexCoord = vec2(S0, T1); gl_Position = P - U - V; EmitVertex();
gTexCoord = vec2(S1, T1); gl_Position = P + U - V; EmitVertex();
gTexCoord = vec2(S0, T0); gl_Position = P - U + V; EmitVertex();
gTexCoord = vec2(S1, T0); gl_Position = P + U + V; EmitVertex();
EndPrimitive();
}
The draw call and other relevant code:
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GLuint attribLocation = glGetAttribLocation(m_ProgramTextPrinter, "Character");
glVertexAttribIPointer(attribLocation, 1, GL_UNSIGNED_BYTE, 1, text.data()->c_str());
glEnableVertexAttribArray(attribLocation);
glDrawArrays(GL_POINTS, 0, text.data()->size());
Basically this code will be used for some text rendering. When I use this code, I see that my letters are put on top of each other. When I modify
glVertexAttribIPointer(attribLocation, 1, GL_UNSIGNED_BYTE, 1, text.data()->c_str());
into
glVertexAttribIPointer(attribLocation, 1, GL_UNSIGNED_BYTE, 2, text.data()->c_str());
I notice there is a shift in the x-direction as expected from the Geometry shader, nevertheless the letters are still on top of each other.
I'm using an NVIDIA Geforce GT 630M, driver version: 320.18 and an OpenGL 4.3 context.
Reference to the original author's code
I got the code working by using VBOs as Bartek hinted at: I basically replaced
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GLuint attribLocation = glGetAttribLocation(m_ProgramTextPrinter, "Character");
glVertexAttribIPointer(attribLocation, 1, GL_UNSIGNED_BYTE, 1, text.data()->c_str());
glEnableVertexAttribArray(attribLocation);
glDrawArrays(GL_POINTS, 0, text.data()->size());
with
GLuint vaoID, bufferID;
glGenVertexArrays(1, &vaoID);
glBindVertexArray(vaoID);
glGenBuffers(1, &bufferID);
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glBufferData(GL_ARRAY_BUFFER, text.data()->size() * sizeof(GL_UNSIGNED_BYTE), text.data()->data(), GL_DYNAMIC_DRAW);
GLuint attribLocation = glGetAttribLocation(m_ProgramTextPrinter, "Character");
glVertexAttribIPointer(attribLocation, 1, GL_UNSIGNED_BYTE, 0, 0);
glEnableVertexAttribArray(attribLocation);
glDrawArrays(GL_POINTS, 0, text.data()->size());
glDeleteVertexArrays(1, &vaoID);
glDeleteBuffers(1, &bufferID);