0
votes

I'm trying to use glDrawElements to draw a model loaded from assimp, geometry displays fine but textures dont show up on the model, i just get a black version of the model i've loaded.

Load Model Function

ModelInfo LoadModel(const std::string& modelPath){
printf( "Loading model: %s\n", modelPath.c_str());
//verify that file exists first
std::ifstream fin(modelPath.c_str());
if(!fin.fail()){
    fin.close();
}else{
    throw std::runtime_error("could not open file" + modelPath);
}

Assimp::Importer importer;
const aiScene* scene = importer.ReadFile( modelPath,
        aiProcessPreset_TargetRealtime_Fast |
        //aiProcess_CalcTangentSpace      |
        aiProcess_Triangulate           |
        aiProcess_GenSmoothNormals      |
        aiProcess_FlipUVs
    //aiProcess_JoinIdenticalVertices |
    //aiProcess_SortByPType);
);

if(!scene){
    throw std::runtime_error(importer.GetErrorString());
}
printf("imported %s\n",modelPath.c_str());
fflush(stdout);

std::vector<unsigned int> indices;
std::vector<float> vertices;
std::vector<float> uvs;
std::vector<float> normals;

aiMesh* mesh = scene->mMeshes[0];

int numOfFaces = mesh->mNumFaces;
int numOfIndices = numOfFaces * 3;
indices.resize(numOfIndices);

for (unsigned int i =0; i < mesh->mNumFaces; ++i){
    const aiFace &face = mesh->mFaces[i];
    assert(face.mNumIndices == 3);
    indices[i * 3 + 0] = face.mIndices[0];
    indices[i * 3 + 1] = face.mIndices[1];
    indices[i * 3 + 2] = face.mIndices[2];
}

int numOfVertices = mesh->mNumVertices;
vertices.resize(numOfVertices * 3);
normals.resize(numOfVertices * 3);
uvs.resize(numOfVertices * 2);
for( unsigned int i = 0; i < mesh->mNumVertices; ++i){
    if(mesh->HasPositions()){
        vertices[i * 3 + 0] = mesh->mVertices[i].x;
        vertices[i * 3 + 1] = mesh->mVertices[i].y;
        vertices[i * 3 + 2] = mesh->mVertices[i].z;
        //printf("[ %f, %f, %f]\n",vertices[i*3+0],vertices[i*3+1],vertices[i*3+2]);
    }


    if( mesh->HasNormals()){
        normals[i * 3 + 0] = mesh->mNormals[i].x;
        normals[i * 3 + 1] = mesh->mNormals[i].x;
        normals[i * 3 + 2] = mesh->mNormals[i].x;
    }

    if(mesh->HasTextureCoords(0)){
        uvs[i * 2 + 0] = mesh->mTextureCoords[0][i].x;
        uvs[i * 2 + 1] = mesh->mTextureCoords[0][i].y;
        printf("[ %f, %f]\n",uvs[i*2+0],uvs[i*2+1]);
    }
}

//create voa
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
//create element buffer
GLuint elementBuffer;
glGenBuffers(1, &elementBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

//create vertex buffer
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(aiVector3D), &vertices[0], GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
printf("vertices.size is %lu\n", vertices.size());
printf("uvs.size is %lu\n", uvs.size());

GLuint uvBuffer;
glGenBuffers(1, &uvBuffer);
glBindBuffer(GL_ARRAY_BUFFER, uvBuffer);
glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(aiVector2D), &uvs[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0);

GLuint normalBuffer;
glGenBuffers(1, &normalBuffer);
glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(aiVector3D), &normals[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

ModelInfo retval;
retval.vao    = vao;
retval.index  = elementBuffer;
retval.vertex = vertexBuffer;
retval.uv     = uvBuffer;
retval.normal = normalBuffer;
retval.count  = numOfIndices;

return retval;

}

Short overview of Load Model, loads the model with assimp, then loads the data from the first mesh in the assimp scene into vectors, then creates a vao, vbos for geometry, texture coordinates, normals, and indexes. Data is then loaded into the buffers from the vectors and vertex attributes are set. Once all is loaded a struct containing several GLuints is created to return data.

Render Model function

void render_model(ModelInfo info){
cleanup();
//set program
glUseProgram(modelprogram);

//set uniforms
glm::mat4 view = camera.matrix();
glUniformMatrix4fv(modelcamera, 1, GL_FALSE, &view[0][0]);

glm::mat4 model = glm::mat4();
model = glm::rotate(model, degree, glm::vec3(0,1,1));

glUniformMatrix4fv(modelmodel, 1, GL_FALSE, &model[0][0]);

glBindVertexArray(info.vao);

glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(modeltext, texture);
glDrawElements(GL_TRIANGLES, info.count, GL_UNSIGNED_INT, (void*)0);
}

Brief overview of render model. Set the shader program to handle rendering the model, get matrix for the camera, pass it to the shader, generate a matrix for the model, make it spin, pass it to the shader. Bind the vao of the model to be rendered. Bind a previously loaded text, which is also used on other geometry in the program where it shows up no problem, pass it to the shader. all is set, call glDrawElements starting from the first position in the index buffer of the current vao. Once drawing is complete do clean up to unbind any buffers and arrays

the shaders i'm using for this are very basic vertex shader

#version 140
uniform mat4 camera;
uniform mat4 model;

in vec3 position;
in vec2 uv;

out vec2 fragUV;

void main(){
    //pass variables on to fragment shader
    fragUV   = uv;

    //vertex to draw
    gl_Position = camera * model * vec4(position,1);
}

fragment shader #version 140

uniform sampler2D modeltext;

in vec2 fragUV;

out vec4 finalColor;

void main(){

    finalColor = texture(modeltext, fragUV);
}

All uniforms are loaded correctly with a function that verifies that something was actually loaded, the texture is used and works else where in the program. The model has texture coordinates. The geometry is loading and rendering no problem

1

1 Answers

1
votes

You are passing the wrong value to the sampler uniform.

glBindTexture (GL_TEXTURE_2D, texture);
glUniform1i   (modeltext,     texture);

Sampler uniforms do not take the name (ID) of a texture object, they take the index of the Texture Image Unit that you bound your texture to. In this example, you appear to be using the default Texture Image Unit: GL_TEXTURE0, so the value that your sampler uniform should use is 0.

Because a sampler2D actually expects the Texture Image Unit and not the name of a texture, you never have to change this uniform when you change the bound texture. GLSL will always sample whatever texture is bound to the corresponding Texture Image Unit (assuming they have compatible types / states).

This means that generally you set sampler uniforms once when you initialize your GLSL program and never touch it again. In newer versions of GLSL (4.20) you can even hard-code the Texture Image Unit that a sampler uses in the shader itself, but since you are using GLSL 1.40 you do not have this option unless the extension: GL_ARB_shading_language_420pack is supported.


Consider the following code, which correctly demonstrates how to use sampler uniforms:

glActiveTexture (GL_TEXTURE0);
glBindTexture   (GL_TEXTURE_2D, texture);
glUniform1i     (modeltext,     0);

I have included the redundant call to glActiveTexture (...) merely to demonstrate how the uniform sampler and active texture unit are related... you do not pass the constant GL_TEXTURE0 to a sampler, instead you use the integer index. GL_TEXTURE0 is the default active texture unit.