В модели Блинна освещенность вычисляется по следующей формуле:
Cout = C * (ka + kd * max( (N, L), 0) + I * ks * max((N, H), 0)p), (1)
C – цвет поверхности в расчетной точке;
ka и kd – соответственно вклады фонового и диффузионного освещений;
I – цвет бликового освещения;
ks – вклад бликового освещения;
p – параметр, отвечающий за контрастность блика, которая растет с ростом параметра; одновременно уменьшается и размер блика;
N, L и V – соответственно единичные векторы нормали к поверхности, к источнику света и к наблюдателю (рис. 1);
H – единичный вектор-бисектор направлений L и V.
Рис. 1. Векторы в модели освещенности Блинна
Вектор H, если нормированы векторы L и V, вычисляется по следующей формуле:
H = (L + V) / |L + V|.
Нормированный вектор H – это нормированный вектор L + V.
В вершинном шейдере в каждой вершине вычисляются векторы N, L, V и H.
Векторы N, L и H передаются фрагментному шейдеру посредством varying-переменных.
Координаты наблюдателя и источника света передаются вершинному шейдеру посредством uniform-переменных EyePos и LightPos.
Контрастность блика определяется uniform-переменной specPower, передаваемой фрагментному шейдеру.
В приводимой ниже программе контрастность блика можно менять, нажимая на клавиши F11 и F12.
Фрагментный шейдер нормирует векторы N, L и H и вычисляет освещенность по формуле (1).
Сфера, освещенная по Блинну, показана на рис. 2.
Рис. 2. Сфера в модели освещенности Блинна с p = 14 и 4
Вершинный Блинн-шейдер (файл blinnVS.glsl):
// Blinn vertex shader
varying vec3 n, l, h;
uniform vec3 EyePos, LightPos;
void main()
{
vec3 p = vec3(gl_ModelViewMatrix * gl_Vertex);
n = gl_NormalMatrix * gl_Normal;
l = LightPos - p;
h = l + (EyePos - p);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Фрагментный Блинн-шейдер (файл blinnFS.glsl):
// Blinn fragment shader
varying vec3 n, l, h;
uniform float specPower;
void main()
{
vec4 diffColor = vec4(0.75, 0.0, 0.0, 1.0);
vec4 specColor = vec4(0.75, 0.75, 0.0, 1.0);
vec3 n2 = normalize(n);
vec4 diff = diffColor * max(dot(n2, normalize(l)), 0);
vec4 spec = specColor * pow(max(dot(n2, normalize(h)), 0), specPower);
gl_FragColor = diff + spec;
}
В модели Фонга освещенность вычисляется по следующей формуле:
Cout = C * (ka + kd * max((N, L), 0) + I * ks * max((V, R), 0)p) (2)
Описание параметров C, ka, kd, I, ks, p, N, L и V см. в предыдущем разделе.
Вектор R – получается в результате отражения вектора L относительно вектора N (рис. 3).
Рис. 3. Векторы в модели освещенности Фонга
В вершинном шейдере в каждой вершине вычисляются векторы N, L и V.
Они передаются фрагментному шейдеру посредством varying-переменных.
Координаты наблюдателя и источника света передаются вершинному шейдеру посредством uniform-переменных EyePos и LightPos.
Как и в Блинн-шейдере, контрастность блика определяется uniform-переменной specPower, передаваемой фрагментному шейдеру.
В приводимой ниже программе контрастность блика можно менять, нажимая на клавиши F11 и F12.
Фрагментный шейдер нормирует векторы N, L и V, вычисляет и нормирует вектор и R и вычисляет освещенность по формуле (2).
Сфера, освещенная по Фонгу, показана на рис. 4.
Рис. 4. Сфера в модели освещенности Фонга с p = 14 и 4
Вершинный Фонг-шейдер (файл phongVS.glsl):
// Phong vertex shader
varying vec3 n, l, v;
uniform vec3 EyePos, LightPos;
void main()
{
vec3 p = vec3 (gl_ModelViewMatrix * gl_Vertex);
n = gl_NormalMatrix * gl_Normal;
l = LightPos - p;
v = EyePos - p;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Фрагментный Фонг-шейдер (файл phongFS.glsl):
// Phong fragment shader
varying vec3 n, l, v;
uniform float specPower;
void main()
{
vec4 diffColor = vec4(0.75, 0.0, 0.0, 1.0);
vec4 specColor = vec4(0.75, 0.75, 0.0, 1.0);
vec3 n2 = normalize(n);
vec3 l2 = normalize(l);
vec3 r2 = reflect(-l2, n2);
vec4 diff = diffColor * max(dot(n2, l2), 0);
vec4 spec = specColor * pow(max(dot(normalize(v), r2), 0), specPower);
gl_FragColor = diff + spec;
}
using System;
using System.IO; // StreamReader
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input; // KeyboardKeyEventArgs
namespace SimpleShader
{
public class Simple_shader: GameWindow
{
public Simple_shader() : base(300, 300)
{
KeyDown += Keyboard_KeyDown; // Обработчик нажатий на клавиши клавиатуры
}
// Шейдеры вершин и фрагментов
int VertexShaderObject, FragmentShaderObject, ProgramObject;
const string VertexShaderFilename = "blinnVS.glsl";
const string FragmentShaderFilename = "blinnFS.glsl";
//const string VertexShaderFilename = "phongVS.glsl";
//const string FragmentShaderFilename = "phongFS.glsl";
Vector3 EyePos = new Vector3(0f, 0f, 3f); // Камера
Vector3 LightPos = new Vector3(0f, 0f, 2f); // Источник света
float specPower = 14; // Контрастность блика
// Обработчик нажатий на клавиши клавиатуры
void Keyboard_KeyDown(object sender, KeyboardKeyEventArgs e)
{
if (e.Key == Key.Escape) this.Exit();
if (e.Key == Key.F11) specPower += 2;
if (e.Key == Key.F12) if (specPower > 2) specPower -= 2;
}
private int readShader(string shaderFilename, ShaderType st, string vf)
{
string LogInfo;
int shaderObject = GL.CreateShader(st);
using (StreamReader sr = new StreamReader(shaderFilename))
{
GL.ShaderSource(shaderObject, sr.ReadToEnd());
GL.CompileShader(shaderObject);
}
GL.GetShaderInfoLog(shaderObject, out LogInfo);
if (LogInfo.Length > 0 && !LogInfo.Contains("hardware"))
Console.WriteLine("Ошибка компиляции шейдера " + vf + "\nLog:\n" + LogInfo);
else
Console.WriteLine("Компиляция шейдера " + vf + " завершена успешно.");
return shaderObject;
}
protected override void OnLoad(EventArgs e)
{
string LogInfo;
int[] temp = new int[1];
GL.ClearColor(0.2f, 0f, 0.4f, 0f); // Цвет фона
// Загрузка и компиляция шейдеров вершин и фрагментов
VertexShaderObject = readShader(VertexShaderFilename, ShaderType.VertexShader, "вершин");
FragmentShaderObject = readShader(FragmentShaderFilename, ShaderType.FragmentShader, "фрагментов");
// Связываем шейдеры с рабочей программой
ProgramObject = GL.CreateProgram();
GL.AttachShader(ProgramObject, VertexShaderObject);
GL.AttachShader(ProgramObject, FragmentShaderObject);
// Связываем все вместе
GL.LinkProgram(ProgramObject);
// Удаляем ранее созданные шейдеры
GL.DeleteShader(VertexShaderObject);
GL.DeleteShader(FragmentShaderObject);
GL.GetProgram(ProgramObject, GetProgramParameterName.LinkStatus, out temp[0]);
Console.WriteLine("Связывание программ (" + ProgramObject + ") " + ((temp[0] == 1) ? "выполнено." : "НЕ ВЫПОЛНЕНО."));
if (temp[0] != 1) // В случае неудачи при связывании
{
GL.GetProgramInfoLog(ProgramObject, out LogInfo);
Console.WriteLine("Информация:\n" + LogInfo);
}
GL.GetProgram(ProgramObject, GetProgramParameterName.ActiveAttributes, out temp[0]);
Console.WriteLine("Зарегестрировано " + temp[0] + " атрибута.");
Console.WriteLine("Создание шейдера завершено. GL-ошибка: " + GL.GetError() + ".");
Console.WriteLine("");
}
protected override void OnUnload(EventArgs e)
{
GL.DeleteProgram(ProgramObject);
base.OnUnload(e);
}
// Обработчик изменения размеров окна графического вывода
protected override void OnResize(EventArgs e)
{
GL.Viewport(0, 0, Width, Height);
GL.MatrixMode(MatrixMode.Projection);
Matrix4 p = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Width / (float)Height, 0.1f, 100.0f);
GL.LoadMatrix(ref p);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();
base.OnResize(e);
}
// Обработчик обновления
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
}
// Вывод сферы, формирование координат нормалей и текстуры
// r - радиус сферы
// nx - число полигонов (сегментов) по X
// ny - число полигонов (сегментов) по Y
void sphere(double r, int nx, int ny)
{
int ix, iy;
double rsy, rcy, rsy1, rcy1, sx, cx, piy, pix, ay, ay1, ax;
Vector3d vert;
piy = Math.PI / (double)ny;
pix = 2.0 * Math.PI / (double)nx;
// Рисуем полигональную модель сферы и формируем нормали
// Каждый полигон - это трапеция. Трапеции верхнего и нижнего слоев вырождаются в треугольники
GL.Begin(PrimitiveType.QuadStrip);
ay = -piy;
for (iy = 0; iy < ny; iy++)
{
ay += piy;
rsy = r * Math.Sin(ay);
rcy = r * Math.Cos(ay);
ay1 = ay + piy;
rsy1 = r * Math.Sin(ay1);
rcy1 = r * Math.Cos(ay1);
ax = pix;
for (ix = 0; ix <= nx; ix++)
{
ax -= pix;
sx = Math.Sin(ax);
cx = Math.Cos(ax);
vert = new Vector3d(rsy * cx, rsy * sx, -rcy);
GL.Normal3(vert); // Координаты нормали в текущей вершине; нормаль направлена от центра
GL.Vertex3(vert);
vert = new Vector3d(rsy1 * cx, rsy1 * sx, -rcy1);
GL.Normal3(vert);
GL.Vertex3(vert);
}
}
GL.End();
}
// Обработчик воспроизведения
protected override void OnRenderFrame(FrameEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.UseProgram(ProgramObject);
GL.Uniform3(GL.GetUniformLocation(ProgramObject, "EyePos"), EyePos.X, EyePos.Y, EyePos.Z);
GL.Uniform3(GL.GetUniformLocation(ProgramObject, "LightPos"), LightPos.X, LightPos.Y, LightPos.Z);
GL.Uniform1(GL.GetUniformLocation(ProgramObject, "specPower"), specPower);
GL.PushMatrix();
Matrix4 t = Matrix4.LookAt(EyePos, Vector3.Zero, Vector3.UnitY);
GL.MultMatrix(ref t);
sphere(1, 32, 32); // Выводим сферу
GL.UseProgram(0);
GL.PopMatrix();
this.SwapBuffers();
}
// Точка входа
[STAThread]
public static void Main()
{
using (Simple_shader mapping = new Simple_shader())
{
mapping.Title = "Blinn/Phong shader";
mapping.Run(10.0, 2.0);
}
}
}
}