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_Vertex
、gl_Normal
和 gl_Color
访问固定函数属性,如固定函数矩阵 gl_NormalMatrix
、gl_ModelViewMatrix
和 gl_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;
我一直在努力实现自己的 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_Vertex
、gl_Normal
和 gl_Color
访问固定函数属性,如固定函数矩阵 gl_NormalMatrix
、gl_ModelViewMatrix
和 gl_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;