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.
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.EDIT: My next change was here. See the comment
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).
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
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.
For the drawing function a few things were changed. See the comments in the code