OpenGL: Как мне отрисовать отдельные значения RGB с учетом перекрестного произведения, которое содержит общий свет?
Я работал над собственной реализацией модели затенения в стиле Гуро, и у меня остальная часть работала в основном так, как я хочу, но проблема, с которой я столкнулся, заключается в том, что он показывает только белый свет. Функция calc_color - это место, где выполняется эта операция. Переменная 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;
}
2 ответа
Распространенной формулой для расчета рассеянного света является вычисление произведения точки вектора нормали поверхности и вектора от поверхности к источнику света. Посмотрите, как это подделка света работает на аэротвисте?,
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)
Обратите внимание, что если поверхность (материал) имеет дополнительный цвет, то этот цвет должен быть умножен на компонент до конечного цвета:
(Cr, Cg, Cb) = (Cr, Cg, Cb) * (CMr, CMg, CMb)
Напишите функцию, которая рассчитывает свет для одного источника света и добавьте свет к окончательному цвету:
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;
Я рекомендую написать вашу первую программу Shader, которая выполняет осветление фрагментов и состоит из Vertex Shader и Fragment Shader.
Программа должна использовать GLSL версии 2.00 ( Спецификация OpenGL Shading Language 1.20). Эта программа может получить доступ к атрибутам фиксированной функции по встроенным переменным gl_Vertex
, gl_Normal
а также gl_Color
, как матрицы с фиксированной функцией gl_NormalMatrix
, gl_ModelViewMatrix
а также gl_ModelViewProjectionMatrix
и функция ftransform()
,
См. Также Встроенные атрибуты вершин и Hello World в GLSL.
Далее программа должна использовать Uniform 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();
}
)";
Фрагмент шейдерной дозы в расчете на фрагмент света в пространстве вида:
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;
}
и связать программу.
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;
}
Далее получите однородные локации по glGetUniformLocation
в функции 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");
// [...]
}
Программа шейдера может быть использована glUseProgram
, Униформа устанавливается 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);
// [...]
}
Смотрите предварительный просмотр вашей программы, с приложенными предложениями: