OpenGL:如何在给定包含总光的叉积的情况下渲染单个 RGB 值?

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

我一直在努力实现自己的 Gouraud 风格着色模型,其余部分几乎按照我想要的方式工作,但我 运行 遇到的问题是不是只显示白光。 calc_color 函数是执行此操作的地方。颜色变量表示给定位置的 R、G 和 B 值的 光。我一直在为所有三个数组分配 Color 只是为了正确实现阴影,但现在已经完成了,我想找出一种从总光值中提取 R、G 和 B 值的方法。

我尝试了几种不同的方法,比如获取总光,获取 Light1r 的百分比等值,但它总是看起来很奇怪或太亮。

#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;
}

我建议写你的第一个 Shader program, which does per fragment lighting and consists of a Vertex Shader and a Fragment Shader
该程序必须使用 GLSL 版本 2.00 (OpenGL Shading Language 1.20 Specification)。该程序可以通过内置变量 gl_Vertexgl_Normalgl_Color 访问固定函数属性,如固定函数矩阵 gl_NormalMatrixgl_ModelViewMatrixgl_ModelViewProjectionMatrix 和函数 ftransform().
另见 Built in Vertex Attributes and Hello World in GLSL.
此外,程序必须使用 Uniform Variables 作为灯光颜色和位置。

顶点着色器将模型 space 坐标和向量转换为视图 space 并通过 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();
}
)";

片段着色器在视图 space:

中进行 per Fragment Light 计算
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);
}
)";

编译着色器阶段

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;
}

和link程序。

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;
}

在函数init:

中通过glGetUniformLocation进一步得到统一位置
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");

    // [...]
}

着色器程序可以被glUseProgram. The uniforms are set by glUniform*使用。
除了顶点坐标外,还必须为每个顶点设置法向量属性,以使光照计算正常进行。但是为整个网格设置单一颜色就足够了:

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);

    // [...]
}

查看您的程序预览,以及已应用的建议:

计算漫射光的常用公式是计算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)

为了获得光的颜色,RGB 值按分量乘以漫反射系数:

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

如果有多个光源,则光色相加:

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

请注意,如果表面 (material) 有一个额外的颜色,那么这个颜色必须按分量乘以最终颜色:

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

编写一个函数来计算 1 个单一光源的光并将光添加到最终颜色:

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;
}

遍历属性字段,设置每个颜色(0,0,0)并使用上面的函数添加每个光源的颜色:

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);
        }
    }
}

以下灯光颜色设置的结果:

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

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