obj loader not displaying correctly in c++ OpenGL - c++

I have an issue with my wavefront .obj loader where it isn't displaying what I want it to display. I want it to display a cube, but I keep getting this:
Here is my obj file
# cube.obj
#
g cube
v 0.0 0.0 0.0
v 0.0 0.0 1.0
v 0.0 1.0 0.0
v 0.0 1.0 1.0
v 1.0 0.0 0.0
v 1.0 0.0 1.0
v 1.0 1.0 0.0
v 1.0 1.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 -1.0
vn 0.0 1.0 0.0
vn 0.0 -1.0 0.0
vn 1.0 0.0 0.0
vn -1.0 0.0 0.0
f 1//2 7//2 5//2
f 1//2 3//2 7//2
f 1//6 4//6 3//6
f 1//6 2//6 4//6
f 3//3 8//3 7//3
f 3//3 4//3 8//3
f 5//5 7//5 8//5
f 5//5 8//5 6//5
f 1//4 5//4 6//4
f 1//4 6//4 2//4
f 2//1 6//1 8//1
f 2//1 8//1 4//1
I added parts in my code to see if everything is being stored correctly, vertices and normals are stored as GLfloats
Display:
Inputs:
Here is my draw function:
I'm not quite sure about the last parameter in glDrawArrays, it is hard coded because I'm not quite sure about the number of indices.
void Draw(void)
{
glPushMatrix();
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glRotatef( rotate_x, 1.0, 0.0, 0.0 );
glRotatef( rotate_y, 0.0, 1.0, 0.0 );
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, normals);
glVertexPointer(3, GL_FLOAT,0, vertices);
glDrawArrays(GL_TRIANGLES, 0, (14));
glTranslatef(0, 0, -3);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glFlush();
glutSwapBuffers();
glPopMatrix();
}
This is my main aswell:
int main(int argc,char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutCreateWindow("Object Loader!");
glEnable(GL_DEPTH_TEST);
//glEnable(GL_LIGHTING);
//glEnable(GL_LIGHT0);
GLfloat posLight0[] = {0.0f, 0.0f, -1.0f, 0.0f};
GLfloat lightColor[] = {1.0f, 1.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, posLight0);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor);
setUpArrays();
glutDisplayFunc(Draw);
glutSpecialFunc(specialKeys);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
One last thing is that I am not allowed to use glDrawElements or glBegin() direct mode.
Here is more of my program if it is helpful:
Global variables setup at the beginning
GLfloat* vertices;
GLfloat* normals;
GLint* faces;
int amountOfFaces;
double rotate_y=0;
double rotate_x=0;
Loader* loader = new Loader();
Setting up the arrays for the vertices, normals and indices:
void setUpArrays()
{
loader->loadObject("cube.obj");
vector<GLfloat> temp = loader->getVerticies();
vertices = new GLfloat[temp.size()];
int tempSize = 0;
cout << "vectors: ";
for(vector<GLfloat>::const_iterator i = temp.begin(); i != temp.end(); ++i)
{
vertices[tempSize] = *i;
cout << vertices[tempSize] << ", ";
tempSize++;
}
cout << endl;
vector<GLfloat> tempNormal = loader->getNormals();
normals = new GLfloat[tempNormal.size()];
tempSize = 0;
cout << "normals: ";
for(vector<GLfloat>::const_iterator i = tempNormal.begin(); i != tempNormal.end(); ++i)
{
normals[tempSize] = *i;
cout << normals[tempSize] << ", ";
tempSize++;
}
amountOfFaces = tempSize;
cout << endl;
vector<GLint> tempIndices = loader->getFaces();
faces = new GLint[tempIndices.size()];
tempSize = 0;
cout << "Indices: ";
for(vector<GLint>::const_iterator i = tempIndices.begin(); i != tempIndices.end(); ++i)
{
faces[tempSize] = *i;
cout << faces[tempSize] << ", ";
tempSize++;
}
cout << endl;
amountOfFaces += tempSize;
}

I'm baffled as to why but the OBJ format gives vertex attribute indices starting at 1, and not 0. I.e. f 1//2 refers to the first vertex and second normal. Your debug output doesn't show a zero in indices so I'm guessing the loader hasn't accounted for this.
A valid vertex index starts from 1 and matches the corresponding vertex elements of a previously defined vertex list. Each face can contain three or more vertices. [wikipedia]
Hopefully the fix is as easy as: faces[tempSize] = *i - 1;
[EDIT]
Your draw call specifies 14 vertices, but you have 12 triangles so should be drawing 36. amountOfFaces sounds like it should be tempIndices.size()/3.
Your vectors should be references to avoid a copy, and it will be way faster to populate faces etc. with a single memcpy. E.g: memcpy(faces, &tempIndices.front(), sizeof(GLint) * tempIndices.size())
[EDIT]
Oh, right, of course. Now I feel silly for not noticing. Faces indexes the vertices, so they can be reused (saves memory and extra processing). Instead of glDrawArrays you want this:
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, faces); //replate 36 for things you draw that aren't cubes
If you wanted to use glDrawArrays, you would have to rearrange your vertex arrays so each vertex appears in the order given by faces, including all the duplicates. For example your new position array would be [0,0,0, 1,1,0 1,0,0 ...] being vectors 0,6,4 forming the first triangle. std::vectors make this kind of rearranging easy to code.
One last thing. The normals array isn't as big as the position array, but OpenGL doesn't support mixed indexing. So you'll have to duplicate some of the normals so every vertex has both a position and normal.

Related

OpenGL is not culling faces properly when drawing OBJ model

I'm trying to render a Teapot model from an OBJ file. I'm using the Fixed Function rendering pipeline, and I cannot change to the Programmable Pipeline. I would like to have some basic lighting and materials applied to the scene as well, so my teapot has a green shiny material applied to it. However, when I rotate the teapot around the Y-Axis, I can clearly see through to the back side of the teapot.
Here's what I've tried so far:
Changing the way OpenGL culls the faces (GL_CCW, GL_CW, GL_FRONT, GL_BACK) and none produce the correct results.
Changing which way OpenGL calculates the front of the faces (GL_FRONT, GL_CCW, GL_BACK, GL_CW) and none produce the correct results.
Testing the OBJ file to ensure that it orders its vertices correctly. When I drag the file into https://3dviewer.net/ it shows the correct Teapot that is not see-through.
Changing the lighting to see if that does anything at all. Changing the lighting does not stop the teapot from being see-through in some cases.
Disabling GL_BLEND. This did nothing
Here is what I currently have enabled:
glLightfv(GL_LIGHT0, GL_AMBIENT, light0Color);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0DiffColor);
glLightfv(GL_LIGHT0, GL_SPECULAR, light0SpecColor);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientIntensity);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_NORMALIZE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_CCW);
glFrontFace(GL_CCW);
Here are the material properties:
float amb[4] = {0.0215, 0.1745, 0.0215, 1.0};
float diff[4] = {0.07568, 0.61424, 0.07568, 1.0};
float spec[4] = {0.633, 0.727811, 0.633, 1.0};
float shininess = 0.6 * 128;
glMaterialfv(GL_FRONT, GL_AMBIENT, amb);
glMaterialfv(GL_FRONT, GL_DIFFUSE, diff);
glMaterialfv(GL_FRONT, GL_SPECULAR, spec);
glMaterialf(GL_FRONT, GL_SHININESS, shininess);
Here is the rendering code:
glClearColor(0.0, 0.0, 0.0, 1.0);
glClearDepth(1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0, 0, -150);
glRotatef(r, 0.0, 1.0, 0.0);
glScalef(0.5, 0.5, 0.5);
r += 0.5;
m.draw(0, 0, 0);
I'm not sure if it's the cause of the problem, but I've included the model loading code below just in case it's relevant:
while(std::getline(stream, line))
{
if (line[0] == 'v' && line[1] == 'n') // If we see a vertex normal in the OBJ file
{
line = line.substr(3, line.size() - 3); // Removes the 'vn ' from the line
std::stringstream ss(line);
glm::vec3 normal;
ss >> normal.x >> normal.y >> normal.z;
tempNormalData.push_back(normal);
}
if (line[0] == 'v') // If we see a vertex on this line of the OBJ file
{
line = line.substr(2, line.size() - 2); // Removes the 'v ' from the line
std::stringstream ss(line);
glm::vec3 position;
ss >> position.x >> position.y >> position.z;
tempVertData.push_back(position);
}
if (line[0] == 'f') // If we see a face in the OBJ file
{
line = line.substr(2, line.size() - 2); // Removes the 'f ' from the line
std::stringstream ss(line);
glm::vec3 faceData;
ss >> faceData.x >> faceData.y >> faceData.z;
tempFaceData.push_back(faceData);
}
}
if (tempVertData.size() != tempNormalData.size() && tempNormalData.size() > 0)
{
std::cout << "Not the same number of normals as vertices" << std::endl;
}
else
{
for (int i = 0; i < (int)tempVertData.size(); i++)
{
Vertex v;
v.setPosition(tempVertData[i]);
v.setNormal(tempNormalData[i]);
vertices.push_back(v);
}
for (int i = 0; i < tempFaceData.size(); i++)
{
Vertex v1 = vertices[tempFaceData[i].x - 1];
Vertex v2 = vertices[tempFaceData[i].y - 1];
Vertex v3 = vertices[tempFaceData[i].z - 1];
Face face(v1, v2, v3);
faces.push_back(face);
}
}
}
Lastly, when I draw the faces I just loop through the faces list and call the draw function on the face object. The face draw function just wraps a glBegin(GL_TRIANGLES) and a glEnd() call:
for (int i = 0; i < (int)faces.size(); i++)
{
auto& f = faces[i];
f.draw(position);
}
Face draw function:
glBegin(GL_TRIANGLES);
glVertex3f(position.x + v1.getPosition().x, position.y + v1.getPosition().y, position.z + v1.getPosition().z);
glNormal3f(v1.getNormal().x, v1.getNormal().y, v1.getNormal().z);
glVertex3f(position.x + v2.getPosition().x, position.y + v2.getPosition().y, position.z + v2.getPosition().z);
glNormal3f(v2.getNormal().x, v2.getNormal().y, v2.getNormal().z);
glVertex3f(position.x + v3.getPosition().x, position.y + v3.getPosition().y, position.z + v3.getPosition().z);
glNormal3f(v3.getNormal().x, v3.getNormal().y, v3.getNormal().z);
glEnd();
I don't really want to implement my own Z-Buffer culling algorithm, and I'm hoping that there is a really easy fix to my problem that I'm just missing.
SOLUTION (thanks to Genpfault)
I had not requested a depth buffer from OpenGL. I'm using Qt as my windowing API, so I had to request it from my format object as follows:
format.setDepthBufferSize(32);
This requests a depth buffer of 32 bits, which fixed the issue.
in order to make face culling working you need to:
define winding rule
glFrontFace(GL_CCW); // or GL_CW depends on your model and coordinate systems
set which faces to skip
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); // or GL_FRONT depends on what you want to achieve
As you can see this is where you have a bug in your code as you are calling this with wrong parameter most likely causing new glError entries.
in case of concave mesh you need also depth buffer
glEnable(GL_DEPTH_TEST);
However your OpenGL context must have allocated depth buffer bits in its pixelformat during context creation. The most safe values are 16 and 24 bits however any decent nowadays gfx card can handle 32bit too. If you need more then you need to use FBO.
mesh with consistent polygon winding
wavefront obj files are notorious for having inconsistent winding so in case you see some triangles flipped its most likely bug in the mesh file itself.
This can be remedied either by using some 3D tool or by detecting wrong triangles and reverse their vertexes and flipping normal.
Also your rendering code glBegin/glEnd is written in very inefficient way:
glVertex3f(position.x + v1.getPosition().x, position.y + v1.getPosition().y, position.z + v1.getPosition().z);
glNormal3f(v1.getNormal().x, v1.getNormal().y, v1.getNormal().z);
for each of the component/operand you call some class member function and even making arithmetics ... The position can be done with simple glTranslate in actual GL_MODELVIEW matrix and if you got some 3D vector class try to access its components as pointer and use glVertex3fv and glNormal3fv instead that would be much much faster.

Opengl obj object loader with textures

I am using an object loader to load an obj model that I exported from blender. I can load the model but none of the textures are loaded and I don't know why. Should they be loaded when I render the model in the main class or do I need to load the textures as well as rendering the model?
objloader.cpp
/************************************************************
*Loads obj file - limited to vertices, faces, normals, texture maps
*loads to object structure as defined in .h file
************************************************************/
#include <stdio.h>
#include <iostream>
#include <io.h>
#include <stdlib.h>
#include "glut.h"
#include "objloader.h"
using namespace std;
void object_type::render()
{
glBegin(GL_TRIANGLES); // glBegin and glEnd delimit the vertices that define a primitive (in our case triangles)
for (int j=0;j<polygons_qty;j++)
{
//----------------- FIRST VERTEX -----------------
//Normal coordinates of the first vertex
glNormal3f( normcoord[ polygon[j].n[0] - 1 ].i,
normcoord[ polygon[j].n[0] - 1 ].j,
normcoord[ polygon[j].n[0] - 1 ].k);
// Texture coordinates of the first vertex
glTexCoord2f( mapcoord[ polygon[j].t[0] - 1 ].u,
mapcoord[ polygon[j].t[0] - 1 ].v);
// Coordinates of the first vertex
glVertex3f( vertex[ polygon[j].v[0] - 1].x,
vertex[ polygon[j].v[0] - 1].y,
vertex[ polygon[j].v[0] - 1].z);
//----------------- SECOND VERTEX -----------------
//Normal coordinates of the first vertex
glNormal3f( normcoord[ polygon[j].n[1] - 1 ].i,
normcoord[ polygon[j].n[1] - 1 ].j,
normcoord[ polygon[j].n[1] - 1 ].k);
// Texture coordinates of the first vertex
glTexCoord2f( mapcoord[ polygon[j].t[1] - 1 ].u,
mapcoord[ polygon[j].t[1] - 1 ].v);
// Coordinates of the first vertex
glVertex3f( vertex[ polygon[j].v[1] - 1].x,
vertex[ polygon[j].v[1] - 1].y,
vertex[ polygon[j].v[1] - 1].z);
//----------------- THIRD VERTEX -----------------
//Normal coordinates of the first vertex
glNormal3f( normcoord[ polygon[j].n[2] - 1 ].i,
normcoord[ polygon[j].n[2] - 1 ].j,
normcoord[ polygon[j].n[2] - 1 ].k);
// Texture coordinates of the first vertex
glTexCoord2f( mapcoord[ polygon[j].t[2] - 1 ].u,
mapcoord[ polygon[j].t[2] - 1 ].v);
// Coordinates of the first vertex
glVertex3f( vertex[ polygon[j].v[2] - 1].x,
vertex[ polygon[j].v[2] - 1].y,
vertex[ polygon[j].v[2] - 1].z);
}
glEnd();
}
/*
vertex_type vertex[MAX_VERTICES];
mapcoord_type mapcoord[MAX_VERTICES];
normcoord_type normcoord[MAX_NORMALS];
polygon_type polygon[MAX_POLYGONS];
int id_texture
*/
int object_type::objdatadisplay()
{
int i;
printf("VERTICES: %d\n",vertices_qty);
for (i =0;i<vertices_qty;i++)
{
printf("%f %f %f\n",vertex[i].x,vertex[i].y,vertex[i].z);
}
printf("NORMALS: %d\n",normcoord_qty);
for (i =0;i<normcoord_qty;i++)
{
printf("%f %f %f\n",normcoord[i].i,normcoord[i].j,normcoord[i].k);
}
printf("MAP COORDS: %d\n",mapcoord_qty);
for (i =0;i<mapcoord_qty;i++)
{
printf("%f %f\n",mapcoord[i].u,mapcoord[i].v);
}
printf("POLYGONS: %d\n",polygons_qty);
for (i=0;i<polygons_qty;i++) //for each vertex of polygon (triangle)
{
for (int j = 0;j<3;j++)
{
printf("%d::%d/%d/%d\n",i,polygon[i].v[j],polygon[i].t[j],polygon[i].n[j]);
}
}
return 1;
}
int object_type::objloader(char *p_filename)
{
int ivertex=0; //Index variable
int inormal =0;
int ipolygon=0;
int imap=0;
char string[256];
FILE *l_file; //File pointer
char l_char; //Char variable
unsigned short l_face_flags; //Flag that stores some face information
if ((l_file=fopen (p_filename, "rt"))== NULL) return 0; //Open the file
while (!feof(l_file)) //Loop to scan the whole file
{
fscanf(l_file,"%c",&l_char);
if(l_char=='\n')//read char if'/n' -skip to next and read
fscanf(l_file,"%c",&l_char);
switch (l_char) //parse
{
default: fgets(string,256,l_file);
break;
case 'v': //a vertex or a normal or a text co-ord
fscanf(l_file,"%c",&l_char);
switch (l_char)
{
case ' ': //a vertex -expect and so read 3 floats next
fscanf(l_file,"%f %f %f",&vertex[ivertex].x, &vertex[ivertex].y,&vertex[ivertex].z);
ivertex++;
break;
case 'n': //a normal -expect and so read 3 floats next
fscanf(l_file,"%f %f %f",&normcoord[inormal].i, &normcoord[inormal].j,&normcoord[inormal].k);
inormal++;
break;
case 't': //a texture map coord-expect and so read 2 floats next
fscanf(l_file,"%f %f",&mapcoord[imap].u, &mapcoord[imap].v);
imap++;
break;
} //end switch
break;
case 'f': //a face read next assume format is -> f 1/1/1 2/2/2 3/3/3
for (int i=0;i<3;i++) //for each vertex of polygon (triangle)
{
fscanf(l_file,"%c",&l_char); //read space char - ignore this
fscanf(l_file,"%d",&polygon[ipolygon].v[i]); //read vertex.
fscanf(l_file,"%c",&l_char); //read space char - ignore this
fscanf(l_file,"%d",&polygon[ipolygon].t[i]); //read text coord.
fscanf(l_file,"%c",&l_char); //read space char - ignore this
fscanf(l_file,"%d",&polygon[ipolygon].n[i]); //read normal.
}
ipolygon++;
break;
} //end switch
}
fclose (l_file); // Closes the file stream
vertices_qty = ivertex;
polygons_qty = ipolygon;
mapcoord_qty = imap;
normcoord_qty = inormal;
return 1; //if successful
}
objloader.h
#ifndef OBJLOAD
#define OBJLOAD
/************************************************
*Loads obj file - limited to vertices, faces, normals, texture maps
*loads to object structure as defined in .h file
****************************************************/
#define MAX_VERTICES 8000 // Max number of vertices (for each object)
#define MAX_POLYGONS 8000 // Max number of polygons (for each object)
#define MAX_NORMALS 8000 // Max number of polygons (for each object)
// Our vertex type
typedef struct{
float x,y,z;
}vertex_type;
// Our normal type
typedef struct{
float i,j,k;
}normcoord_type;
// The polygon (triangle), 3 numbers that aim 3 vertices
typedef struct{
int v[3],t[3],n[3];
}polygon_type;
// The mapcoord type, 2 texture coordinates for each vertex
typedef struct{
float u,v;
}mapcoord_type;
// The object type
class object_type{
public:
int id_texture;
object_type(){}
~object_type(){}
int objloader(char *p_filename);
int objdatadisplay();
void render();
private:
char name[20];
int vertices_qty;
int polygons_qty;
int mapcoord_qty;
int normcoord_qty;
vertex_type vertex[MAX_VERTICES];
mapcoord_type mapcoord[MAX_VERTICES];
normcoord_type normcoord[MAX_NORMALS];
polygon_type polygon[MAX_POLYGONS];
};
#endif
main.cpp
#include "objloader.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glew.h>
#ifdef __WIN32__
#include <windows.h>
#endif
#include "glut.h" //glut has all ogl relevant .h files included
int screen_width=800;
int screen_height=600;
//angle of rotation
float xpos = 0, ypos = 0, zpos = 0, xrot = 0, yrot = 0, angle=0.0;
float cRadius = 10.0f; // our radius distance from our character
float lastx, lasty;
object_type *objarray[2]; //objects container for our world. Used throughout so global
//Lights settings
GLfloat light_ambient[]= { 0.1f, 0.1f, 0.1f, 0.1f };
GLfloat light_diffuse[]= { 1.0f, 1.0f, 1.0f, 0.0f };
GLfloat light_specular[]= { 1.0f, 1.0f, 1.0f, 0.0f };
GLfloat light_position[]= { 100.0f, 0.0f, -10.0f, 1.0f };
//Materials settings
GLfloat mat_ambient[]= { 0.5f, 0.5f, 0.0f, 0.0f };
GLfloat mat_diffuse[]= { 0.5f, 0.5f, 0.0f, 0.0f };
GLfloat mat_specular[]= { 1.0f, 1.0f, 1.0f, 0.0f };
GLfloat mat_shininess[]= { 1.0f };
/************************************
*
* SUBROUTINE init(void)
*
* Used to initialize OpenGL and to setup our world
*
************************************/
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0); // Clear background color to black
// Viewport transformation
glViewport(0,0,screen_width,screen_height);
// Projection transformation
glMatrixMode(GL_PROJECTION); // Specifies which matrix stack is the target for matrix operations
glLoadIdentity(); // We initialize the projection matrix as identity
gluPerspective(45.0f,(GLfloat)screen_width/(GLfloat)screen_height,5.0f,10000.0f);
//Lights initialization and activation
glLightfv (GL_LIGHT1, GL_AMBIENT, light_ambient);
glLightfv (GL_LIGHT1, GL_DIFFUSE, light_diffuse);
glLightfv (GL_LIGHT1, GL_DIFFUSE, light_specular);
glLightfv (GL_LIGHT1, GL_POSITION, light_position);
glEnable (GL_LIGHT1);
glEnable (GL_LIGHTING);
//Materials initialization and activation
glMaterialfv (GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv (GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv (GL_FRONT, GL_DIFFUSE, mat_specular);
glMaterialfv (GL_FRONT, GL_POSITION, mat_shininess);
//Other initializations
glShadeModel(GL_SMOOTH); // Type of shading for the polygons
glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Texture mapping perspective correction
glEnable(GL_TEXTURE_2D); // Texture mapping ON
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); // Polygon rasterization mode (polygon filled)
glEnable(GL_CULL_FACE); // Enable the back face culling
glEnable(GL_DEPTH_TEST); // Enable the depth test
glTranslatef(0.0f, 0.0f, -cRadius);
glRotatef(xrot,1.0,0.0,0.0);
angle++; //increase the angle
for (int i=0;i<2;i++)
{
printf("*************\n");
objarray[i] = new (object_type);
objarray[i]->objloader("C:/3dModels/Museum.obj");
objarray[i]->objdatadisplay();
}
}
/**********************************************************
*
* SUBROUTINE resize(int p_width, int p_height)
*
* This routine must be called everytime we resize our window.
*
* Input parameters: p_width = width in pixels of our viewport
* p_height = height in pixels of our viewport
*
*********************************************************/
void resize (int p_width, int p_height)
{
if (screen_width==0 && screen_height==0) exit(0);
screen_width=p_width; // We obtain the new screen width values and store it
screen_height=p_height; // Height value
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We clear both the color and the depth buffer so to draw the next frame
glViewport(0,0,screen_width,screen_height); // Viewport transformation
glMatrixMode(GL_PROJECTION); // Projection transformation
glLoadIdentity(); // We initialize the projection matrix as identity
gluPerspective(45.0f,(GLfloat)screen_width/(GLfloat)screen_height,5.0f,10000.0f);
glutPostRedisplay (); // This command redraw the scene (it calls the same routine of glutDisplayFunc)
}
/**********************************************************
*
* SUBROUTINE keyboard(void)
*
* Subroutine to handle keyboard input
*
*********************************************************/
void keyboard (unsigned char key, int x, int y) {
if (key=='q')
{
xrot += 1;
if (xrot >360) xrot -= 360;
}
if (key=='z')
{
xrot -= 1;
if (xrot < -360) xrot += 360;
}
if (key=='w')
{
float xrotrad, yrotrad;
yrotrad = (yrot / 180 * 3.141592654f);
xrotrad = (xrot / 180 * 3.141592654f);
xpos += float(sin(yrotrad));
zpos -= float(cos(yrotrad));
ypos -= float(sin(xrotrad));
}
if (key=='s')
{
float xrotrad, yrotrad;
yrotrad = (yrot / 180 * 3.141592654f);
xrotrad = (xrot / 180 * 3.141592654f);
xpos -= float(sin(yrotrad));
zpos += float(cos(yrotrad));
ypos += float(sin(xrotrad));
}
if (key=='d')
{
float yrotrad;
yrotrad = (yrot / 180 * 3.141592654f);
xpos += float(cos(yrotrad)) * 0.2;
zpos += float(sin(yrotrad)) * 0.2;
}
if (key=='a')
{
float yrotrad;
yrotrad = (yrot / 180 * 3.141592654f);
xpos -= float(cos(yrotrad)) * 0.2;
zpos -= float(sin(yrotrad)) * 0.2;
}
if (key==27)
{
exit(0);
}
}
/**********************************************************
*
* SUBROUTINE mouseMovement(void)
*
* Subroutine to handle mouse input
*
*********************************************************/
void mouseMovement(int x, int y) {
int diffx=x-lastx; //check the difference between the current x and the last x position
int diffy=y-lasty; //check the difference between the current y and the last y position
lastx=x; //set lastx to the current x position
lasty=y; //set lasty to the current y position
xrot += (float) diffy; //set the xrot to xrot with the addition of the difference in the y position
yrot += (float) diffx; //set the xrot to yrot with the addition of the difference in the x position
}
/**********************************************************
*
* SUBROUTINE display(void)
*
* This is our main rendering subroutine, called each frame
*
*********************************************************/
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // This clear the background color to dark blue
glMatrixMode(GL_MODELVIEW); // Modeling transformation
glPushMatrix();
glLoadIdentity(); // Initialize the model matrix as identity
glTranslatef(0.0f, 0.0f, -cRadius); // We move the object forward (the model matrix is multiplied by the translation matrix)
glRotatef(xrot,1.0,0.0,0.0); // Rotations of the object (the model matrix is multiplied by the rotation matrices)
glRotatef(yrot,0.0,1.0,0.0);
glTranslated(-xpos,0.0f,-zpos); //translate the screen to the position of our camera
if (objarray[0]->id_texture!=-1)
{
glBindTexture(GL_TEXTURE_2D, objarray[0]->id_texture); // We set the active texture
glEnable(GL_TEXTURE_2D); // Texture mapping ON
printf("Txt map ON");
}
else
glDisable(GL_TEXTURE_2D); // Texture mapping OFF
objarray[0]->render();
glPopMatrix();
glPushMatrix();
glTranslatef(5.0,0.0,-20.0);
glFlush(); // This force the execution of OpenGL commands
glutSwapBuffers(); // In double buffered mode we invert the positions of the visible buffer and the writing buffer
}
/**********************************************************
*
* The main routine
*
*********************************************************/
int main(int argc, char **argv)
{
// We use the GLUT utility to initialize the window, to handle the input and to interact with the windows system
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(screen_width,screen_height);
glutInitWindowPosition(0,0);
glutCreateWindow("Demo 1: To exit press ESC");
glutDisplayFunc(display);
glutIdleFunc(display);
glutReshapeFunc (resize);
glutPassiveMotionFunc(mouseMovement); //check for mouse movement
glutKeyboardFunc (keyboard);
init();
glutMainLoop();
return(0);
}
Example obj from blender, cube.obj:
# Blender v2.66 (sub 1) OBJ File: 'cube.blend'
# www.blender.org
mtllib cube.mtl
g Cube
v 1.000000 0.665869 -1.000000
v 1.000000 0.665869 1.000000
v -1.000000 0.665869 1.000000
v -1.000000 0.665869 -1.000000
v 1.000000 2.665869 -0.999999
v 0.999999 2.665869 1.000001
v -1.000000 2.665869 1.000000
v -1.000000 2.665869 -1.000000
vt 0.000000 0.334353
vt 0.332314 0.333333
vt 0.333333 0.665647
vt 1.000000 0.001019
vt 0.998981 0.333333
vt 0.666667 0.332314
vt 1.000000 0.665647
vt 0.667686 0.666667
vt 0.334353 0.666667
vt 0.333333 0.334353
vt 0.666667 0.665647
vt 0.333333 0.332314
vt 0.001020 0.333333
vt 0.332314 0.000000
vt 0.333333 0.001019
vt 0.665647 0.000000
vt 0.001019 0.666667
vt 0.667686 0.000000
vt 0.666667 0.334353
vt 0.665647 0.333333
vt 0.000000 0.001020
vt 0.334353 0.333333
vn 0.000000 -1.000000 0.000000
vn -0.000000 1.000000 0.000000
vn 1.000000 -0.000000 0.000001
vn -0.000000 -0.000000 1.000000
vn -1.000000 -0.000000 -0.000000
vn 0.000000 0.000000 -1.000000
vn 1.000000 0.000000 -0.000000
usemtl Material
s off
f 1/1/1 2/2/1 3/3/1
f 5/4/2 8/5/2 7/6/2
f 1/5/3 5/7/3 6/8/3
f 2/9/4 6/10/4 3/11/4
f 3/12/5 7/13/5 4/14/5
f 5/15/6 1/16/6 4/6/6
f 4/17/1 1/1/1 3/3/1
f 6/18/2 5/4/2 7/6/2
f 2/19/7 1/5/7 6/8/7
f 6/10/4 7/20/4 3/11/4
f 7/13/5 8/21/5 4/14/5
f 8/22/6 5/15/6 4/6/6
Your OBJ loader doesn't even attempt to parse MTL files.
You'll have to add MTL handling yourself.
You'll probably just want to support map_Kd.
Good luck.

Can't get Gouraud Shading in OpenGL to work

I'm trying to get a shape to have some shading due to a light source but I'd like the shape to all be one colour.
My problem is that no matter how hard I try I cannot seem to get any shading on a singular colour model. I've simplified my model to a single triangle to make this example clearer:
#include <GL/glut.h>
#include <math.h>
#include <iostream>
#include<map>
#include<vector>
using namespace std;
/* Verticies for simplified demo */
float vertices[][3] = {
{0.1, 0.1, 0.1},
{0.2, 0.8, 0.3},
{0.3, 0.5, 0.5},
{0.8, 0.2, 0.1},
};
const int VERTICES_SIZE = 4;
/* Polygons for simplified demo */
int polygon[][3] = {
{0, 1, 3},
{0, 2, 1},
{0, 3, 2},
{1, 2, 3},
};
const int POLYGON_SIZE = 4;
/* Average point for looking at */
float av_point[3];
/*
* Holds the normal for each vertex calculated by averaging the
* planar normals that each vertex is connected to.
* It holds {index_of_vertex_in_vertices : normal}
*/
map<int, float*> vertex_normals;
/*
* Calculates average point in list of vertices
* Stores in result
*/
void averagePoint(float vertices[][3], int length, float result[3]) {
for(int i = 0; i < length; i++) {
result[0] += vertices[i][0];
result[1] += vertices[i][1];
result[2] += vertices[i][2];
}
result[0] /= length;
result[1] /= length;
result[2] /= length;
}
/*
* Performs inplace normalisation of vector v
*/
void normalise(float v[3]) {
GLfloat length = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
v[0] /= length;
v[1] /= length;
v[2] /= length;
}
/*
* Performs cross product of vectors u and v and stores
* result in result
* Normalises result.
*/
void crossProduct(float u[], float v[], float result[]) {
result[0] = u[1] * v[2] - u[2] * v[1];
result[1] = u[2] * v[0] - u[0] * v[2];
result[2] = u[0] * v[1] - u[1] * v[0];
}
/*
* Calculates normal for plane
*/
void calculate_normal(int polygon[3], float vertices[][3], float normal[3]) {
GLfloat u[3], v[3];
for (int i = 0; i < 3; i++) {
u[i] = vertices[polygon[0]][i] - vertices[polygon[1]][i];
v[i] = vertices[polygon[2]][i] - vertices[polygon[1]][i];
}
crossProduct(u, v, normal);
normalise(normal);
}
/*
* Populates vertex_normal with it's averaged face normal
*/
void calculate_vertex_normals (map<int, float*> &vertex_normal){
map<int, vector<int> > vertex_to_faces;
map<int, float*> faces_to_normal;
// Loop over faces
for (int i = 0; i < POLYGON_SIZE; i++) {
float* normal = new float[3];
calculate_normal(polygon[i], vertices, normal);
for (int j = 0; j < 3; j++) {
vertex_to_faces[polygon[i][j]].push_back(i);
}
faces_to_normal[i] = normal;
}
vertex_normal.clear();
// Loop over vertices
for (int v = 0; v < VERTICES_SIZE; v++) {
vector<int> faces = vertex_to_faces[v];
int faces_count = 0;
float* normal = new float[3];
for (vector<int>::iterator it = faces.begin(); it != faces.end(); ++it){
normal[0] += faces_to_normal[*it][0];
normal[1] += faces_to_normal[*it][1];
normal[2] += faces_to_normal[*it][2];
faces_count++;
}
normal[0] /= faces_count;
normal[1] /= faces_count;
normal[2] /= faces_count;
vertex_normal[v] = normal;
}
// Delete normal declared in first loop
for (int i = 0; i < POLYGON_SIZE; i++) {
delete faces_to_normal[i];
}
}
/*
* Draws polygons in polygon array.
*/
void draw_polygon() {
for(int i = 0; i < POLYGON_SIZE; i++) {
glBegin(GL_POLYGON);
for(int j = 0; j < 3; j++) {
glNormal3fv(vertex_normals[polygon[i][j]]);
glVertex3fv(vertices[polygon[i][j]]);
}
glEnd();
}
}
/*
* Sets up lighting and material properties
*/
void init()
{
// Calculate average point for looking at
averagePoint(vertices, VERTICES_SIZE, av_point);
// Calculate vertices average normals
calculate_vertex_normals(vertex_normals);
glClearColor (0.0, 0.0, 0.0, 0.0);
cout << "init" << endl;
// Intialise and set lighting parameters
GLfloat light_pos[] = {1.0, 1.0, 1.0, 0.0};
GLfloat light_ka[] = {0.2, 0.2, 0.2, 1.0};
GLfloat light_kd[] = {1.0, 1.0, 1.0, 1.0};
GLfloat light_ks[] = {1.0, 1.0, 1.0, 1.0};
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ka);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_kd);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_ks);
// Initialise and set material parameters
GLfloat material_ka[] = {1.0, 1.0, 1.0, 1.0};
GLfloat material_kd[] = {0.43, 0.47, 0.54, 1.0};
GLfloat material_ks[] = {0.33, 0.33, 0.52, 1.0};
GLfloat material_ke[] = {0.0, 0.0, 0.0, 0.0};
GLfloat material_se[] = {10.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ka);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_kd);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_ks);
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_ke);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_se);
// Smooth shading
glShadeModel(GL_SMOOTH);
// Enable lighting
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
// Enable Z-buffering
glEnable(GL_DEPTH_TEST);
}
/*
* Free's resources
*/
void destroy() {
for (int i = 0; i < VERTICES_SIZE; i++) {
delete vertex_normals[i];
}
}
/*
* Display simple polygon
*/
void display (){
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw_polygon();
glutSwapBuffers();
}
/*
* Sets up camera perspective and view point
* Looks at average point in model.
*/
void reshape (int w, int h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(70, 1.0, 0.1, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0, 0, 1, av_point[0], av_point[1], av_point[2], 0, 0.5, 0);
}
int main (int argc, char **argv)
{
// Initialize graphics window
glutInit(&argc, argv);
glutInitWindowSize(256, 256);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE);
// Initialize OpenGL
init();
glutCreateWindow("Rendering");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop ();
destroy();
return 1;
}
I'm really new to OpenGL so I'm hoping that it's something simple. Since I've remembered to set my normals so I'm not sure what else is going wrong.
The end aim is to render a face with Gouraud shading (and then textures) for my coursework however we've almost been left to figure out OpenGL (1.4 - course requirement) for ourselves, and we aren't allowed to use shaders.
I'm trying to create something similar to this picture (taken from Google):
with my triangle.
shading due to a light source but I'd like the shape to all be one colour.
Aren't those two requirements mutually exclusive? What exactly is your desired outcome. Can you draw a picture what you're imagining? When it comes to implementing, using shaders is a lot easier than juggling with a gazillion of OpenGL state machine switches.
Update
Anyway here's my revised version of OPs code that draws a single triangle subject to Gourad illumination. This code compiles and draw a single triangle with a hint of a specular reflex.
Let's go through what I did. First there's your original setup of the triangle. Nothing special here and nothing changed either (except a few includes) (EDIT) on second look I did a change. The use of a std::map was totally unaccounted for. We know the number of vertices and can just preallocate the normals' memory.
#include <GL/glut.h>
#include <math.h>
// for memcpy
#include <string.h>
#include <map>
#include <vector>
#include <iostream>
using namespace::std;
/* Verticies for simplified demo */
const int VERTICES_SIZE = 4;
float vertices[VERTICES_SIZE][3] = {
{0.1, 0.1, 0.1},
{0.2, 0.8, 0.3},
{0.3, 0.5, 0.5},
{0.8, 0.2, 0.1},
};
// this is now a plain array
float vertex_normals[VERTICES_SIZE][3];
/* Polygons for simplified demo */
const int POLYGON_SIZE = 4;
int polygon[POLYGON_SIZE][3] = {
{0, 1, 3},
{0, 2, 1},
{0, 3, 2},
{1, 2, 3},
};
/* Average point for looking at */
float av_point[3];
/*
* Calculates average point in list of vertices
* Stores in result
*/
void averagePoint(float vertices[][3], int length, float result[3]) {
for(int i = 0; i < length; i++) {
result[0] += vertices[i][0];
result[1] += vertices[i][1];
result[2] += vertices[i][2];
}
result[0] /= length;
result[1] /= length;
result[2] /= length;
}
/*
* Performs inplace normalisation of vector v
*/
void normalise(float v[3]) {
GLfloat length = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
v[0] /= length;
v[1] /= length;
v[2] /= length;
}
/*
* Performs cross product of vectors u and v and stores
* result in result
* Normalises result.
*/
void crossProduct(float u[], float v[], float result[]) {
result[0] = u[1] * v[2] - u[2] * v[1];
result[1] = u[2] * v[0] - u[0] * v[2];
result[2] = u[0] * v[1] - u[1] * v[0];
}
/*
* Calculates normal for plane
*/
void calculate_normal(int polygon[3], float vertices[][3], float normal[3]) {
GLfloat u[3], v[3];
for (int i = 0; i < 3; i++) {
u[i] = vertices[polygon[0]][i] - vertices[polygon[1]][i];
v[i] = vertices[polygon[2]][i] - vertices[polygon[1]][i];
}
crossProduct(u, v, normal);
normalise(normal);
}
EDIT: My next change was here. See the comment
/*
* Populates normals with it's averaged face normal
*
* Passing the normal output buffer as a parameter was a bit
* pointless, as this procedure accesses global variables anyway.
* Either pass everything as parameters or noting at all,
* be consequent. And doing it mixed is pure evil.
*/
void calculate_vertex_normals()
{
// We love RAII, no need for new and delete!
vector< vector<int> > vertex_to_faces(POLYGON_SIZE);
vector< vector<float> > faces_to_normal(POLYGON_SIZE);
// Loop over faces
for (int i = 0; i < POLYGON_SIZE; i++) {
vector<float> normal(3);
calculate_normal(polygon[i], vertices, &normal[0]);
for (int j = 0; j < 3; j++) {
vertex_to_faces[polygon[i][j]].push_back(i);
}
faces_to_normal[i] = normal;
}
// Loop over vertices
for (int v = 0; v < VERTICES_SIZE; v++) {
// avoid a copy here by using a reference
vector<int> &faces = vertex_to_faces[v];
int faces_count = 0;
float normal[3];
for (vector<int>::iterator it = faces.begin(); it != faces.end(); ++it){
normal[0] += faces_to_normal[*it][0];
normal[1] += faces_to_normal[*it][1];
normal[2] += faces_to_normal[*it][2];
faces_count++;
}
// dividing a vector obtained by a number of unit length vectors
// summed by the number of unit vectors summed does not normalize
// it. You need to normalize it properly!
normalise(normal);
// memcpy is really be best choice here
memcpy(vertex_normals[v], normal, sizeof(normal));
}
}
draw_polygon is a rather unhappy name for this function. It draws a triangulated mesh. *EDIT: Also it can be written much nicer by employing vertex arrays (available since 1994 with OpenGL-1.1).
/*
* Draws polygons in polygon array.
*/
void draw_polygon() {
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, &vertices[0][0]);
glNormalPointer(GL_FLOAT, 0, &vertex_normals[0][0]);
glDrawElements(GL_TRIANGLES, POLYGON_SIZE*3, GL_UNSIGNED_INT, polygon);
}
Here it's getting interesting. A common misconception is, that people think OpenGL is "initialized". That's not the case. What you initialize is data. In your case your geometry data
/*
* Sets up lighting and material properties
*/
void init_geometry()
{
// Calculate average point for looking at
averagePoint(vertices, VERTICES_SIZE, av_point);
// Calculate vertices average normals
calculate_vertex_normals(vertex_normals);
}
Here comes the tricky part: OpenGL fixed function illumination is a state as everything else. When you call glLightfv it will set internal parameters based on state when being called. The position is transformed by the modelview when calling this. But without a proper modelview being set up, you can't setup the illumination. Hence I put it into its own function, which we call right after setting up modelview in the drawing function.
void setup_illumination()
{
// Intialise and set lighting parameters
GLfloat light_pos[] = {1.0, 1.0, 1.0, 0.0};
GLfloat light_ka[] = {0.2, 0.2, 0.2, 1.0};
GLfloat light_kd[] = {1.0, 1.0, 1.0, 1.0};
GLfloat light_ks[] = {1.0, 1.0, 1.0, 1.0};
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ka);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_kd);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_ks);
// Initialise and set material parameters
GLfloat material_ka[] = {1.0, 1.0, 1.0, 1.0};
GLfloat material_kd[] = {0.43, 0.47, 0.54, 1.0};
GLfloat material_ks[] = {0.33, 0.33, 0.52, 1.0};
GLfloat material_ke[] = {0.0, 0.0, 0.0, 0.0};
GLfloat material_se[] = {10.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ka);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_kd);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_ks);
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_ke);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_se);
// Smooth shading
glShadeModel(GL_SMOOTH);
// Enable lighting
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
}
For the drawing function a few things were changed. See the comments in the code
/*
* Display simple polygon
*/
void display (void)
{
// float window sizes are usefull for view volume calculations
//
// requesting the window dimensions for each drawing iteration
// is just two function calls. Compare this to the number of function
// calls a typical application will do for the actual rendering
// Trying to optimize away those two calls is a fruitless microoptimization
float const window_width = glutGet(GLUT_WINDOW_WIDTH);
float const window_height = glutGet(GLUT_WINDOW_HEIGHT);
float const window_aspect = window_width / window_height;
// glViewport operates independent of the projection --
// another reason to put it into the drawing code
glViewport(0, 0, window_width, window_height);
glClearDepth(1.);
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// It's a often made mistake to setup projection in the window resize
// handler. Projection is a drawing state, hence should be set in
// the drawing code. Also in most programs you will have multiple
// projections mixed throughout rendering a single frame so there you
// actually **must** set projection in drawing code, otherwise it
// wouldn't work.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(70, window_aspect, 1, 100);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0, 0, -3, av_point[0], av_point[1], av_point[2], 0, 1, 0);
// Fixed function pipeline light position setup calls operate on the current
// modelview matrix, so we must setup the illumination parameters with the
// modelview matrix at least after the view transformation (look-at) applied.
setup_illumination();
// Enable depth testing (z buffering would be enabled/disabled with glDepthMask)
glEnable(GL_DEPTH_TEST);
draw_polygon();
glutSwapBuffers();
}
int main (int argc, char **argv)
{
// Initialize graphics window
glutInit(&argc, argv);
glutInitWindowSize(256, 256);
glutInitDisplayMode (GLUT_DEPTH | GLUT_DOUBLE);
// we actually have to create a window
glutCreateWindow("illuination");
// Initialize geometry
init_geometry();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
You seem to have an array called vertices (which is the correct spelling), and another array called verticies, in several places (calculate_normal is the most obvious example). Is this a mistake? It could be messing up your normal calculations where you take one co-ordinate from the first array but the second co-ordinate from a different, unrelated array.

Faces not being drawn properly

Update
It appears as though my normals are working fine, and it's something with how I'm drawing my faces (only half are being drawn), and I can't figure out why -
If you could take a look at my code from before (shown below)
Original post
I'm currently working on a parser/renderer for .obj file types. I'm running into an issue with displaying the normal vectors:
Without normals:
With normals:
For some reason, I cannot figure out why only half of the normal vectors are having an effect, while the other half act as if there isn't a face at all.
Here is my code for loading in the obj file:
void ObjModel::Load(string filename){
ifstream file(filename.c_str());
if(!file) return;
stringstream ss;
string param, line;
float nparam, cur;
vector<vector<float> > coords;
vector<float> point;
while(getline(file, line)){
ss.clear();
ss.str(line);
ss >> param;
//vertex
if(param == "v"){
for(int i = 0; i < 3; i++){
ss >> nparam;
this->vertices.push_back(nparam);
}
}
//face
else if(param == "f"){
coords.clear();
point.clear();
for(int i = 0; i < 3; i++){
ss >> nparam;
nparam--;
for(int j = 0; j < 3; j++){
cur = this->vertices[nparam * 3 + j];
this->faces.push_back(cur);
point.push_back(cur);
}
coords.push_back(point);
}
point = this->ComputeNormal(coords[0], coords[1], coords[2]);
for(int i = 0; i < 3; i++) this->normals.push_back(point[i]);
}
else continue;
}
}
void ObjModel::Render(){
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, &this->faces[0]);
glNormalPointer(GL_FLOAT, 0, &this->normals[0]);
glDrawArrays(GL_TRIANGLES, 0, this->faces.size() / 3);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
And here is the function to calculate the normal vector:
vector<float> ObjModel::ComputeNormal(vector<float> v1, vector<float> v2, vector<float> v3){
vector<float> vA, vB, vX;
float mag;
vA.push_back(v1[0] - v2[0]);
vA.push_back(v1[1] - v2[1]);
vA.push_back(v1[2] - v2[2]);
vB.push_back(v1[0] - v3[0]);
vB.push_back(v1[1] - v3[1]);
vB.push_back(v1[2] - v3[2]);
vX.push_back(vA[1] * vB[2] - vA[2] * vB[1]);
vX.push_back(vA[2] * vB[0] - vA[0] * vB[2]);
vX.push_back(vA[0] * vB[1] - vA[1] * vB[0]);
mag = sqrt(vX[0] * vX[0] + vX[1] * vX[1] + vX[2] * vX[2]);
for(int i = 0; i < 3; i++) vX[i] /= mag;
return vX;}
I've checked already to make sure that there are an equal number of normal vectors and faces (which there should be, if I'm right).
Thank you in advance! :)
Edit Here is how I am enabling/disabling features of OpenGL:
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
GLfloat amb_light[] = 0.1, 0.1, 0.1, 1.0 ;
GLfloat diffuse[] = {0.6, 0.6, 0.6, 1};
GLfloat specular[] = {0.7, 0.7, 0.3, 1};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb_light);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);
glShadeModel(GL_SMOOTH);
glLightModeli(L_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDisable(GL_CULL_FACE);
Are you using elements? Obj files start counting at 1 but OpenGL starts counting at 0. Just subtract 1 from each element and you should get the correct rendering.
The orientation of normals matters. It looks like the face orientation of your object is not consistens, so the normals of neighbor faces, with similar planes, point in opposite directions.
If you imported that model from a model file, I suggest you don't calculate the normals in your code – you should not do this anyway, since artists may make manual adjustments to the normals to locally fine tune illumination – but store them in the model file as well. All 3D modellers have a function to flip normals into a common orientation. In Blender e.g. this function is reached with the hotkey CTRL + N in edit mode.
for(int i = 0; i < 3; i++) this->normals.push_back(point[i]);
That only provides one normal for each face. You need one normal for each vertex.

Why is my diffuse/specular lighting not working?

I've got an OpenGL program running, and it displays geometry, but it's all "flat," one gray tone, with no diffuse shading or specular reflection:
Pictured are three tori, each made of quad strips. We should see shading, but we don't. What am I doing wrong?
Here is the code where I set the vertices and normals (draw_torus() is called to build a display list):
/* WrapTorus, adapted from
http://www.math.ucsd.edu/~sbuss/MathCG/OpenGLsoft/WrapTorus/WrapTorus.html
by Sam Buss */
/*
* Issue vertex command for segment number j of wrap number i.
* Normal added by Lars Huttar.
* slices1 = numWraps; slices2 = numPerWrap.
*/
void putVert(float i, float j, float slices1, float slices2, float majR, float minR) {
float wrapFrac = j / slices2;
/* phi is rotation about the circle of revolution */
float phi = PI2 * wrapFrac;
/* theta is rotation about the origin, in the xz plane. */
float theta = PI2 * (i + wrapFrac) / slices1;
float y = minR * (float)sin(phi);
float r = majR + minR * (float)cos(phi);
float x = (float)sin(theta) * r;
float z = (float)cos(theta) * r;
/* normal vector points to (x,y,z) from: */
float xb = (float)sin(theta) * majR;
float zb = (float)cos(theta) * majR;
glNormal3f(x - xb, y, z - zb);
glVertex3f(x, y, z);
}
static void draw_torus(int numPerWrap, int numWraps, float majR, float minR) {
int i, j;
glBegin( GL_QUAD_STRIP );
for (i=0; i < numWraps; i++ ) {
for (j=0; j < numPerWrap; j++) {
putVert((float)i, (float)j, (float)numWraps, (float)numPerWrap, majR, minR);
putVert((float)(i + 1), (float)j, (float)numWraps, (float)numPerWrap, majR, minR);
}
}
putVert(0.0, 0.0, (float)numWraps, (float)numPerWrap, majR, minR);
putVert(1.0, 0.0, (float)numWraps, (float)numPerWrap, majR, minR);
glEnd();
}
Is there something wrong with the order of vertices?
Here is the part of the init function where the display list is built:
GLfloat white[4] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat color[4] = { 0.5, 0.6, 0.7, 1.0 };
...
glShadeModel(GL_SMOOTH);
torusDL = glGenLists (1);
glNewList(torusDL, GL_COMPILE);
setMaterial(color, white, 100);
draw_torus(8, 45, 1.0, 0.05);
glEndList();
where setMaterial() just does:
static void setMaterial(const GLfloat color[3], const GLfloat hlite[3], int shininess) {
glColor3fv(color);
glMaterialfv(GL_FRONT, GL_SPECULAR, hlite);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
glMateriali(GL_FRONT, GL_SHININESS, shininess); /* [0,128] */
}
Here is lighting that's also done during initialization:
GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat color[4] = {0.20, 0.20, 0.20, 1.00};
GLfloat spec[4] = {0.30, 0.30, 0.30, 1.00};
GLfloat shiny = 8.0;
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
Here is where the display list gets called, in the draw function:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glLoadIdentity();
glScalef(3.5, 3.5, 3.5);
for (i = 0; i < ac->nrings; i++) {
glScalef(0.8, 0.8, 0.8);
glRotatef(...);
glCallList(torusDL);
}
glFlush();
glPopMatrix();
glXSwapBuffers(dpy, window);
The full .c source file for this "glx hack" is here. In case it makes a difference, this code is in the context of xscreensaver.
As you see, glEnable(GL_NORMALIZE) normalizes the normal vectors after the transformations used for lighting calculations (in fixed-function pipelines). These calculations rely on unit length normals for correct results.
It's worth pointing out that the transforms applied to normal vectors are not the same as the transforms applied to vertex geometry. The OpenGL 2.1 specification describes the transform, as do many other resources. As a vector, a normal has the homogeneous representation: [nx, ny, nz, 0] - a point at 'infinity', and a mathematically elegant way to unify matrix and 4-vector operations in the GL pipeline.
Of course, you could perform this normalization yourself, and it may be more efficient to do so, as your torus geometry is only generated once for a pre-compiled display list:
nx = x - b, ny = y, nz = z - zb;
nl = 1.0f / sqrtf(nx * nx + ny * ny + nz * nz);
glNormal3f(nx * nl, ny * nl, nz * nl);
Be sure to check (nl) for division by zero (or some epsilon), if that's a possibility.