OpenGL: How do I render individual RGB values given a cross product that contains the total light?

1.5k views Asked by At

I've been working on my own implementation of a Gouraud style shading model, and I've got the rest of it working pretty much how I want it, but the problem I've run into is it only shows white light. The calc_color function is where this operation is being performed. The Color variable represents the total light of the R, G and B values for that given location. I've been assigning Color to all three arrays just to get the shading implemented properly, but now that that is complete, I'd like to figure out a way to extract the R, G and B values from that total light value.

I've tried several different things, like taking the total light, and taking a percentage of the Light1r, etc. values but it always ends up looking strange or too bright.

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef MAC
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

using namespace std;

//Camera variables
int xangle = -270;
int yangle = 0;

//Control Modes (Rotate mode by default)
int mode = 0;
int lightmode = 0;

//Player Position (Y offset so it would not be straddling the grid)
float cubeX = 0;
float cubeY = 0.5;
float cubeZ = 0;

//Vertex arrays for surface
float surfaceX [12][12];
float surfaceY [12][12];
float surfaceZ [12][12];

//Surface Normal arrays
float Nx[11][11];
float Ny[11][11];
float Nz[11][11];

//Color arrays
float R[11][11];
float G[11][11];
float B[11][11];

//Light position and color variables
float Light1x = 0;
float Light1y = 5;
float Light1z = 0;

float Light1r = 0;
float Light1g = 1;
float Light1b = 0;

float Light2x = -5;
float Light2y = 5;
float Light2z = -5;

float Light2r = 0;
float Light2g = 1;
float Light2b = 0;



//Random number generator
float RandomNumber(float Min, float Max)
{
    return ((float(rand()) / float(RAND_MAX)) * (Max - Min)) + Min;
}

//---------------------------------------
// Initialize surface 
//---------------------------------------
void init_surface()
{
    //Initialize X, select column  
    for (int i = 0; i < 12; i++) 
    {
        //Select row
        //Surface is +1 so the far right normal will be generated correctly     
        for (int j = 0; j < 12; j++)
        {   
            //-5 to compensate for negative coordinate values
            surfaceX[i][j] = i-5;
            //Generate random surface height
            surfaceY[i][j] = RandomNumber(5, 7) - 5;
            //surfaceY[i][j] = 0;
            surfaceZ[i][j] = j-5;
        }
    }
}

void define_normals()
{
    //Define surface normals
    for (int i = 0; i < 11; i++)
    {
        for (int j = 0; j < 11; j++)
        {
            //Get two tangent vectors
            float Ix = surfaceX[i+1][j] - surfaceX[i][j];
            float Iy = surfaceY[i+1][j] - surfaceY[i][j];
            float Iz = surfaceZ[i+1][j] - surfaceZ[i][j];
            float Jx = surfaceX[i][j+1] - surfaceX[i][j];
            float Jy = surfaceY[i][j+1] - surfaceY[i][j];
            float Jz = surfaceZ[i][j+1] - surfaceZ[i][j];

            //Do cross product, inverted for upward normals
            Nx[i][j] = - Iy * Jz + Iz * Jy;
            Ny[i][j] = - Iz * Jx + Ix * Jz;
            Nz[i][j] = - Ix * Jy + Iy * Jx;

            //Original vectors
            //Nx[i][j] = Iy * Jz - Iz * Jy;
            //Ny[i][j] = Iz * Jx - Ix * Jz;
            //Nz[i][j] = Ix * Jy - Iy * Jx;

            float length = sqrt( 
                Nx[i][j] * Nx[i][j] + 
                Ny[i][j] * Ny[i][j] + 
                Nz[i][j] * Nz[i][j]);
            if (length > 0)
            {
                Nx[i][j] /= length;
                Ny[j][j] /= length;
                Nz[i][j] /= length;
            }
        }   
    } 
}

void calc_color()
{
    for (int i = 0; i < 10; i++)
    {           
        for (int j = 0; j < 10; j++)
        {
            //Calculate light vector
            //Light position, hardcoded for now 0,1,1
            float Lx = Light1x - surfaceX[i][j]; 
            float Ly = Light1y - surfaceY[i][j];
            float Lz = Light1z - surfaceZ[i][j];

            float length = sqrt(Lx * Lx + Ly * Ly + Lz * Lz);
            if (length > 0)
            {
                Lx /= length;
                Ly /= length;
                Lz /= length;
                        }

            //std::cout << "Lx: " << Lx << std::endl;   
            //std::cout << "Ly: " << Ly << std::endl;
            //std::cout << "Lz: " << Lz << std::endl;



            //Grab surface normals
            //These are Nx,Ny,Nz due to compiler issues
            float Na = Nx[i][j];
            float Nb = Ny[i][j];
            float Nc = Nz[i][j];

            //std::cout << "Na: " << Na << std::endl;   
            //std::cout << "Nb: " << Nb << std::endl;   
            //std::cout << "Nc: " << Nc << std::endl;

            //Do cross product
            float Color = (Na * Lx) + (Nb * Ly) + (Nc * Lz);
            std::cout << "Color: " << Color << std::endl;

            //if (Color > 0)
            //{
            //  Color = Color / 100;
            //}
            //Percent of light color
            //float Ramt = (Light1r/2) / Color;
            //float Gamt = (Light1g/2) / Color;
            //float Bamt = (Light1b/2) / Color;
            //R[i][j] = Ramt * Color;
            //G[i][j] = Gamt * Color;
            //B[i][j] = Bamt * Color;

            R[i][j] = Color;
            G[i][j] = Color;
            B[i][j] = Color;    
        }
    }
}

//---------------------------------------
// Init function for OpenGL
//---------------------------------------
void init()
{
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //Viewing Window Modified
    glOrtho(-7.0, 7.0, -7.0, 7.0, -7.0, 7.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //Rotates camera
    //glRotatef(30.0, 1.0, 1.0, 1.0);
    glEnable(GL_DEPTH_TEST);

    //Project 3 code
    init_surface();
    define_normals();

    //Shading code
    // glShadeModel(GL_SMOOTH);
    // glEnable(GL_NORMALIZE);

    //X,Y,Z - R,G,B
    // init_light(GL_LIGHT1, Light1x, Light1y, Light1z, Light1r, Light1g, Light1b);
    // init_light(GL_LIGHT2, Light2x, Light2y, Light2z, Light2r, Light2g, Light2b);
    //init_light(GL_LIGHT2, 0, 1, 0, 0.5, 0.5, 0.5);


}

void keyboard(unsigned char key, int x, int y)
{

///TODO: allow user to change color of light

    //Controls
    //Toggle Mode  
    if (key == 'q')
    {
        if(mode == 0)
        {       
            mode = 1;
            std::cout << "Switched to Light mode (" << mode << ")" << std::endl;    
        }
        else if(mode == 1)
        {   
            mode = 0;
            std::cout << "Switched to Rotate mode (" << mode << ")" << std::endl;
        }
    }
    //Toggle light control
    else if (key == 'e' && mode == 1)
    {
        if(lightmode == 0)
        {       
            lightmode = 1;
            std::cout << "Switched to controlling light 2 (" << lightmode << ")" << std::endl;  
        }

        else if(lightmode == 1)
        {       
            lightmode = 0;
            std::cout << "Switched to controlling light 1 (" << lightmode << ")" << std::endl;  
        }
    }

    ////Rotate Camera (mode 0)
    //Up & Down
    else if (key == 's' && mode == 0)
        xangle += 5;
    else if (key == 'w' && mode == 0)
    xangle -= 5;

    //Left & Right
    else if (key == 'a' && mode == 0) 
    yangle -= 5;
    else if (key == 'd' && mode == 0) 
    yangle += 5;

    ////Move Light (mode 1)
    //Forward & Back
    else if (key == 'w' && mode == 1) 
    {
        if (lightmode == 0) 
        {       
            Light1z = Light1z - 1;
            //init_surface();           
            //define_normals();         
            //calc_color();
            //glutPostRedisplay();

        }
        else if (lightmode == 1)
        Light2z = Light2z - 1;
        //init_surface();

    }

    else if (key == 's' && mode == 1)
    {
        if (lightmode == 0)
        Light1z = Light1z + 1;

        else if (lightmode == 1)
        Light2z = Light2z + 1;
    }

    //Strafe
    else if (key == 'd' && mode == 1)
    {
        if (lightmode == 0)
        Light1x = Light1x + 1;

        else if (lightmode == 1)
        Light2x = Light2x + 1;  
    }
    else if (key == 'a' && mode == 1)
    {
        if (lightmode == 0)     
        Light1x = Light1x - 1;
        else if (lightmode == 1)
        Light2x = Light2x - 1;

    }   

    //Up & Down (Cube offset by +0.5 in Y)
    else if (key == 'z' && mode == 1)
    {
        if (lightmode == 0)
        Light1y = Light1y + 1;
        else if (lightmode == 1)
        Light2y = Light2y + 1;
    }
    else if (key == 'x' && mode == 1)
    {
        if (lightmode == 0)
        Light1y = Light1y - 1;
        else if (lightmode == 1)
        Light2y = Light2y - 1;
    }

    //Redraw objects
    glutPostRedisplay();
}


//---------------------------------------
// Display callback for OpenGL
//---------------------------------------
void display()
{       
        // Clear the screen
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //Rotation Code
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(xangle, 1.0, 0.0, 0.0);
    glRotatef(yangle, 0.0, 1.0, 0.0);

    //Light Code
    // init_material(Ka, Kd, Ks, 100 * Kp, 0.8, 0.6, 0.4);  

    // init_light(GL_LIGHT1, Light1x, Light1y, Light1z, Light1r, Light1g, Light1b);
        // init_light(GL_LIGHT2, Light2x, Light2y, Light2z, Light2r, Light2g, Light2b);

    // glEnable(GL_LIGHTING);
    //Color Code
    calc_color();

    //Draw the squares, select column  
    for (int i = 0; i <= 9; i++)
    {
        //Select row        
        for (int j = 0; j <= 9; j++)
        {   
            glBegin(GL_POLYGON); 

            //Surface starts at top left
            //Counter clockwise

            glColor3f(R[i][j], G[i][j], B[i][j]);
            std::cout << R[i][j] << " " <<  G[i][j] << " " <<  B[i][j] << endl;
            // glNormal3f(Nx[i][j], Ny[i][j], Nz[i][j]);
            glVertex3f(surfaceX[i][j], surfaceY[i][j], surfaceZ[i][j]);

            glColor3f(R[i][j+1], G[i][j+1], B[i][j+1]);
            // glNormal3f(Nx[i][j+1], Ny[i][j+1], Nz[i][j+1]);
            glVertex3f(surfaceX[i][j+1], surfaceY[i][j+1], surfaceZ[i][j+1]);

            glColor3f(R[i+1][j+1], G[i+1][j+1], B[i+1][j+1]);
            // glNormal3f(Nx[i+1][j+1], Ny[i+1][j+1], Nz[i+1][j+1]);
            glVertex3f(surfaceX[i+1][j+1], surfaceY[i+1][j+1], surfaceZ[i+1][j+1]);

            glColor3f(R[i+1][j], G[i+1][j], B[i+1][j]);
            // glNormal3f(Nx[i+1][j], Ny[i+1][j], Nz[i+1][j]);
            glVertex3f(surfaceX[i+1][j], surfaceY[i+1][j], surfaceZ[i+1][j]);

            glEnd();
        }
    }

    // glDisable(GL_LIGHTING);
    //Draw the normals
    for (int i = 0; i <= 10; i++)
    {
        for (int j = 0; j <= 10; j++)
        {

            glBegin(GL_LINES);
            glColor3f(0.0, 1.0, 1.0);
            float length = 1;
            glVertex3f(surfaceX[i][j], surfaceY[i][j], surfaceZ[i][j]);
                glVertex3f(surfaceX[i][j]+length*Nx[i][j], 
                surfaceY[i][j]+length*Ny[i][j], 
                surfaceZ[i][j]+length*Nz[i][j]);
            glEnd();
        }
    }

    //Marking location of lights
    glPointSize(10);
    glBegin(GL_POINTS);
    glColor3f(Light1r, Light1g, Light1b);
    glVertex3f(Light1x, Light1y, Light1z);      
    glEnd();

    glPointSize(10);
    glBegin(GL_POINTS);
    glColor3f(Light2r, Light2g, Light2b);
    glVertex3f(Light2x, Light2y, Light2z);      
    glEnd();

    //+Z = Moving TOWARD camera in opengl
    //Origin point for reference
    glPointSize(10);
    glColor3f(1.0, 1.0, 0.0);
    glBegin(GL_POINTS);
    glVertex3f(0, 0, 0);        
    glEnd();

    //Assign Color of Lines
    float R = 1;
    float G = 1;
    float B = 1;
    glBegin(GL_LINES);
    glColor3f(R, G, B);

    ////Drawing the grid
    //Vertical lines
    for (int i = 0; i < 11; i++)
    {
        int b = -5 + i;

        glVertex3f(b, 0, -5);
        glVertex3f(b, 0, 5);
    }

    //Horizontal lines
    for (int i = 0; i < 11; i++)
    {
        int b = -5 + i;
        glVertex3f(-5,0,b);
        glVertex3f(5,0,b);

    }

    glEnd();
    glFlush();  
}

//---------------------------------------
// Main program
//---------------------------------------
int main(int argc, char *argv[])
{
    srand(time(NULL));

    //Print Instructions
    std::cout << "Project 3 Controls: " << std::endl;
    std::cout << "q switches control mode" << std::endl;
    std::cout << "w,a,s,d for camera rotation" << std::endl;


    //Required
    glutInit(&argc, argv);
    //Window will default to a different size without
    glutInitWindowSize(500, 500);
    //Window will default to a different position without
    glutInitWindowPosition(250, 250);
    //
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH);
    //Required
    glutCreateWindow("Project 3");
    //Required, calls display function
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);

    //Required
    init();
    glutMainLoop();



   return 0;
}
2

There are 2 answers

0
Rabbid76 On BEST ANSWER

A common formula to calculate a a diffuse light is to calculate the Dot product of the normal vector of the surface and the vector to from the surface to the light source. See How does this faking the light work on aerotwist?.

kd = max(0, L dot N)

To get the color of the light, the RGB values are component wise multiplied by the diffuse coefficient:

(Cr, Cg, Cb) = (LCr, LCg, LCb) * kd

If there are multiple light sources, then the light colors are summed:

(Cr, Cg, Cb) = (LC1r, LC1g, LC1b) * max(0, L1 dot N) + (LC2r, LC2g, LC2b) * max(0, L2 dot N)

Note, if the surface (material) has an additional color, then ths color would have to be component wise multiplied to the final color:

(Cr, Cg, Cb) = (Cr, Cg, Cb) * (CMr, CMg, CMb)

Write a function which calculates the light for 1 single light source and add the light to the final color:

void add_light_color(int i, int j, float lpx, float lpy, float lpz, float lcr, float lcg, float lcb)
{
    float Lx = lpx - surfaceX[i][j]; 
    float Ly = lpy - surfaceY[i][j];
    float Lz = lpz - surfaceZ[i][j];

    float length = sqrt(Lx * Lx + Ly * Ly + Lz * Lz);
    if (length <= 0.0)
        return;

    float kd = Lx/length * Nx[i][j] + Ly/length * Ny[i][j] + Ly/length * Ny[i][j];
    if ( kd <= 0.0 )
    return; 

    R[i][j] += kd * lcr;
    G[i][j] += kd * lcg;
    B[i][j] += kd * lcb;
}

Traverse the filed of attributes, set each color (0, 0, 0) and use the above function to add the color form each light source:

void calc_color()
{
    float lp1[] = {Light1x, Light1y, Light1z};
    float lp2[] = {Light2x, Light2y, Light2z};
    float lc1[] = {Light1r, Light1g, Light1b};
    float lc2[] = {Light2r, Light2g, Light2b};

    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < 10; j++)
        {
            R[i][j] = G[i][j] = B[i][j] = 0.0;  

            add_light_color(i, j, Light1x, Light1y, Light1z, Light1r, Light1g, Light1b);
            add_light_color(i, j, Light2x, Light2y, Light2z, Light2r, Light2g, Light2b);
        }
    }
}

The result for the following light color settings:

float Light1r = 1;
float Light1g = 0;
float Light1b = 0;

float Light2r = 0;
float Light2g = 1;
float Light2b = 0;

0
Rabbid76 On

I recommend to write your first Shader program, which does per fragment lighting and consists of a Vertex Shader and a Fragment Shader.
The program has to use GLSL version 2.00 (OpenGL Shading Language 1.20 Specification). This program can access the Fixed function attributes by the built in variables gl_Vertex, gl_Normal and gl_Color, as the fixed function matrices gl_NormalMatrix, gl_ModelViewMatrix and gl_ModelViewProjectionMatrix and the function ftransform().
See als Built in Vertex Attributes and Hello World in GLSL.
Further the program has to use Uniform Variables for the light colors and positions.

The Vertex shader transforms the model space coordinates and vectors to view space and pass the to the fragment shader by Varying Variables:

std::string vertex_shader = R"(
#version 120

uniform vec3 u_light_pos_1;
uniform vec3 u_light_pos_2;

varying vec3 v_pos;
varying vec3 v_nv;
varying vec4 v_color;
varying vec3 v_light_pos1;
varying vec3 v_light_pos2;

void main()
{
    v_pos        = (gl_ModelViewMatrix * gl_Vertex).xyz;
    v_nv         = gl_NormalMatrix * gl_Normal;
    v_color      = gl_Color;
    v_light_pos1 = (gl_ModelViewMatrix * vec4(u_light_pos_1, 1.0)).xyz;
    v_light_pos2 = (gl_ModelViewMatrix * vec4(u_light_pos_2, 1.0)).xyz;
    gl_Position  = ftransform();
}
)";

The fragment shader dose the per Fragment Light calculations in view space:

std::string fragment_shader = R"(
#version 120

varying vec3 v_pos;
varying vec3 v_nv;
varying vec4 v_color;
varying vec3 v_light_pos1;
varying vec3 v_light_pos2;

uniform vec3 u_light_col_1;
uniform vec3 u_light_col_2;

void main()
{
    vec3 N  = normalize(v_nv); 
    vec3 L1 = normalize(v_light_pos1 - v_pos);
    vec3 L2 = normalize(v_light_pos2 - v_pos);

    float kd_1 = max(0.0, dot(L1, N));
    float kd_2 = max(0.0, dot(L2, N));

    vec3 light_sum = kd_1 * u_light_col_1 + kd_2 * u_light_col_2;

    gl_FragColor = vec4(v_color.rgb * light_sum, v_color.a);
}
)";

Compile the shader stages

GLuint generate_shader(GLenum stage, const std::string &source)
{
    GLuint shader_obj = glCreateShader(stage);
    const char *srcCodePtr = source.c_str();
    glShaderSource(shader_obj, 1, &srcCodePtr, nullptr);
    glCompileShader(shader_obj);
    GLint status;
    glGetShaderiv(shader_obj, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE)
    {
        GLint maxLen;
        glGetShaderiv(shader_obj, GL_INFO_LOG_LENGTH, &maxLen);
        std::vector< char >log( maxLen );
        GLsizei len;
        glGetShaderInfoLog(shader_obj, maxLen, &len, log.data());
        std::cout << "compile error:" << std::endl << log.data() << std::endl;
    }
    return shader_obj;
}

and link the program.

GLuint generate_program(const std::string &vert_sh, const std::string &frag_sh)
{
    std::cout << "compile vertex shader" << std::endl;
    GLuint vert_obj = generate_shader(GL_VERTEX_SHADER, vert_sh);
    std::cout << "compile fragment shader" << std::endl;
    GLuint frag_obj = generate_shader(GL_FRAGMENT_SHADER, frag_sh);

    std::cout << "link shader program" << std::endl;
    GLuint program_obj = glCreateProgram();
    glAttachShader(program_obj, vert_obj);
    glAttachShader(program_obj, frag_obj);
    glLinkProgram(program_obj);
    GLint status;
    glGetProgramiv(program_obj, GL_LINK_STATUS, &status);
    if (status == GL_FALSE)
    {
        GLint maxLen;
        glGetProgramiv(program_obj, GL_INFO_LOG_LENGTH, &maxLen);
        std::vector< char >log( maxLen );
        GLsizei len;
        glGetProgramInfoLog(program_obj, maxLen, &len, log.data());
        std::cout  << "link error:" << std::endl << log.data() << std::endl;
    }

    glDeleteShader(vert_obj);
    glDeleteShader(frag_obj);
    return program_obj;
}

Further get the uniform locations by glGetUniformLocation in the function init:

GLuint diffuse_prog_obj = 0;
GLint loc_l_pos[] = {-1, -1};
GLint loc_l_col[] = {-1, -1};
void init()
{
    diffuse_prog_obj = generate_program(vertex_shader, fragment_shader);
    loc_l_pos[0] = glGetUniformLocation(diffuse_prog_obj, "u_light_pos_1");
    loc_l_pos[1] = glGetUniformLocation(diffuse_prog_obj, "u_light_pos_2");
    loc_l_col[0] = glGetUniformLocation(diffuse_prog_obj, "u_light_col_1");
    loc_l_col[1] = glGetUniformLocation(diffuse_prog_obj, "u_light_col_2");

    // [...]
}

The shader program can be used by glUseProgram. The uniforms are set by glUniform*.
Beside the vertex coordinates, the normal vector attributes have to be set per vertex, to make the light calculations proper work. But it is sufficient to set a single color for the entire mesh:

void display()
{
    // [...]

    // install program
    glUseProgram(diffuse_prog_obj);

    // set light positions and colors 
    glUniform3f(loc_l_pos[0], Light1x, Light1y, Light1z);
    glUniform3f(loc_l_pos[1], Light2x, Light2y, Light2z);
    glUniform3f(loc_l_col[0], Light1r, Light1g, Light1b);
    glUniform3f(loc_l_col[1], Light2r, Light2g, Light2b);

    // set object color
    glColor3f(1, 1, 0.5);

    //Draw the squares, select column  
    for (int i = 0; i <= 9; i++)
    {
        //Select row        
        for (int j = 0; j <= 9; j++)
        {   
            glBegin(GL_POLYGON); 

            std::cout << R[i][j] << " " <<  G[i][j] << " " <<  B[i][j] << endl;
            glNormal3f(Nx[i][j], Ny[i][j], Nz[i][j]);
            glVertex3f(surfaceX[i][j], surfaceY[i][j], surfaceZ[i][j]);

            glNormal3f(Nx[i][j+1], Ny[i][j+1], Nz[i][j+1]);
            glVertex3f(surfaceX[i][j+1], surfaceY[i][j+1], surfaceZ[i][j+1]);

            glNormal3f(Nx[i+1][j+1], Ny[i+1][j+1], Nz[i+1][j+1]);
            glVertex3f(surfaceX[i+1][j+1], surfaceY[i+1][j+1], surfaceZ[i+1][j+1]);

            glNormal3f(Nx[i+1][j], Ny[i+1][j], Nz[i+1][j]);
            glVertex3f(surfaceX[i+1][j], surfaceY[i+1][j], surfaceZ[i+1][j]);

            glEnd();
        }
    }

    // invalidate installed program
    glUseProgram(0);

    // [...]
}

See the preview of you program, with the applied suggestions: