Calculating smooth normals on seperate faces - c++

I've been trying to achieve smooth normals on a generated terrain mesh (tile grid style) to give off smooth lighting, however I have only be able to achieve flat shaded lighting and I believe this might be to do with the fact all the faces are separated (though some vertex's may have the same position).
I've looked around for a similar question, the closest would be: this question however I'm not sure about how to go about implementing his solution of taking a percentage.
Currently I have this code which is based on the typical way to calculate smooth normals - but only achieves flat shading in my case.
// Reset Normals
for (int i = 0; i < this->vertexCount; i++) {
this->vertices[i].normal = glm::vec3(0.0f);
}
// For each face
for (int i = 0; i < totalVerts; i += 3) {
auto index0 = indices ? this->indices[i] : i;
auto index1 = indices ? this->indices[i + 1] : i + 1;
auto index2 = indices ? this->indices[i + 2] : i + 2;
auto vertex0 = this->vertices[index0].position;
auto vertex1 = this->vertices[index1].position;
auto vertex2 = this->vertices[index2].position;
auto normal = glm::cross(vertex1 - vertex0, vertex2 - vertex0);
this->vertices[index0].normal += normal;
this->vertices[index1].normal += normal;
this->vertices[index2].normal += normal;
}
// Normalize
for (int i = 0; i < this->vertexCount; i++) {
this->vertices[i].normal = glm::normalize(this->vertices[i].normal);
}
How would I go about changing this to smooth between surrounding faces? (As for why each face is separate, it's because some may have verts with specific properties which cannot be shared with other faces)

For your reference, a working code to get the face normals, vertices, and vertex normals is:
void get_vertices_and_normals_from_triangles(vector<triangle> &t, vector<vec3> &fn, vector<vec3> &v, vector<vec3> &vn)
{
// Face normals
fn.clear();
// Vertices
v.clear();
// Vertex normals
vn.clear();
if(0 == t.size())
return;
cout << "Triangles: " << t.size() << endl;
cout << "Welding vertices" << endl;
// Insert unique vertices into set.
set<indexed_vertex_3> vertex_set;
for(vector<triangle>::const_iterator i = t.begin(); i != t.end(); i++)
{
vertex_set.insert(i->vertex[0]);
vertex_set.insert(i->vertex[1]);
vertex_set.insert(i->vertex[2]);
}
cout << "Vertices: " << vertex_set.size() << endl;
cout << "Generating vertex indices" << endl;
vector<indexed_vertex_3> vv;
// Add indices to the vertices.
for(set<indexed_vertex_3>::const_iterator i = vertex_set.begin(); i != vertex_set.end(); i++)
{
size_t index = vv.size();
vv.push_back(*i);
vv[index].index = index;
}
for (size_t i = 0; i < vv.size(); i++)
{
vec3 vv_element(vv[i].x, vv[i].y, vv[i].z);
v.push_back(vv_element);
}
vertex_set.clear();
// Re-insert modifies vertices into set.
for(vector<indexed_vertex_3>::const_iterator i = vv.begin(); i != vv.end(); i++)
vertex_set.insert(*i);
cout << "Assigning vertex indices to triangles" << endl;
// Find the three vertices for each triangle, by index.
set<indexed_vertex_3>::iterator find_iter;
for(vector<triangle>::iterator i = t.begin(); i != t.end(); i++)
{
find_iter = vertex_set.find(i->vertex[0]);
i->vertex[0].index = find_iter->index;
find_iter = vertex_set.find(i->vertex[1]);
i->vertex[1].index = find_iter->index;
find_iter = vertex_set.find(i->vertex[2]);
i->vertex[2].index = find_iter->index;
}
vertex_set.clear();
cout << "Calculating normals" << endl;
fn.resize(t.size());
vn.resize(v.size());
for(size_t i = 0; i < t.size(); i++)
{
vec3 v0;// = t[i].vertex[1] - t[i].vertex[0];
v0.x = t[i].vertex[1].x - t[i].vertex[0].x;
v0.y = t[i].vertex[1].y - t[i].vertex[0].y;
v0.z = t[i].vertex[1].z - t[i].vertex[0].z;
vec3 v1;// = t[i].vertex[2] - t[i].vertex[0];
v1.x = t[i].vertex[2].x - t[i].vertex[0].x;
v1.y = t[i].vertex[2].y - t[i].vertex[0].y;
v1.z = t[i].vertex[2].z - t[i].vertex[0].z;
fn[i] = cross(v0, v1);
fn[i] = normalize(fn[i]);
vn[t[i].vertex[0].index] = vn[t[i].vertex[0].index] + fn[i];
vn[t[i].vertex[1].index] = vn[t[i].vertex[1].index] + fn[i];
vn[t[i].vertex[2].index] = vn[t[i].vertex[2].index] + fn[i];
}
for (size_t i = 0; i < vn.size(); i++)
vn[i] = normalize(vn[i]);
}
The code to stuff the vertex data into a vector is as follows. Note that the unwelded vertices of the triangles are reconstructed in the following calls to vertex_data.push_back(v0.x);, etc.
void draw_mesh(void)
{
glUseProgram(render.get_program());
glUniformMatrix4fv(uniforms.render.proj_matrix, 1, GL_FALSE, &main_camera.projection_mat[0][0]);
glUniformMatrix4fv(uniforms.render.mv_matrix, 1, GL_FALSE, &main_camera.view_mat[0][0]);
glUniform1f(uniforms.render.shading_level, 1.0f);
vector<float> vertex_data;
for (size_t i = 0; i < triangles.size(); i++)
{
vec3 colour(0.0f, 0.8f, 1.0f);
size_t v0_index = triangles[i].vertex[0].index;
size_t v1_index = triangles[i].vertex[1].index;
size_t v2_index = triangles[i].vertex[2].index;
vec3 v0_fn(vertex_normals[v0_index].x, vertex_normals[v0_index].y, vertex_normals[v0_index].z);
vec3 v1_fn(vertex_normals[v1_index].x, vertex_normals[v1_index].y, vertex_normals[v1_index].z);
vec3 v2_fn(vertex_normals[v2_index].x, vertex_normals[v2_index].y, vertex_normals[v2_index].z);
vec3 v0(triangles[i].vertex[0].x, triangles[i].vertex[0].y, triangles[i].vertex[0].z);
vec3 v1(triangles[i].vertex[1].x, triangles[i].vertex[1].y, triangles[i].vertex[1].z);
vec3 v2(triangles[i].vertex[2].x, triangles[i].vertex[2].y, triangles[i].vertex[2].z);
vertex_data.push_back(v0.x);
vertex_data.push_back(v0.y);
vertex_data.push_back(v0.z);
vertex_data.push_back(v0_fn.x);
vertex_data.push_back(v0_fn.y);
vertex_data.push_back(v0_fn.z);
vertex_data.push_back(colour.x);
vertex_data.push_back(colour.y);
vertex_data.push_back(colour.z);
vertex_data.push_back(v1.x);
vertex_data.push_back(v1.y);
vertex_data.push_back(v1.z);
vertex_data.push_back(v1_fn.x);
vertex_data.push_back(v1_fn.y);
vertex_data.push_back(v1_fn.z);
vertex_data.push_back(colour.x);
vertex_data.push_back(colour.y);
vertex_data.push_back(colour.z);
vertex_data.push_back(v2.x);
vertex_data.push_back(v2.y);
vertex_data.push_back(v2.z);
vertex_data.push_back(v2_fn.x);
vertex_data.push_back(v2_fn.y);
vertex_data.push_back(v2_fn.z);
vertex_data.push_back(colour.x);
vertex_data.push_back(colour.y);
vertex_data.push_back(colour.z);
}
GLuint components_per_vertex = 9;
const GLuint components_per_normal = 3;
GLuint components_per_position = 3;
const GLuint components_per_colour = 3;
GLuint triangle_buffer;
glGenBuffers(1, &triangle_buffer);
GLuint num_vertices = static_cast<GLuint>(vertex_data.size()) / components_per_vertex;
glBindBuffer(GL_ARRAY_BUFFER, triangle_buffer);
glBufferData(GL_ARRAY_BUFFER, vertex_data.size() * sizeof(GLfloat), &vertex_data[0], GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(glGetAttribLocation(render.get_program(), "position"));
glVertexAttribPointer(glGetAttribLocation(render.get_program(), "position"),
components_per_position,
GL_FLOAT,
GL_FALSE,
components_per_vertex * sizeof(GLfloat),
NULL);
glEnableVertexAttribArray(glGetAttribLocation(render.get_program(), "normal"));
glVertexAttribPointer(glGetAttribLocation(render.get_program(), "normal"),
components_per_normal,
GL_FLOAT,
GL_TRUE,
components_per_vertex * sizeof(GLfloat),
(const GLvoid*)(components_per_position * sizeof(GLfloat)));
glEnableVertexAttribArray(glGetAttribLocation(render.get_program(), "colour"));
glVertexAttribPointer(glGetAttribLocation(render.get_program(), "colour"),
components_per_colour,
GL_FLOAT,
GL_TRUE,
components_per_vertex * sizeof(GLfloat),
(const GLvoid*)(components_per_normal * sizeof(GLfloat) + components_per_position * sizeof(GLfloat)));
glDrawArrays(GL_TRIANGLES, 0, num_vertices);
glDeleteBuffers(1, &triangle_buffer);
}
I'm not sure if you're getting notifications of my edits.

Related

Problems with OpenGL tilemap rendering using glDrawArraysInstanced

Im working on my 2D tile map sidescroller game in c++, Im using few libraries like openGL, openAL etc.
Right now im reworking my render part of my game to make it better from performance side, before i started working on it, I had working version, but it was calling something like 180isch draw calls every frame, which is bad.
So i decided to go with instanced rendering for each texture in my game (if I use 90 textures on my map, i will try to render 90 draw calls, but if texture is not on my screen i will skip it), when i got to it and tried to render something i got cornered, I did some calculations and everything should be fine but it isnt.
if (game->data->currentWorld != 0)
{
tileSizeX = (2.0f / tileXsideCount);
tileSizeY = (2.0f / tileYsideCount);
float worldXoffset = getWorldOffsetX();
float worldYoffset = getWorldOffsetY();
float playerXoffset = getPlayerOffsetX();
float playerYoffset = getPlayerOffsetY();
int tileXoffset = getTileOffsetX();
int tileYoffset = getTileOffsetY();
int* textRenderCount = new int[game->data->currentWorld->textureCount];
float vertices[] =
{
-1 + tileSizeX, -1 + tileSizeY , 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
-1 + tileSizeX, -1, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom right
-1, -1, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom left
-1, -1 + tileSizeY, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
for (int i = 1; i < game->data->currentWorld->textureCount; i++)
{
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
int offsetIndex = 0;
unsigned int texture;
if (game->data->loadedTextures[i] != NULL)
{
texture = game->data->loadedTextures[i];
}
else
{
std::cout << "Renderer::RenderLoop - texture not found for id: " << i << endl;
}
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// texture coord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
tempShader->use();
glBindTexture(GL_TEXTURE_2D, texture);
for(int x = 0; x < game->data->currentWorld->sizeX; x++)
{
for (int y = 0; y < game->data->currentWorld->sizeY; y++)
{
if (
x > (tileXoffset - (tileXsideCount / 2) - 1) &&
x < (tileXoffset + (tileXsideCount)+1) &&
y >(tileYoffset - (tileYsideCount / 2) - 1) &&
y < (tileYoffset + (tileYsideCount)+1)
)
{
if (game->data->currentWorld->world[x][y]->id == i)
{
if (game->data->debugTiles)
{
std::cout << "For tile: x: " << x << " y: " << y;
}
float offsetX = { ((x*1.0f) - worldXoffset)*tileSizeX};
float offsetY = { ((y*1.0f) - worldYoffset)*tileSizeY};
if (game->data->debugTiles)
{
std::cout << " offset is: " << offsetX << offsetY << endl;
std::cout << "worldXoffset is : " << worldXoffset << endl;
std::cout << "worldYoffset is : " << worldYoffset << endl;
}
stringstream ss;
std::string indexText;
ss << offsetIndex;
indexText = ss.str();
tempShader->setVec2(("offset[" + indexText + "]").c_str(), offsetX, offsetY);
offsetIndex++;
}
}
}
}
if (game->data->debugTiles)
{
std::cout << "RenderCount for last render request:" << offsetIndex+1 <<endl;
game->data->debugTiles = false;
}
glBindVertexArray(VAO);
glDrawArraysInstanced(GL_TRIANGLES,0, 6,offsetIndex+1);
UnloadSceneBuffers();
}
and here my functions to return offsets:
float Renderer::getWorldOffsetX()
{
if (game->data->currentChar->xPos < tileXsideCount / 2)
{
return 0.0f;
}
else if(game->data->currentChar->xPos > game->data->currentWorld->sizeX - (tileXsideCount/2))
{
return (game->data->currentWorld->sizeX - (tileXsideCount / 2));
}
else
{
return (game->data->currentChar->xPos - tileXsideCount/2);
}
};
float Renderer::getWorldOffsetY()
{
if (game->data->currentChar->yPos < tileYsideCount / 2)
{
return 0.0f;
}
else if (game->data->currentChar->yPos > game->data->currentWorld->sizeY - (tileYsideCount / 2))
{
return (game->data->currentWorld->sizeY - (tileYsideCount / 2));
}
else
{
return (game->data->currentChar->yPos - tileYsideCount/2);
}
};
float Renderer::getPlayerOffsetX()
{
if (game->data->currentChar->xPos < tileXsideCount / 2)
{
return -1.0f + (game->data->currentChar->xPos*tileSizeX);
}
else if (game->data->currentChar->xPos > game->data->currentWorld->sizeX - (tileXsideCount / 2))
{
return +1.0f - (tileSizeX*((game->data->currentWorld->sizeX-tileXsideCount) - game->data->currentChar->xPos));
}
else
{
return 0.0f;
}
};
float Renderer::getPlayerOffsetY()
{
if (game->data->currentChar->yPos < tileYsideCount / 2)
{
return -1.0f + (game->data->currentChar->yPos*tileSizeY);
}
else if (game->data->currentChar->yPos > game->data->currentWorld->sizeY - (tileYsideCount / 2))
{
return +1.0f - (tileSizeY*((game->data->currentWorld->sizeY - tileYsideCount) - game->data->currentChar->yPos));
}
else
{
return 0.0f;
}
};
int Renderer::getTileOffsetX()
{
if (game->data->currentChar->xPos < tileXsideCount / 2)
{
return 0 + tileXsideCount/2;
}
else if (game->data->currentChar->xPos > game->data->currentWorld->sizeX - (tileXsideCount / 2))
{
return (game->data->currentWorld->sizeX-(tileXsideCount/2));
}
else
{
return game->data->currentChar->xPos;
}
}
int Renderer::getTileOffsetY()
{
if (game->data->currentChar->yPos < tileYsideCount / 2)
{
return 0 + tileYsideCount / 2;
}
else if (game->data->currentChar->yPos > game->data->currentWorld->sizeY -(tileYsideCount / 2))
{
return (game->data->currentWorld->sizeY - (tileYsideCount / 2));
}
else
{
return game->data->currentChar->yPos;
}
}
and here are my shaders:
Vertex:
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec3 aColor;
layout(location = 2) in vec2 aTexCoord;
out vec2 TexCoord;
uniform vec2 offset[198];
void main()
{
vec2 currentOffset = offset[gl_InstanceID];
gl_Position = vec4(aPos + currentOffset,0.0f,1.0f);
TexCoord = aTexCoord;
}
Fragment:
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}
and this is my shader set vec2 function (its mostly the same as on learnopengl.com):
void setVec2(const std::string &name, float input1,float input2)
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), input1,input2);
}
Edit:
This is what i see:

OpenGL vertex array objects with tinyobjloader

In order to use modern openGl with tinyobjloader, I'm trying to change the viewer exemple.
I just change the LoadObjAndConvert function, to add vertex array objects as i seen in this tutorial, and to no longer use the buffer object that contains all the data (position, indices, color, uv) because it seems that we can no longer use it with modern openGL.
Result look like I have bad vertex index, the model is only partly rendered, and if the model has only one mesh (the stanford bunny) it does not even show up.
The code is too long, but it is the same as the tinyobjloader viewer exemple, so I will only post functions that are different.
Here is the LoadObjAndConvert function modified (modified parts are between lines to help) :
static bool LoadObjAndConvert(float bmin[3], float bmax[3],
std::vector<DrawObject>* drawObjects,
std::vector<tinyobj::material_t>& materials,
std::map<std::string, GLuint>& textures,
const char* filename) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
timerutil tm;
tm.start();
std::string base_dir = GetBaseDir(filename);
if (base_dir.empty()) {
base_dir = ".";
}
#ifdef _WIN32
base_dir += "\\";
#else
base_dir += "/";
#endif
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename,
base_dir.c_str());
if (!err.empty()) {
std::cerr << err << std::endl;
}
tm.end();
if (!ret) {
std::cerr << "Failed to load " << filename << std::endl;
return false;
}
printf("Parsing time: %d [ms]\n", (int)tm.msec());
printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3);
printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3);
printf("# of texcoords = %d\n", (int)(attrib.texcoords.size()) / 2);
printf("# of materials = %d\n", (int)materials.size());
printf("# of shapes = %d\n", (int)shapes.size());
// Append `default` material
materials.push_back(tinyobj::material_t());
for (size_t i = 0; i < materials.size(); i++) {
printf("material[%d].diffuse_texname = %s\n", int(i),
materials[i].diffuse_texname.c_str());
}
// Load diffuse textures
{
for (size_t m = 0; m < materials.size(); m++) {
tinyobj::material_t* mp = &materials[m];
if (mp->diffuse_texname.length() > 0) {
// Only load the texture if it is not already loaded
if (textures.find(mp->diffuse_texname) == textures.end()) {
GLuint texture_id;
int w, h;
int comp;
std::string texture_filename = mp->diffuse_texname;
if (!FileExists(texture_filename)) {
// Append base dir.
texture_filename = base_dir + mp->diffuse_texname;
if (!FileExists(texture_filename)) {
std::cerr << "Unable to find file: " << mp->diffuse_texname
<< std::endl;
exit(1);
}
}
unsigned char* image =
stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default);
if (!image) {
std::cerr << "Unable to load texture: " << texture_filename
<< std::endl;
exit(1);
}
std::cout << "Loaded texture: " << texture_filename << ", w = " << w
<< ", h = " << h << ", comp = " << comp << std::endl;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (comp == 3) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
GL_UNSIGNED_BYTE, image);
} else if (comp == 4) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, image);
} else {
assert(0); // TODO
}
glBindTexture(GL_TEXTURE_2D, 0);
stbi_image_free(image);
textures.insert(std::make_pair(mp->diffuse_texname, texture_id));
}
}
}
}
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
{
for (size_t s = 0; s < shapes.size(); s++) {
/*-----------------------------------------------------------*/
DrawObject o;// I keep this object for later purpose, texture, etc
//std::vector<float> buffer; // pos(3float), normal(3float), color(3float)
//I replace "buffer" by arrays:
std::vector<GLfloat> mesh_vertex;
std::vector<GLfloat> mesh_normals;
std::vector<GLfloat> mesh_colors;
std::vector<GLfloat> mesh_textCoords;
std::vector<GLuint> mesh_indices;
/*fill index array*/
for (long i = 0; i < shapes[s].mesh.indices.size(); i++)
{
mesh_indices.push_back(shapes[s].mesh.indices[i].vertex_index);
}
/*-----------------------------------------------------------*/
// Check for smoothing group and compute smoothing normals
std::map<int, vec3> smoothVertexNormals;
if (hasSmoothingGroup(shapes[s]) > 0) {
std::cout << "Compute smoothingNormal for shape [" << s << "]" << std::endl;
computeSmoothingNormals(attrib, shapes[s], smoothVertexNormals);
}
for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) {
tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0];
tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1];
tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2];
int current_material_id = shapes[s].mesh.material_ids[f];
if ((current_material_id < 0) ||
(current_material_id >= static_cast<int>(materials.size()))) {
// Invaid material ID. Use default material.
current_material_id =
materials.size() -
1; // Default material is added to the last item in `materials`.
}
// if (current_material_id >= materials.size()) {
// std::cerr << "Invalid material index: " << current_material_id <<
// std::endl;
//}
//
float diffuse[3];
for (size_t i = 0; i < 3; i++) {
diffuse[i] = materials[current_material_id].diffuse[i];
}
float tc[3][2];
if (attrib.texcoords.size() > 0) {
if ((idx0.texcoord_index < 0) || (idx1.texcoord_index < 0) ||
(idx2.texcoord_index < 0)) {
// face does not contain valid uv index.
tc[0][0] = 0.0f;
tc[0][1] = 0.0f;
tc[1][0] = 0.0f;
tc[1][1] = 0.0f;
tc[2][0] = 0.0f;
tc[2][1] = 0.0f;
} else {
assert(attrib.texcoords.size() >
size_t(2 * idx0.texcoord_index + 1));
assert(attrib.texcoords.size() >
size_t(2 * idx1.texcoord_index + 1));
assert(attrib.texcoords.size() >
size_t(2 * idx2.texcoord_index + 1));
// Flip Y coord.
tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index];
tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1];
tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index];
tc[1][1] = 1.0f - attrib.texcoords[2 * idx1.texcoord_index + 1];
tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index];
tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1];
}
} else {
tc[0][0] = 0.0f;
tc[0][1] = 0.0f;
tc[1][0] = 0.0f;
tc[1][1] = 0.0f;
tc[2][0] = 0.0f;
tc[2][1] = 0.0f;
}
float v[3][3];
for (int k = 0; k < 3; k++) {
int f0 = idx0.vertex_index;
int f1 = idx1.vertex_index;
int f2 = idx2.vertex_index;
assert(f0 >= 0);
assert(f1 >= 0);
assert(f2 >= 0);
v[0][k] = attrib.vertices[3 * f0 + k];
v[1][k] = attrib.vertices[3 * f1 + k];
v[2][k] = attrib.vertices[3 * f2 + k];
bmin[k] = std::min(v[0][k], bmin[k]);
bmin[k] = std::min(v[1][k], bmin[k]);
bmin[k] = std::min(v[2][k], bmin[k]);
bmax[k] = std::max(v[0][k], bmax[k]);
bmax[k] = std::max(v[1][k], bmax[k]);
bmax[k] = std::max(v[2][k], bmax[k]);
}
float n[3][3];
{
bool invalid_normal_index = false;
if (attrib.normals.size() > 0) {
int nf0 = idx0.normal_index;
int nf1 = idx1.normal_index;
int nf2 = idx2.normal_index;
if ((nf0 < 0) || (nf1 < 0) || (nf2 < 0)) {
// normal index is missing from this face.
invalid_normal_index = true;
} else {
for (int k = 0; k < 3; k++) {
assert(size_t(3 * nf0 + k) < attrib.normals.size());
assert(size_t(3 * nf1 + k) < attrib.normals.size());
assert(size_t(3 * nf2 + k) < attrib.normals.size());
n[0][k] = attrib.normals[3 * nf0 + k];
n[1][k] = attrib.normals[3 * nf1 + k];
n[2][k] = attrib.normals[3 * nf2 + k];
}
}
} else {
invalid_normal_index = true;
}
if (invalid_normal_index && !smoothVertexNormals.empty()) {
// Use smoothing normals
int f0 = idx0.vertex_index;
int f1 = idx1.vertex_index;
int f2 = idx2.vertex_index;
if (f0 >= 0 && f1 >= 0 && f2 >= 0) {
n[0][0] = smoothVertexNormals[f0].v[0];
n[0][1] = smoothVertexNormals[f0].v[1];
n[0][2] = smoothVertexNormals[f0].v[2];
n[1][0] = smoothVertexNormals[f1].v[0];
n[1][1] = smoothVertexNormals[f1].v[1];
n[1][2] = smoothVertexNormals[f1].v[2];
n[2][0] = smoothVertexNormals[f2].v[0];
n[2][1] = smoothVertexNormals[f2].v[1];
n[2][2] = smoothVertexNormals[f2].v[2];
invalid_normal_index = false;
}
}
if (invalid_normal_index) {
// compute geometric normal
CalcNormal(n[0], v[0], v[1], v[2]);
n[1][0] = n[0][0];
n[1][1] = n[0][1];
n[1][2] = n[0][2];
n[2][0] = n[0][0];
n[2][1] = n[0][1];
n[2][2] = n[0][2];
}
}
for (int k = 0; k < 3; k++) {
/*-----------------------------------------------------------*/
// I leave old calls to "buffer" in comment for understanding
//buffer.push_back(v[k][0]);
//buffer.push_back(v[k][1]);
//buffer.push_back(v[k][2]);
mesh_vertex.push_back(v[k][0]);
mesh_vertex.push_back(v[k][1]);
mesh_vertex.push_back(v[k][2]);
//buffer.push_back(n[k][0]);
//buffer.push_back(n[k][1]);
//buffer.push_back(n[k][2]);
mesh_normals.push_back(n[k][0]);
mesh_normals.push_back(n[k][1]);
mesh_normals.push_back(n[k][2]);
// Combine normal and diffuse to get color.
float normal_factor = 0.2;
float diffuse_factor = 1 - normal_factor;
float c[3] = {n[k][0] * normal_factor + diffuse[0] * diffuse_factor,
n[k][1] * normal_factor + diffuse[1] * diffuse_factor,
n[k][2] * normal_factor + diffuse[2] * diffuse_factor};
float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
if (len2 > 0.0f) {
float len = sqrtf(len2);
c[0] /= len;
c[1] /= len;
c[2] /= len;
}
//buffer.push_back(c[0] * 0.5 + 0.5);
//buffer.push_back(c[1] * 0.5 + 0.5);
//buffer.push_back(c[2] * 0.5 + 0.5);
mesh_colors.push_back(c[0] * 0.5 + 0.5);
mesh_colors.push_back(c[1] * 0.5 + 0.5);
mesh_colors.push_back(c[2] * 0.5 + 0.5);
//buffer.push_back(tc[k][0]);
//buffer.push_back(tc[k][1]);
mesh_textCoords.push_back(tc[k][0]);
mesh_textCoords.push_back(tc[k][1]);
/*-----------------------------------------------------------*/
}
}
o.vb_id = 0;
o.numTriangles = 0;
// OpenGL viewer does not support texturing with per-face material.
if (shapes[s].mesh.material_ids.size() > 0 &&
shapes[s].mesh.material_ids.size() > s) {
o.material_id = shapes[s].mesh.material_ids[0]; // use the material ID
// of the first face.
} else {
o.material_id = materials.size() - 1; // = ID for default material.
}
printf("shape[%d] material_id %d\n", int(s), int(o.material_id));
/*-----------------------------------------------------------*/
/*if (buffer.size() > 0) {
glGenBuffers(1, &o.vb_id);
glBindBuffer(GL_ARRAY_BUFFER, o.vb_id);
glBufferData(GL_ARRAY_BUFFER, buffer.size() * sizeof(float),
&buffer.at(0), GL_STATIC_DRAW);
o.numTriangles = buffer.size() / (3 + 3 + 3 + 2) /
3; // 3:vtx, 3:normal, 3:col, 2:texcoord
printf("shape[%d] # of triangles = %d\n", static_cast<int>(s),
o.numTriangles);
}
drawObjects->push_back(o);*/
// Replace by :
GLuint positionVBO = 0;
GLuint texcoordVBO = 0;
GLuint normalVBO = 0;
GLuint indicesEBO = 0;
// Upload per-vertex positions
if (!mesh_vertex.empty())
{
glGenBuffers(1, &positionVBO);
glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
glBufferData(GL_ARRAY_BUFFER, mesh_vertex.size() * sizeof(GLfloat), &mesh_vertex[0], GL_STATIC_DRAW); // GL_DYNAMIC_DRAW ?
glBindBuffer(GL_ARRAY_BUFFER, 0);
positionVBO_array.push_back(positionVBO);
}
// Upload per-vertex texture coordinates
if (!mesh_textCoords.empty())
{
glGenBuffers(1, &texcoordVBO);
glBindBuffer(GL_ARRAY_BUFFER, texcoordVBO);
glBufferData(GL_ARRAY_BUFFER,
mesh_textCoords.size() * sizeof(float),
&mesh_textCoords[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// Upload per-vertex normals
if (!mesh_normals.empty())
{
glGenBuffers(1, &normalVBO);
glBindBuffer(GL_ARRAY_BUFFER, normalVBO);
glBufferData(GL_ARRAY_BUFFER, mesh_normals.size() * sizeof(GLfloat), &mesh_normals[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
normalVBO_array.push_back(normalVBO);
}
// Upload the indices that form triangles
if (!shapes[0].mesh.indices.empty())
{
glGenBuffers(1, &indicesEBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
shapes[s].mesh.indices.size() * sizeof(unsigned int),
shapes[s].mesh.indices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
indicesEBO_array.push_back(indicesEBO);
indicesEBOSize_array.push_back(shapes[s].mesh.indices.size());
}
// Hook up vertex/index buffers to a "vertex array object" (VAO)
// VAOs are the closest thing OpenGL has to a "mesh" object.
// VAOs feed data from buffers to the inputs of a vertex shader.
GLuint meshVAO;
vglGenVertexArrays(1, &meshVAO);
meshVAO_array.push_back(meshVAO);// I keep the ids in order to loop inside meshVAO_array in the draw function
// Attach position buffer as attribute 0
if (positionVBO != 0)
{
glBindVertexArray(meshVAO);
// Note: glVertexAttribPointer sets the current
// GL_ARRAY_BUFFER_BINDING as the source of data
// for this attribute.
// That's why we bind a GL_ARRAY_BUFFER before
// calling glVertexAttribPointer then
// unbind right after (to clean things up).
glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof(float) * 3, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Enable the attribute (they are disabled by default
// -- this is very easy to forget!!)
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
// Attach texcoord buffer as attribute 1
if (texcoordVBO != 0)
{
glBindVertexArray(meshVAO);
glBindBuffer(GL_ARRAY_BUFFER, texcoordVBO);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
sizeof(float) * 2, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
}
// Attach normal buffer as attribute 2
if (normalVBO != 0)
{
glBindVertexArray(meshVAO);
glBindBuffer(GL_ARRAY_BUFFER, normalVBO);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE,
sizeof(float) * 3, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(2);
glBindVertexArray(0);
}
if (indicesEBO != 0)
{
glBindVertexArray(meshVAO);
// Note: Calling glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// when a VAO is bound attaches the index buffer to the VAO.
// From an API design perspective, this is subtle.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesEBO);
glBindVertexArray(0);
}
/*-----------------------------------------------------------*/
}
}
printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]);
printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]);
return true;
}
(Sorry for this long code block)
And here is the while loop of the main function, the only difference with tinyobjloader is between the two lines:
unsigned int program = shaders::CreateShader("data/simple.vert", "data/simple.frag"); // just some really simples shaders
while (glfwWindowShouldClose(window) == GL_FALSE) {
glfwPollEvents();
glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// camera & rotate
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
GLfloat mat[4][4];
gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0],
up[1], up[2]);
build_rotmatrix(mat, curr_quat);
glMultMatrixf(&mat[0][0]);
// Fit to -1, 1
glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent);
// Centerize object.
glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]),
-0.5 * (bmax[2] + bmin[2]));
/*-----------------------------------------------------------*/
//Draw(gDrawObjects, materials, textures);
// Can now bind the vertex array object to
// the graphics pipeline, to render with it.
glUseProgram(program);
for (int s = 0; s < meshVAO_array.size(); s++)
{
glBindVertexArray(meshVAO_array[s]);
glDrawElements(GL_TRIANGLES, indicesEBOSize_array[s], GL_UNSIGNED_INT, 0);//mesh.IndexCount
glBindVertexArray(0);
}
glUseProgram(0);
// when done, unbind it from the graphics pipeline:
glBindVertexArray(0);
/*-----------------------------------------------------------*/
glfwSwapBuffers(window);
}
What am I doing wrong?
In the nested loops you all the indices of shapes[].mesh.indices are use to lokkup the attributes, which are stored in attrib.vertices, attrib.normals and attrib.texcoords.
This attributes are prepared and linearized. They are stored in there idexed order to the linear arrays mesh_vertex, mesh_normals, mesh_colors and mesh_textCoords.
But the indices are directly copied from shapes[].mesh.indices to mesh_indices
for (long i = 0; i < shapes[s].mesh.indices.size(); i++)
{
mesh_indices.push_back(shapes[s].mesh.indices[i].vertex_index);
}
The indices in mesh_indices still refer to the vertex coordinates stored in attrib.vertices but the have no meaning for the attributes in the new containers.
The original indices are not needed any more. The indices of the new attribute would be continuously ascending: [0, 1, 2, 3, 4, 5 ...]
It is sufficient to draw the array of generic vertex attribute data in its existing order:
// you have to know the number of attributes
// something like mesh_vertex.size() / 3;
GLsizei no_of_attributes = .... ;
glBindVertexArray(meshVAO_array[s]);
glDrawArrays(GL_TRIANGLES, 0, no_of_attributes);
glBindVertexArray(0);

Why text is rendering as quads?

I am using freetype and freetype-gl to render text.
Unfortunately my text is rendering as quads.
That's how I am doing it:
enum
{
//Max num of objects
MAX_SPRITES = 60000,
//Max num of indices
//One sprite got 6 indices
MAX_INDICES = MAX_SPRITES * 6,
//Once sprite got 4 vertices
SPRITE_SIZE = sizeof(Vertex) * 4,
//Max num of GL_TEXTUREX => X -> int <0; 32>
MAX_TEXTURES = 32
};
enum BUFFER_SIZE
{
VERTEX = SPRITE_SIZE * MAX_SPRITES,
INDEX = MAX_INDICES * sizeof(GLuint),
};
enum SHADER_OUT
{
POSITION = 0,
COLOR = 1,
UV = 2,
TEXTURE = 3,
};
class Renderer2D
{
public:
Renderer2D() = default;
~Renderer2D();
void Create();
void RenderClear();
void DrawString(const std::string& text, const Vector2& position, const Color& color);
void Render();
private:
Uint vao = 0,
vbo = 0,
ebo = 0,
indexCount = 0,
drawCalls = 0;
ftgl::texture_atlas_t* textureAtlas = nullptr;
ftgl::texture_font_t* font = nullptr;
std::vector<MiUint> textureSlots;
Vertex* mappedVertex = nullptr;
void End();
Uint* SetIndices();
float FindTexture();
};
Renderer2D::~Renderer2D()
{
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
glDeleteVertexArrays(1, &vao);
ftgl::texture_atlas_delete(textureAtlas);
ftgl::texture_font_delete(font);
textureSlots.clear();
}
void Renderer2D::Create()
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, BUFFER_SIZE::VERTEX, nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(SHADER_OUT::POSITION);
glEnableVertexAttribArray(SHADER_OUT::COLOR);
glEnableVertexAttribArray(SHADER_OUT::UV);
glEnableVertexAttribArray(SHADER_OUT::TEXTURE);
glVertexAttribPointer(SHADER_OUT::POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const void*)offsetof(Vertex, position));
glVertexAttribPointer(SHADER_OUT::COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), (const void*)offsetof(Vertex, color));
glVertexAttribPointer(SHADER_OUT::UV, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const void*)offsetof(Vertex, uv));
glVertexAttribPointer(SHADER_OUT::TEXTURE, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const void*)offsetof(Vertex, texID));
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, BUFFER_SIZE::INDEX, nullptr, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, NULL, BUFFER_SIZE::INDEX, SetIndices());
glBindVertexArray(0);
textureAtlas = ftgl::texture_atlas_new(512, 512, 1);
font = ftgl::texture_font_new_from_file(textureAtlas, 50, "Media/arial.ttf");
}
void Renderer2D::RenderClear()
{
textureSlots.clear();
mappedVertex = nullptr;
mappedVertex = (Vertex*)glMapNamedBufferRange(vbo, NULL, BUFFER_SIZE::VERTEX, GL_MAP_WRITE_BIT);
}
void Renderer2D::DrawString(const std::string& text, const Vector2& position, const Color& color)
{
using namespace ftgl;
float ts = FindTexture(),
x = position.x;
for (MiUint i = 0; i < text.length(); i++) {
char c = text[i];
texture_glyph_t* glyph = texture_font_get_glyph(font, c);
if (glyph != nullptr) {
if (i > 0) {
float kerning = texture_glyph_get_kerning(glyph, text[i - 1]);
x += kerning;
}
float x0 = x + glyph->offset_x,
x1 = x0 + glyph->width,
y0 = position.y + glyph->offset_y,
y1 = y0 - glyph->height,
u0 = glyph->s0,
u1 = glyph->s1,
v0 = glyph->t0,
v1 = glyph->t1;
mappedVertex->position = Vector3(x0, y0, 0.0f);
mappedVertex->uv = Vector2(u0, v0);
mappedVertex->texID = ts;
mappedVertex->color = color;
mappedVertex++;
mappedVertex->position = Vector3(x0, y1, 0.0f);
mappedVertex->uv = Vector2(u0, v1);
mappedVertex->texID = ts;
mappedVertex->color = color;
mappedVertex++;
mappedVertex->position = Vector3(x1, y1, 0.0f);
mappedVertex->uv = Vector2(u1, v1);
mappedVertex->texID = ts;
mappedVertex->color = color;
mappedVertex++;
mappedVertex->position = Vector3(x1, y0, 0.0f);
mappedVertex->uv = Vector2(u1, v0);
mappedVertex->texID = ts;
mappedVertex->color = color;
mappedVertex++;
indexCount += 6;
x += glyph->advance_x;
}
}
}
void Renderer2D::Render()
{
End();
drawCalls = 0;
for (int i = 0; i < (int)textureSlots.size(); ++i) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, textureSlots[i]);
}
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
++drawCalls;
indexCount = 0;
textureSlots.clear();
mappedVertex = nullptr;
glDisable(GL_BLEND);
}
void Renderer2D::End()
{
glUnmapNamedBuffer(vbo);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
MiUint* Renderer2D::SetIndices()
{
MiUint* indices = new MiUint[BUFFER_SIZE::INDEX],
offset = 0;
for (int i = 0; i < BUFFER_SIZE::INDEX; i += 6) {
indices[i] = offset;
indices[i + 1] = offset + 1;
indices[i + 2] = offset + 2;
indices[i + 3] = offset + 2;
indices[i + 4] = offset + 3;
indices[i + 5] = offset;
offset += 4;
}
return indices;
}
float Renderer2D::FindTexture()
{
float result = 0.0f;
bool found = false;
for (MiUint i = 0; i < (MiUint)textureSlots.size(); i++) {
if (textureSlots[i] == textureAtlas->id) {
result = (float)(i + 1);
found = true;
break;
}
}
if (!found) {
if (textureSlots.size() >= MAX_TEXTURES)
{
Render();
RenderClear();
}
textureSlots.push_back(textureAtlas->id);
result = (float)(textureSlots.size());
}
return result;
}
and my main is just setting up a 2d camera (ortho matrix, etc.), renderer.Create, in main loop clear renderer, drawString("STRING", Vector2(0.0f, 0.0f), Color(255, 0, 0, 255)) and renderer render.
Result is like:
How to fix it? I went through drawString method and it seems as if it works perfectly fine. What causes that it's rendered like this?
#Rabbid76
Simply just
Vertex:
#version 460 core
layout (location = 0) in vec2 a_position;
layout (location = 1) in vec4 a_color;
layout (location = 2) in vec2 a_uv;
layout (location = 3) in float a_texID;
out vec2 v_fragmentUV;
out vec4 v_fragmentColor;
out vec2 v_fragmenPosition;
out float v_texID;
uniform mat4 u_camera;
void main()
{
gl_Position = u_camera * vec4(a_position, 0.0, 1.0);
v_fragmenPosition = a_position;
v_fragmentColor = a_color;
v_texID = a_texID;
v_fragmentUV = vec2(a_uv.x, 1 - a_uv.y);
}
Pixel:
#version 460 core
out vec4 outColor;
in vec2 v_fragmentUV;
in vec4 v_fragmentColor;
in vec2 v_fragmenPosition;
in float v_texID;
layout (location = 0) uniform sampler2D u_textureSampler[32];
void main()
{
int texID = int(v_texID - 0.5);
outColor = texture(u_textureSampler[texID], v_fragmentUV);
}
I set tetxure sampler uniform in main. When I render textures like a player it's rendered very well. There are only text problems.
Wher do you use glUniform* to set the texture unit index to the texture sampler uniform?
Maybe I should just show my main class
App2D::App2D()
{
window.Init(4, 6);
window.Create("w", 1280, 720, WINDOW_RESIZABLE);
camera.Projection(0.0f, 1280.0f, 0.0f, 720.0f);
SetupShaders();
renderer2D.Create();
glClearColor(0.5, 0.5, 0.5, 1.0);
/*short textureIDS[] = {
texture.LoadFromFile("Media/MenuImage.png"),
texture.LoadFromFile("Media/image1.png"),
texture.LoadFromFile("Media/image2.png"),
};
for (int y = 0; y < 720; y += 4.) {
for (int x = 0; x < 1280; x += 4.) {
sprite.push_back(new Sprite2D(Rect(x, y, 4.0f), Rect(0.0f, 0.0f, 1.0f, 1.0f), rand() % 3 + 1));
}
}
std::cout << sprite.size() << std::endl;*/
}
void App2D::MainLoop()
{
int frames = 0;
float time = 0.0f;
while (!quit) {
timer.Reset();
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
ExitGame();
}
UpdateInput();
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SetupCameraShader();
camera.Update();
Update(1.0f);
renderer2D.RenderClear();
renderer2D.DrawString("STRING", Vector2(0.0f, 0.0f), Color(255, 0, 0, 255));
renderer2D.Render();
window.SwapBuffers();
frames++;
if (timer.Elapsed() - time > 1.0f) {
time++;
Log("FPS: " + std::to_string(frames) + "\n");
frames = 0;
}
}
}
void App2D::SetupShaders()
{
shader.Free();
GLuint shaders[2] = {
shader.CreateShader("vertex.shader", GL_VERTEX_SHADER),
shader.CreateShader("fragment.shader", GL_FRAGMENT_SHADER)
}; shader.CreateAndUseProgram(shaders, 2);
GLint textures[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
glUniform1iv(0, 10, textures);
}
void App2D::SetupCameraShader()
{
GLint u_camera = shader.GetUniformLocation("u_camera");
Mat4 cameraMatrix = Mat4(1.0f);
cameraMatrix = camera.getCameraMatrix();
//cameraMatrix = textCamera.getCameraMatrix();
glUniformMatrix4fv(u_camera, 1, GL_FALSE, &(cameraMatrix[0][0]));
}

ASSIMP Animated Mesh glued to camera, looks 2D

Have been trying to render a MD5 mesh to my games engine but when I do the mesh seems to me glued to the same (follows it around) and looks flat. for an example of the problem, look here
AnimatedMesh.cpp:
#include "AnimatedMesh.h"
#define POSITION_LOCATION 0
#define TEX_COORD_LOCATION 1
#define NORMAL_LOCATION 2
#define BONE_ID_LOCATION 3
#define BONE_WEIGHT_LOCATION 4
void AnimMesh::VertexBoneData::AddBoneData(unsigned int BoneID, float Weight)
{
for (unsigned int i = 0; i < ARRAY_SIZE_IN_ELEMENTS(IDs); i++) {
if (Weights[i] == 0.0) {
IDs[i] = BoneID;
Weights[i] = Weight;
return;
}
}
assert(0);
}
AnimMesh::AnimMesh()
{
m_VAO = 0;
ZERO_MEM(m_Buffers);
m_NumBones = 0;
m_pScene = NULL;
}
AnimMesh::~AnimMesh()
{
Clear();
}
void AnimMesh::Clear()
{
for (unsigned int i = 0; i < m_Textures.size(); i++) {
SAFE_DELETE(m_Textures[i]);
}
if (m_Buffers[0] != 0) {
glDeleteBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);
}
if (m_VAO != 0) {
glDeleteVertexArrays(1, &m_VAO);
m_VAO = 0;
}
}
bool AnimMesh::LoadAnimMesh(const string& Filename)
{
Clear();
glGenVertexArrays(1, &m_VAO);
glBindVertexArray(m_VAO);
glGenBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);
bool Ret = false;
m_pScene = m_Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);
if (m_pScene) {
m_GlobalInverseTransform = m_pScene->mRootNode->mTransformation;
m_GlobalInverseTransform.Inverse();
Ret = InitFromScene(m_pScene, Filename);
}
else {
printf("Error parsing '%s': '%s'\n", Filename.c_str(), m_Importer.GetErrorString());
}
// Make sure the VAO is not changed from the outside
glBindVertexArray(0);
return Ret;
}
bool AnimMesh::InitFromScene(const aiScene* pScene, const string& Filename)
{
m_Entries.resize(pScene->mNumMeshes);
m_Textures.resize(pScene->mNumMaterials);
vector<Vector3f> Positions;
vector<Vector3f> Normals;
vector<Vector2f> TexCoords;
vector<VertexBoneData> Bones;
vector<unsigned int> Indices;
unsigned int NumVertices = 0;
unsigned int NumIndices = 0;
// Count the number of vertices and indices
for (unsigned int i = 0; i < m_Entries.size(); i++) {
m_Entries[i].MaterialIndex = pScene->mMeshes[i]->mMaterialIndex;
m_Entries[i].NumIndices = pScene->mMeshes[i]->mNumFaces * 3;
m_Entries[i].BaseVertex = NumVertices;
m_Entries[i].BaseIndex = NumIndices;
NumVertices += pScene->mMeshes[i]->mNumVertices;
NumIndices += m_Entries[i].NumIndices;
}
// Reserve space in the vectors for the vertex attributes and indices
Positions.reserve(NumVertices);
Normals.reserve(NumVertices);
TexCoords.reserve(NumVertices);
Bones.resize(NumVertices);
Indices.reserve(NumIndices);
// Initialize the meshes in the scene one by one
for (unsigned int i = 0; i < m_Entries.size(); i++) {
const aiMesh* paiMesh = pScene->mMeshes[i];
InitMesh(i, paiMesh, Positions, Normals, TexCoords, Bones, Indices);
}
if (!InitMaterials(pScene, Filename)) {
return false;
}
// Generate and populate the buffers with vertex attributes and the indices
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[POS_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Positions[0]) * Positions.size(), &Positions[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(POSITION_LOCATION);
glVertexAttribPointer(POSITION_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[TEXCOORD_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(TexCoords[0]) * TexCoords.size(), &TexCoords[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(TEX_COORD_LOCATION);
glVertexAttribPointer(TEX_COORD_LOCATION, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[NORMAL_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Normals[0]) * Normals.size(), &Normals[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(NORMAL_LOCATION);
glVertexAttribPointer(NORMAL_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[BONE_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Bones[0]) * Bones.size(), &Bones[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(BONE_ID_LOCATION);
glVertexAttribIPointer(BONE_ID_LOCATION, 4, GL_INT, sizeof(VertexBoneData), (const GLvoid*)0);
glEnableVertexAttribArray(BONE_WEIGHT_LOCATION);
glVertexAttribPointer(BONE_WEIGHT_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)16);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Buffers[INDEX_BUFFER]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices[0]) * Indices.size(), &Indices[0], GL_STATIC_DRAW);
return GLCheckError();
}
void AnimMesh::InitMesh(unsigned int MeshIndex,
const aiMesh* paiMesh,
vector<Vector3f>& Positions,
vector<Vector3f>& Normals,
vector<Vector2f>& TexCoords,
vector<VertexBoneData>& Bones,
vector<unsigned int>& Indices)
{
const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
// Populate the vertex attribute vectors
for (unsigned int i = 0; i < paiMesh->mNumVertices; i++) {
const aiVector3D* pPos = &(paiMesh->mVertices[i]);
const aiVector3D* pNormal = &(paiMesh->mNormals[i]);
const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;
Positions.push_back(Vector3f(pPos->x, pPos->y, pPos->z));
Normals.push_back(Vector3f(pNormal->x, pNormal->y, pNormal->z));
TexCoords.push_back(Vector2f(pTexCoord->x, pTexCoord->y));
}
LoadBones(MeshIndex, paiMesh, Bones);
// Populate the index buffer
for (unsigned int i = 0; i < paiMesh->mNumFaces; i++) {
const aiFace& Face = paiMesh->mFaces[i];
assert(Face.mNumIndices == 3);
Indices.push_back(Face.mIndices[0]);
Indices.push_back(Face.mIndices[1]);
Indices.push_back(Face.mIndices[2]);
}
}
void AnimMesh::LoadBones(unsigned int MeshIndex, const aiMesh* pMesh, vector<VertexBoneData>& Bones)
{
for (unsigned int i = 0; i < pMesh->mNumBones; i++) {
unsigned int BoneIndex = 0;
string BoneName(pMesh->mBones[i]->mName.data);
if (m_BoneMapping.find(BoneName) == m_BoneMapping.end()) {
// Allocate an index for a new bone
BoneIndex = m_NumBones;
m_NumBones++;
BoneInfo bi;
m_BoneInfo.push_back(bi);
m_BoneInfo[BoneIndex].BoneOffset = pMesh->mBones[i]->mOffsetMatrix;
m_BoneMapping[BoneName] = BoneIndex;
}
else {
BoneIndex = m_BoneMapping[BoneName];
}
for (unsigned int j = 0; j < pMesh->mBones[i]->mNumWeights; j++) {
unsigned int VertexID = m_Entries[MeshIndex].BaseVertex + pMesh->mBones[i]->mWeights[j].mVertexId;
float Weight = pMesh->mBones[i]->mWeights[j].mWeight;
Bones[VertexID].AddBoneData(BoneIndex, Weight);
}
}
}
bool AnimMesh::InitMaterials(const aiScene* pScene, const string& Filename)
{
// Extract the directory part from the file name
string::size_type SlashIndex = Filename.find_last_of("/");
string Dir;
if (SlashIndex == string::npos) {
Dir = ".";
}
else if (SlashIndex == 0) {
Dir = "/";
}
else {
Dir = Filename.substr(0, SlashIndex);
}
bool Ret = true;
// Initialize the materials
for (unsigned int i = 0; i < pScene->mNumMaterials; i++) {
const aiMaterial* pMaterial = pScene->mMaterials[i];
m_Textures[i] = NULL;
if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
aiString Path;
if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
string p(Path.data);
if (p.substr(0, 2) == ".\\") {
p = p.substr(2, p.size() - 2);
}
string FullPath = Dir + "/" + p;
m_Textures[i] = new AnimatedTexture(GL_TEXTURE_2D, FullPath.c_str());
if (!m_Textures[i]->Load()) {
printf("Error loading texture '%s'\n", FullPath.c_str());
delete m_Textures[i];
m_Textures[i] = NULL;
Ret = false;
}
else {
printf("%d - loaded texture '%s'\n", i, FullPath.c_str());
}
}
}
}
return Ret;
}
void AnimMesh::Renderer(float time, glm::mat4 model, glm::mat4 view, glm::mat4 projection, LightDirectional directionalLight, float baseAmbient, glm::vec3 cameraPos, Shader animationShader)
{
m_pEffect = new Skinning(animationShader, directionalLight, baseAmbient, 0.0f, 0.0f);
vector<Matrix4f> Transforms;
BoneTransform(time, Transforms);
for (GLuint i = 0; i < Transforms.size(); i++)
{
m_pEffect->SetBoneTransform(animationShader, i, Transforms[i]);
}
m_pEffect->SetEyeWorldPos(animationShader, cameraPos);
m_pEffect->SetUp(animationShader, model, view, projection);
glBindVertexArray(m_VAO);
for (unsigned int i = 0; i < m_Entries.size(); i++) {
const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;
assert(MaterialIndex < m_Textures.size());
if (m_Textures[MaterialIndex]) {
m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
}
glDrawElementsBaseVertex(GL_TRIANGLES,
m_Entries[i].NumIndices,
GL_UNSIGNED_INT,
(void*)(sizeof(unsigned int)* m_Entries[i].BaseIndex),
m_Entries[i].BaseVertex);
}
// Make sure the VAO is not changed from the outside
glBindVertexArray(0);
}
unsigned int AnimMesh::FindPosition(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
for (unsigned int i = 0; i < pNodeAnim->mNumPositionKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
unsigned int AnimMesh::FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
assert(pNodeAnim->mNumRotationKeys > 0);
for (unsigned int i = 0; i < pNodeAnim->mNumRotationKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
unsigned int AnimMesh::FindScaling(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
assert(pNodeAnim->mNumScalingKeys > 0);
for (unsigned int i = 0; i < pNodeAnim->mNumScalingKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
void AnimMesh::CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumPositionKeys == 1) {
Out = pNodeAnim->mPositionKeys[0].mValue;
return;
}
unsigned int PositionIndex = FindPosition(AnimationTime, pNodeAnim);
unsigned int NextPositionIndex = (PositionIndex + 1);
assert(NextPositionIndex < pNodeAnim->mNumPositionKeys);
float DeltaTime = (float)(pNodeAnim->mPositionKeys[NextPositionIndex].mTime - pNodeAnim->mPositionKeys[PositionIndex].mTime);
float Factor = (AnimationTime - (float)pNodeAnim->mPositionKeys[PositionIndex].mTime) / DeltaTime;
assert(Factor >= 0.0f && Factor <= 1.0f);
const aiVector3D& Start = pNodeAnim->mPositionKeys[PositionIndex].mValue;
const aiVector3D& End = pNodeAnim->mPositionKeys[NextPositionIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
void AnimMesh::CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
// we need at least two values to interpolate...
if (pNodeAnim->mNumRotationKeys == 1) {
Out = pNodeAnim->mRotationKeys[0].mValue;
return;
}
unsigned int RotationIndex = FindRotation(AnimationTime, pNodeAnim);
unsigned int NextRotationIndex = (RotationIndex + 1);
assert(NextRotationIndex < pNodeAnim->mNumRotationKeys);
float DeltaTime = (float)(pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime);
float Factor = (AnimationTime - (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;
assert(Factor >= 0.0f && Factor <= 1.0f);
const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
Out = Out.Normalize();
}
void AnimMesh::CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumScalingKeys == 1) {
Out = pNodeAnim->mScalingKeys[0].mValue;
return;
}
unsigned int ScalingIndex = FindScaling(AnimationTime, pNodeAnim);
unsigned int NextScalingIndex = (ScalingIndex + 1);
assert(NextScalingIndex < pNodeAnim->mNumScalingKeys);
float DeltaTime = (float)(pNodeAnim->mScalingKeys[NextScalingIndex].mTime - pNodeAnim->mScalingKeys[ScalingIndex].mTime);
float Factor = (AnimationTime - (float)pNodeAnim->mScalingKeys[ScalingIndex].mTime) / DeltaTime;
assert(Factor >= 0.0f && Factor <= 1.0f);
const aiVector3D& Start = pNodeAnim->mScalingKeys[ScalingIndex].mValue;
const aiVector3D& End = pNodeAnim->mScalingKeys[NextScalingIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
void AnimMesh::ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const Matrix4f& ParentTransform)
{
string NodeName(pNode->mName.data);
const aiAnimation* pAnimation = m_pScene->mAnimations[0];
Matrix4f NodeTransformation(pNode->mTransformation);
const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);
if (pNodeAnim) {
// Interpolate scaling and generate scaling transformation matrix
aiVector3D Scaling;
CalcInterpolatedScaling(Scaling, AnimationTime, pNodeAnim);
Matrix4f ScalingM;
ScalingM.InitScaleTransform(Scaling.x, Scaling.y, Scaling.z);
// Interpolate rotation and generate rotation transformation matrix
aiQuaternion RotationQ;
CalcInterpolatedRotation(RotationQ, AnimationTime, pNodeAnim);
Matrix4f RotationM = Matrix4f(RotationQ.GetMatrix());
// Interpolate translation and generate translation transformation matrix
aiVector3D Translation;
CalcInterpolatedPosition(Translation, AnimationTime, pNodeAnim);
Matrix4f TranslationM;
TranslationM.InitTranslationTransform(Translation.x, Translation.y, Translation.z);
// Combine the above transformations
NodeTransformation = TranslationM * RotationM * ScalingM;
}
Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;
if (m_BoneMapping.find(NodeName) != m_BoneMapping.end()) {
unsigned int BoneIndex = m_BoneMapping[NodeName];
m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation * m_BoneInfo[BoneIndex].BoneOffset;
}
for (unsigned int i = 0; i < pNode->mNumChildren; i++) {
ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);
}
}
void AnimMesh::BoneTransform(float TimeInSeconds, vector<Matrix4f>& Transforms)
{
Matrix4f Identity;
Identity.InitIdentity();
float TicksPerSecond = (float)(m_pScene->mAnimations[0]->mTicksPerSecond != 0 ? m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f);
float TimeInTicks = TimeInSeconds * TicksPerSecond;
float AnimationTime = fmod(TimeInTicks, (float)m_pScene->mAnimations[0]->mDuration);
ReadNodeHeirarchy(AnimationTime, m_pScene->mRootNode, Identity);
Transforms.resize(m_NumBones);
for (unsigned int i = 0; i < m_NumBones; i++) {
Transforms[i] = m_BoneInfo[i].FinalTransformation;
}
}
const aiNodeAnim* AnimMesh::FindNodeAnim(const aiAnimation* pAnimation, const string NodeName)
{
for (unsigned int i = 0; i < pAnimation->mNumChannels; i++) {
const aiNodeAnim* pNodeAnim = pAnimation->mChannels[i];
if (string(pNodeAnim->mNodeName.data) == NodeName) {
return pNodeAnim;
}
}
return NULL;
}
Skinning.vert:
#version 330
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in ivec4 BoneIDs;
layout (location = 4) in vec4 Weights;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
const int MAX_BONES = 100;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 gBones[MAX_BONES];
uniform mat4 modelInverseTranspose;
void main()
{
mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];
BoneTransform += gBones[BoneIDs[1]] * Weights[1];
BoneTransform += gBones[BoneIDs[2]] * Weights[2];
BoneTransform += gBones[BoneIDs[3]] * Weights[3];
vec4 PosL = BoneTransform * vec4(Position, 1.0);
gl_Position = model * view * projection * PosL;
TexCoord0 = TexCoord;
vec4 NormalL = BoneTransform * vec4(Normal, 0.0);
Normal0 = (modelInverseTranspose * NormalL).xyz;
WorldPos0 = (model * PosL).xyz;
}
I think you might have gotten the matrix multiplication order in the shader wrong. Quote from another question:
Model maps from an object's local coordinate space into world space, view from world space to camera space, projection from camera to screen.
Try changing
gl_Position = model * view * projection * PosL;
into
gl_Position = projection * view * model * PosL;

Geometry Shader only creates one primitive

I am currently trying to use the geometry shader.
My input is a set of points, for which multiple triangle should be created
This is the geometry shader
#version 330
layout(points) in;
layout(triangle_strip, max_vertices = 3) out;
in vec3[] vPos;
out vec3 oPos;
void main() {
for(int i = -1; i <= 1; i++)
for(int j = -1; j <= 1; j++)
{
for(int k = -1; k <= 1; k++)
{
oPos = vPos[0] + vec3(i,j,k);
EmitVertex();
}
EndPrimitive();
}
}
And this the corresponding C++ code
mMeshGenerator->use();
GLuint query;
glGenQueries(1, &query);
glEnable(GL_RASTERIZER_DISCARD);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mMeshVBO->buffer());
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
glBeginTransformFeedback(GL_TRIANGLES);
mPointsVAO->draw();
glEndTransformFeedback();
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
glFlush();
GLuint primitives;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);
std::cout << "-----" << primitives << "-----" << std::endl;
GLfloat feedback[primitives*3*3];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
for (int i = 0; i < primitives*3*3; i++) {
printf("%f\n", feedback[i]);
}
glDisable(GL_RASTERIZER_DISCARD);
According to the query only one primitive is written.
Output for input point (10,20,30):
-----1-----
9.000000
19.000000
29.000000
9.000000
19.000000
30.000000
9.000000
19.000000
31.000000
Whats wrong with my code that there is only 1 triangle instead of 27?
layout(triangle_strip, max_vertices = 3) out;
this means only 3 vertices (a.k.a. 1 triangle) will be allowed to be emitted; all the rest is discarded