Right now I am working at creating a heightmap-based terrain grid, similar to the Lighthouse 3D Terrain Tutorial, except that I am using VBO's and EBO's. All has been going well until I have tried to texture my grid. Currently I am applying one texture that spans the entire grid. Using Window 7's sample Jellyfish picture, I end up with this:
For those familiar with the picture, you can see that it is being repeated several times throughout the terrain grid. This led me to believe that my UV coordinates were being corrupted. However, if I use a function that always returns 0 to determine the height at each grid vertex, I end up with this:
Now I am thoroughly confused, and I can't seem to find any other resources to help me.
My code is as follows:
generate_terrain() function:
QImage terrainImage;
terrainImage.load(imagePath.data());
int width = terrainImage.width();
int height = terrainImage.height();
float uStep = 1.0f / width;
float vStep = 1.0f / height;
grid = new std::vector<float>;
indices = new std::vector<unsigned short>;
for (int i = 0; i <= height-1; ++i) {
for (int j = 0; j <= width-1; ++j) {
QVector3D vertex1{j, heightFunction(terrainImage.pixel(j, i)), i};
QVector3D vertex2{j, heightFunction(terrainImage.pixel(j, i+1)), i+1};
QVector3D vertex3{j+1, heightFunction(terrainImage.pixel(j+1, i+1)), i+1};
QVector3D edge1 = vertex2 - vertex1;
QVector3D edge2 = vertex3 - vertex1;
QVector3D normal = QVector3D::crossProduct(edge1, edge2);
normal.normalize();
grid->push_back(vertex1.x());
grid->push_back(vertex1.y());
grid->push_back(vertex1.z());
grid->push_back(normal.x());
grid->push_back(normal.y());
grid->push_back(normal.z());
grid->push_back(j * uStep);
grid->push_back(i * vStep);
}
}
for (int i = 0; i < height-1; ++i) {
for (int j = 0; j < width-1; ++j) {
indices->push_back(i * width + j);
indices->push_back((i+1) * width + j);
indices->push_back((i+1) * width + (j+1));
indices->push_back((i+1) * width + (j+1));
indices->push_back(i * width + (j+1));
indices->push_back(i * width + j);
}
}
vertices = grid->size()/8;
indexCount = indices->size();
Texture Loading:
f->glGenTextures(1, &textureId);
f->glBindTexture(GL_TEXTURE_2D, textureId);
QImage texture;
texture.load(texturePath.data());
QImage glTexture = QGLWidget::convertToGLFormat(texture);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glTexture.width(), glTexture.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, glTexture.bits());
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
Drawing:
f->glActiveTexture(GL_TEXTURE0);
f->glBindTexture(GL_TEXTURE_2D, textureId);
program->setUniformValue(textureUniform.data(), 0);
f->glBindBuffer(GL_ARRAY_BUFFER, vbo.bufferId());
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), 0);
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *) (sizeof(float) * 3));
f->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *) (sizeof(float) * 6));
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo.bufferId());
f->glEnableVertexAttribArray(0);
f->glEnableVertexAttribArray(1);
f->glEnableVertexAttribArray(2);
f->glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0);
f->glDisableVertexAttribArray(2);
f->glDisableVertexAttribArray(1);
f->glDisableVertexAttribArray(0);
Shaders:
Vertex:
attribute vec3 vertex_modelspace;
attribute vec3 normal_in;
attribute vec2 uv_in;
uniform mat4 mvp;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform vec3 lightPosition;
varying vec2 uv;
varying vec3 normal;
varying vec3 fragPos;
void main(void)
{
gl_Position = projection * view * model * vec4(vertex_modelspace, 1);
uv = uv_in;
normal = normal_in;
fragPos = vec3(model * vec4(vertex_modelspace, 1));
}
Fragment:
varying vec2 uv;
varying vec3 normal;
varying vec3 fragPos;
uniform sampler2D texture;
uniform vec3 lightPosition;
void main(void)
{
vec3 lightColor = vec3(0.6, 0.6, 0.6);
float ambientStrength = 0.2;
vec3 ambient = ambientStrength * lightColor;
vec3 norm = normalize(normal);
vec3 lightDirection = normalize(lightPosition - fragPos);
float diff = max(dot(norm, lightDirection), 0.0);
vec3 diffuse = diff * lightColor;
vec3 color = texture2D(texture, uv).rgb;
vec3 result = (ambient + diffuse) * color;
gl_FragColor = vec4(result, 1.0);
}
I am completely stuck, so any suggestions are welcome :)
P.S. I am also working at trying to get my lighting to look better, so any tips on that would be welcome as well.
Your code is assuming values for the attribute locations, which are the values used as the first argument to glVertexAttribPointer() and glEnableVertexAttribArray(). For example here:
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), 0);
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *) (sizeof(float) * 3));
f->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *) (sizeof(float) * 6));
you're assuming that the positions have location 0, the normals location 1, and the texture coordinates location 2.
This is not guaranteed by anything you have currently in your code. The order of the attribute declarations in the GLSL code does not define the location assignment. For example from the OpenGL 3.2 spec:
When a program is linked, any active attributes without a binding specified through BindAttribLocation will be automatically be bound to vertex attributes by the GL.
Note that this does not specify how the automatic assignment of the locations is done. This means that it's implementation dependent.
To fix this, there are two approaches:
You can call glBindAttribLocation() for all your attributes before the shader program is linked.
You can query the automatically assigned locations by calling glGetAttribLocation() after the program is linked.
In newer OpenGL versions (GLSL 3.30 and later, which is the version matching OpenGL 3.3), you also have the option to specify the location directly in the GLSL code, using qualifiers of the form layout(location=...).
None of these options has any major advantages over the others. Just use the one that works best based on your preferences and software architecture.
Related
What I want to attrive is to render many small quads with this opengl function "glDrawArraysInstanced", the space between them is the same. For example, please refer to the follwing image:
The code is as follow:
void OpenGLShowVideo::displayBySmallMatrix()
{
// Now use QOpenGLExtraFunctions instead of QOpenGLFunctions as we want to
// do more than what GL(ES) 2.0 offers.
QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
f->glClearColor(9.f/255.0f, 14.f/255.0f, 15.f/255.0f, 1);
glClear(GL_COLOR_BUFFER_BIT);
f->glViewport(0, 0, this->width(), this->height());
m_displayByMatrixProgram->bind();
f->glActiveTexture(GL_TEXTURE0 + m_acRenderToScreenTexUnit);
f->glBindTexture(GL_TEXTURE_2D, m_renderWithMaskFbo->texture());
if (m_uniformsDirty) {
m_uniformsDirty = false;
m_displayByMatrixProgram->setUniformValue(m_samplerLoc, m_acRenderToScreenTexUnit);
m_proj.setToIdentity();
m_proj.perspective(INIT_VERTICAL_ANGLE, float(this->width()) / float(this->height()), m_fNearPlane, m_fFarPlane);
m_displayByMatrixProgram->setUniformValue(m_projMatrixLoc, m_proj);
QMatrix4x4 camera;
camera.lookAt(m_eye, m_eye + m_target, QVector3D(0, 1, 0));
m_displayByMatrixProgram->setUniformValue(m_camMatrixLoc, camera);
m_world.setToIdentity();
float fOffsetZ = m_fVerticalAngle / INIT_VERTICAL_ANGLE;
m_world.translate(m_fMatrixOffsetX, m_fMatrixOffsetY, fOffsetZ);
m_proj.scale(MATRIX_INIT_SCALE_X, MATRIX_INIT_SCALE_Y, 1.0f);
m_world.rotate(180, 1, 0, 0);
QMatrix4x4 wm = m_world;
m_displayByMatrixProgram->setUniformValue(m_worldMatrixLoc, wm);
QMatrix4x4 mm;
mm.setToIdentity();
m_displayByMatrixProgram->setUniformValue(m_myMatrixLoc, mm);
m_displayByMatrixProgram->setUniformValue(m_lightPosLoc, QVector3D(0, 0, 70));
QSize tmpSize = QSize(m_viewPortWidth, m_viewPortHeight);
m_displayByMatrixProgram->setUniformValue(m_resolutionLoc, tmpSize);
int whRatioVal = m_viewPortWidth / m_viewPortHeight;
m_displayByMatrixProgram->setUniformValue(m_whRatioLoc, whRatioVal);
}
m_geometries->bindBufferForArraysInstancedDraw();
f->glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_viewPortWidth * m_viewPortHeight);
}
And the vertex shader code is as follow:
#version 330
layout(location = 0) in vec4 vertex;
out vec3 color;
uniform mat4 mvp_matrix;
uniform mat4 projMatrix;
uniform mat4 camMatrix;
uniform mat4 worldMatrix;
uniform mat4 myMatrix;
uniform vec2 viewResolution;
uniform int whRatio;
uniform sampler2D sampler;
void main() {
int posX = gl_InstanceID % int(viewResolution.x);
int posY = gl_InstanceID / int(viewResolution.y);
if( posY % whRatio < whRatio) {
posY = gl_InstanceID / int(viewResolution.x);
}
ivec2 pos = ivec2(posX, posY);
vec2 t = vec2( pos.x * 3.0, pos.y * 3.0 );
mat4 wm = mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t.x, t.y, 1, 1) * worldMatrix;
color = texelFetch(sampler,pos,0).rgb;
gl_Position = projMatrix * camMatrix * wm * vertex;
}
And the fragment shader is as follow:
#version 330 core
in vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0);
}
However, when I move the camera far from the screen (by changing the [camera.lookAt (m_eye, m_eye + m_target, QVector3D (0, 1, 0);] "m_eye" parameter value), I got sth like this:
The space between quads is different, and the size of the quad is also different. But when I move the camera closer to the screen, it looks much better.
I think what you're seeing there is the result of rounding the coordinates to the nearest integer pixel coordinate.
To get something that looks more even, you want to use some form of anti-aliasing. The options that spring to mind are:
Enable some sort of full screen anti-aliasing like MSAA. This is simple to enable, but can have a significant performance cost.
Put your pattern in a texture, and tile that texture over a single quad. Texture filtering and mip maps should take care of the anti-aliasing for you, and it will probably be faster to render that way as well because you only need a single quad.
I have been trying to do basic Shadow Mapping in my custom Engine using LearnOpenGL as the source. The link for the exact tutorial can be found: here.
I have been debugging this bug for around two weeks, researching the internet, and even trying to wrap my head around this, but all I can say is that the shadow almost never appears, and when it appears it is where the light is Pos is terms of x and z. I tried to do everything exactly like in the tutorial around 10 times, I also tried to check this website for similar questions but for every way I found, it was not my case.
findings
In this Image(1) you can see that the shadow is not visible when the light is on top of it, but it is then visible on this Image (2) when the lightPos.x variable is around -4.5 or 4.5, this is so for the lightPos.z variable too. The shadow when appearing is being drawn where the lightPos is, where in the pictures it is circled by a red line.
I use multiple shaders, one for the light and shadow calculations (ShadowMapping) one for a basic depth mapping (ShadowMapGen)
Here is my ShadowMapping shader:
ShadowMapping Vertex
version 460
in vec3 vertexIn;
in vec3 normalIn;
in vec2 textureIn;
out vec3 FragPos;
out vec3 normalOut;
out vec2 textureOut;
out vec4 FragPosLightSpace;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 lightSpaceMatrix;
void main()
{
textureOut = textureIn;
FragPos = vec3(model * vec4(vertexIn, 1.0));
normalOut = mat3(transpose(inverse(model))) * normalIn;
FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);
gl_Position = projection * view * model * vec4(vertexIn, 1.0);
}
ShadowMapping Frag
out vec4 FragColor;
in vec3 FragPos;
in vec3 normalOut;
in vec2 textureOut;
in vec4 FragPosLightSpace;
uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;
uniform vec3 lightPos;
uniform vec3 viewPos;
float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightdir)
{
// perform perspective divide
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// transform to [0,1] range
projCoords = projCoords * 0.5 + 0.5;
// get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
float closestDepth = texture(shadowMap, projCoords.xy).r;
// get depth of current fragment from light's perspective
float currentDepth = projCoords.z;
// check whether current frag pos is in shadow
float bias = max(0.05 * (1.0 - dot(normalOut, lightdir)), 0.005);
// check whether current frag pos is in shadow
// float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
// // PCF
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
// keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
void main()
{
vec3 color = texture(diffuseTexture, textureOut).rgb;
vec3 normal = normalize(normalOut);
vec3 lightColor = vec3(1.0f);
// ambient
vec3 ambient = 0.30 * color;
// diffuse
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * lightColor;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = 0.0;
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
vec3 specular = spec * lightColor;
// calculate shadow
float shadow = ShadowCalculation(FragPosLightSpace, lightDir);
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;
FragColor = vec4(lighting, 1.0);
}
ShadowMapGen Vertex
Fragment Shader is empty for this shader
version 460
in vec3 vertexIn;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;
void main()
{
gl_Position = model * lightSpaceMatrix * vec4(vertexIn, 1.0);
}
Variable initialisation
lightPos = glm::vec3(-2.0f, 4.0f, -1.0f);
near_plane = 1.0f;
far_plane = 7.5f;
//SAMPLE 2D Uniform binding
TheShader::Instance()->SendUniformData("ShadowMapping_diffuseTexture", 0);
TheShader::Instance()->SendUniformData("ShadowMapping_shadowMap", 1);
Depth Map Framebuffer Generation
This is how I generate my depth map/ shadow map texture in the constructor of my scene:
glGenFramebuffers(1, &depthMapFBO);
//Create depth texture
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); // Height and Width = 1024
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
//Attach depth texture as FBO's depth buffer
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Then in an Update() function that runs in the While loop of the engine I firstly do:
Render Objects from light's perspective
//Light Projection and view Matrix
m_lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
m_lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
//Calculate light matrix and send it.
m_lightSpaceMatrix = m_lightProjection * m_lightView;
TheShader::Instance()->SendUniformData("ShadowMapGen_lightSpaceMatrix", 1, GL_FALSE, m_lightSpaceMatrix);
//Render to Framebuffer depth Map
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
//Set current Shader to ShadowMapGen
m_floor.SetShader("ShadowMapGen");
m_moon.SetShader("ShadowMapGen");
//Send model Matrix to current Shader
m_floor.Draw();
m_moon.Draw();
//Set current Shader back to ShadowMapping
m_moon.SetShader("ShadowMapping");
m_floor.SetShader("ShadowMapping");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Render Objects from Camera's perspective
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//Update Camera and Send the view and projection matrices to the ShadowMapping shader
m_freeCamera->Update();
m_freeCamera->Draw();
//Send Light Pos
TheShader::Instance()->SendUniformData("ShadowMapping_lightPos", lightPos);
//Send LightSpaceMatrix
TheShader::Instance()->SendUniformData("ShadowMapping_lightSpaceMatrix", 1, GL_FALSE, m_lightSpaceMatrix);
//Activate Shadow Mapping texture
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthMap);
//Send model Matrix to ShadowMapping shaders
m_moon.Draw();
m_floor.Draw();
I hope someone will see this, thank you for your time.
I tried to do everything exactly like in the tutorial around 10 times
Well, you seem to have missed at least one obvious thing:
m_lightSpaceMatrix = m_lightProjection * m_lightView;
So far, so good, but in your "ShadowMapGen" vertex shader, you wrote:
gl_Position = model * lightSpaceMatrix * vec4(vertexIn, 1.0);
So you end up with model * projection * view multiplication order, which does not make sense no matter which conventions you adhere to. Since the tutorial uses default GL conventions, you always need projection * view * model * vertex multiplication order, which the tutorial also correctly uses.
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 an issue drawing multiple point lights in my scene. I am working on a simple maze-style game in OpenGL, where the maze is randomly generated. Each "room" in the maze is represented by a Room struct, like so:
struct Room
{
int x, z;
bool already_visited, n, s, e, w;
GLuint vertex_buffer, texture, uv_buffer, normal_buffer;
std::vector<glm::vec3>vertices, normals;
std::vector<glm::vec2>uvs;
glm::vec3 light_pos; //Stores the position of a light in the room
};
Each room has a light in it, the position of this light is stored in light_pos. This light is used in a simple per-vertex shader, like so:
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 uv_coords;
layout(location = 2) in vec3 normal;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform vec3 lightpos;
out vec2 vs_uv;
out vec3 vs_normal;
out vec3 color;
void main()
{
gl_Position = mvpMatrix * vec4(pos,1.0);
vs_normal = normal;
vs_uv = uv_coords;
vec3 lightVector = normalize(lightpos - pos);
float diffuse = clamp(dot(normal,lightVector),0.0,1.0);
color = vec3(diffuse,diffuse,diffuse);
}
My fragment shader looks like this (ignore the "vs_normal", it is unused for now):
in vec2 vs_uv;
in vec3 vs_normal;
in vec3 color;
uniform sampler2D tex;
out vec4 frag_color;
void main()
{
frag_color = vec4(color,1.0) * texture(tex,vs_uv).rgba;
}
And my drawing code looks like this:
mat4 mvMatrix = view_matrix*model_matrix;
mat4 mvpMatrix = projection_matrix * mvMatrix;
glBindVertexArray(vertexBufferObjectID);
glUseProgram(shaderProgram);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
for (int x = 0; x < NUM_ROOMS_X; x++)
{
for (int z = 0; z < NUM_ROOMS_Z; z++)
{
//int x = int(std::round(position.x / ROOM_SIZE_X_MAX));
//int z = int(std::round(position.z / ROOM_SIZE_Z_MAX));
Room rm = room_array[x][z];
glBindBuffer(GL_ARRAY_BUFFER, rm.vertex_buffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glBindBuffer(GL_ARRAY_BUFFER, rm.uv_buffer);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glBindBuffer(GL_ARRAY_BUFFER, rm.normal_buffer);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glUniformMatrix4fv(mvpMatrixID, 1, GL_FALSE, &mvpMatrix[0][0]);
glUniformMatrix4fv(mvMatrixID, 1, GL_FALSE, &mvMatrix[0][0]);
glUniform3fv(light_ID, 3, &rm.light_pos[0]); //Here is where I'm setting the new light position. It looks like this is ignored
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, rm.texture);
glUniform1i(texture_ID, 0);
glDrawArrays(GL_QUADS, 0, rm.vertices.size());
}
}
glUseProgram(0);
glBindVertexArray(0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindTexture(GL_TEXTURE_2D, 0);
However, here is what the result looks like (I've modified my drawing code to draw a box where each light is located, and I've circled the room at position (0,0)):
http://imgur.com/w4uPMD6
As you can see, it looks like only the light at position (0,0) affects any of the rooms on the map, the other lights are simply ignored. I know that the lights are positioned correctly, because the boxes I use to show the positions are correct. I think even though I'm setting the new light_pos, it isn't going through for some reason. Any ideas?
One thing that your are doing, which is not very common, is to pass the light position as a vertex attribute. Optimally, you should pass it to the shader as a uniform variable, just as you do with the matrices. But I doubt that is the problem here.
I believe your problem is that you are doing the light calculations in different spaces. The vertexes of the surfaces that you draw are in object/model space, while I'm guessing, your light is located at a point defined in world space. Try multiplying your light position by the inverse of the model matrix you are applying to the vertexes. I'm not familiar with GLM, but I figure there must be an inverse() function in it:
vec3 light_pos_object_space = inverse(model_matrix) * rm.light_pos;
glVertexAttrib3fv(light_ID, &light_pos_object_space[0]);
Figured out my problem. I was calling this function:
glUniform3fv(light_ID, 3, &rm.light_pos[0]);
When I should have been calling this:
glUniform3fv(light_ID, 1, &rm.light_pos[0]);
And this is result when I invert the tangent vector right after transferring it to vertex shader:
The "shadow" is in the wrong place.
(And it works only when I rotate it through Y axis so the last image seem to present a good parallax mapped cube)
IM SURE IT IS NOT A TANGENT VECTOR OR TEXTURE COORDINATES PROBLEM
Because
I used exactly the same tangent calculation functions and exactly the same cube position, normal and texture coordinate data as in working demo.
After all, I exported arrays with position/texcoord/normal/tangent data into a .txt file and I saw what I exactly expected (and what I expected is the same pos/tex/norm data as in working demo, including calculated tangents which I managed to export from working demo).
The next argument is, I copied my shader code to a working demo and it still works.
Other one is, I tried multiple ways to render this cube.
I tried VBO with glVertexAttribPointer, I tried VBO with saving tangent as other texture coordinate (as in the demo), I tried DisplayList with glVertexAttrib4f. Result is... EXACTLY THE SAME.
Height map is loading correctly, I tried to set it as a diffuse map and it looked OK.
glGetError() gives me No Errors and shader compile logs says so.
It is probably something with camera or init states.
Maybe posting an init code will help.
void CDepthBase::OpenGLSet() {
glEnable( GL_TEXTURE_2D );
glShadeModel( GL_SMOOTH );
glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
glClearDepth( 1.0f );
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDepthFunc( GL_LEQUAL );
glEnable(GL_DEPTH_TEST);
glBlendFunc( GL_ONE, GL_ONE );
GLfloat ratio;
glViewport(0, 0, ResolutionWidth, ResolutionHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f, ResolutionWidth / (float)ResolutionHeight, 0.1f, 900.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (GLEW_OK != glewInit()) {
MBX("Failed to init GLEW.", "Error");
}
if (glewIsSupported("GL_ARB_vertex_buffer_object")) {
VBO_supported = true;
} else VBO_supported = false;
glHint( GL_FOG_HINT, GL_DONT_CARE );
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
glShadeModel(GL_SMOOTH);
glAlphaFunc(GL_ALWAYS, 0);
}
By the way, I'm using GL Extension Wrangler with extensions.
Shader code & log (this exported file contains code which was directly passed to glShaderSource):
Vertex shader was successfully compiled to run on hardware.
Fragment shader was successfully compiled to run on hardware.
Fragment shader(s) linked, vertex shader(s) linked.
------------------------------------------------------------------------------------------
varying vec3 lightDir;
varying vec3 viewDir;
attribute vec4 tangent;
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
vec3 vertexPos = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 tn = tangent.xyz;
vec3 n = normalize(gl_NormalMatrix * gl_Normal);
vec3 t = normalize(gl_NormalMatrix * tangent.xyz);
vec3 b = cross(t, n) * -tangent.w;
mat3 tbnMatrix = mat3(t.x, b.x, n.x,
t.y, b.y, n.y,
t.z, b.z, n.z);
lightDir = (gl_LightSource[0].position.xyz - vertexPos) / 100.0;
lightDir = tbnMatrix * lightDir;
viewDir = -vertexPos;
viewDir = tbnMatrix * viewDir;
}
-----------------------------------------------------------------------------------------
varying vec3 lightDir;
varying vec3 viewDir;
uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D heightMap;
uniform float scale;
uniform float bias;
void main()
{
vec3 v = normalize(viewDir);
vec2 TexCoord = gl_TexCoord[0].st;
{
float height = texture2D(heightMap, gl_TexCoord[0].st).r;
height = height * scale + bias;
TexCoord = gl_TexCoord[0].st + (height * v.xy);
}
vec3 l = lightDir;
float atten = max(0.0, 1.0 - dot(l, l));
l = normalize(l);
vec3 n = normalize(texture2D(normalMap, TexCoord).rgb * 2.0 - 1.0);
vec3 h = normalize(l + v);
float nDotL = max(0.0, dot(n, l));
float nDotH = max(0.0, dot(n, h));
float power = (nDotL == 0.0) ? 0.0 : pow(nDotH, gl_FrontMaterial.shininess);
vec4 ambient = gl_FrontLightProduct[0].ambient * atten;
vec4 diffuse = gl_FrontLightProduct[0].diffuse * nDotL * atten;
vec4 specular = gl_FrontLightProduct[0].specular * power * atten;
vec4 color = gl_FrontLightModelProduct.sceneColor + ambient + diffuse + specular;color *= texture2D(diffuseMap,TexCoord);
gl_FragColor = color ;
}
Uniforms are working correctly because results are the same if I switch them with constant values.
Compiling shader:
void __Shader::import(){
if(imported) __Shader::~__Shader();
v = glCreateShader(GL_VERTEX_SHADER);
f = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(v, 1, (const GLchar **)&vsrc.cstr,NULL);
glShaderSource(f, 1, (const GLchar **)&fsrc.cstr,NULL);
glCompileShader(v);
glCompileShader(f);
p = glCreateProgram();
glAttachShader(p,v);
glAttachShader(p,f);
if(_flags & NORMAL_MAPPING)
glBindAttribLocation(p, ATTRIB_TANGENT, "tangent");
glLinkProgram(p);
if(_flags & DIFFUSE_MAPPING)
diffuseUni.loc = glGetUniformLocation(p, "diffuseMap");
if(_flags & NORMAL_MAPPING)
normalUni.loc = glGetUniformLocation(p, "normalMap");
if(_flags & PARALLAX_MAPPING)
heightUni.loc = glGetUniformLocation(p, "heightMap");
if(_flags & SPECULAR_MAPPING)
specularUni.loc = glGetUniformLocation(p, "specularMap");
imported = true;
}
Setting attribute in VBO:
if(tangents.size() > 0){
buffered |= 3;
glGenBuffers(1, &VBO_tangent);
glBindBuffer(GL_ARRAY_BUFFER, VBO_tangent);
glBufferData(GL_ARRAY_BUFFER, tangents.size()*sizeof(tangent), tangents.get_ptr(), GL_STATIC_DRAW);
}
// and in draw:
if(buffered & 3) {
glBindBuffer(GL_ARRAY_BUFFER, VBO_tangent);
glVertexAttribPointer(__Shader::ATTRIB_TANGENT, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(__Shader::ATTRIB_TANGENT);
}
and a small note
for(int i = 0; i < responders.size(); ++i)
if(strstr(responders[i].idea, "tangent problem"))
responders[i].please_dont_talk();
Just tell me your other ideas about what can be the reason of those bad results.
Wheew... already solved it. The problem was with loading texture files even though I did not see any disorders with diffuse mapping or even with diffuse+normal mapping. I was using IMG_Load from SDL, maybe I used it wrong way but it did not work for me. It was probably normal map messed up.
bad texture import code:
if(imported || filenamez.length() < 1) return;
SDL_Surface* surface = 0;
surface = IMG_Load(filenamez.c_str());
if (surface) {
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
bool endianess = filenamez.substr(filenamez.length()-4) == ".jpg";
glTexImage2D(GL_TEXTURE_2D, 0, 3, surface->w, surface->h, 0,
(endianess ? GL_RGB : GL_BGR), GL_UNSIGNED_BYTE, surface->pixels);
}
BEWARE !
I'm now using HBITMAP-based texture loading taken from dhpoware demo which I was talking about. And it works fine.
peace.
After 2-3 days of hard debugging, let me feel a little bit of euphoria.
Oh, I'd forget, the final result: