Список работ

OpenTK C#-шейдерная реализация освещения по Блинну и Фонгу

Содержание

Модель бликового освещения по Блинну

В модели Блинна освещенность вычисляется по следующей формуле:

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.

Блинн-сфера с p = 14 и 4

Рис. 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.

Фонга-сфера с p = 14 и 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;
}

Реализация на C# – OpenTK

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

Источники

  1. Боресков А. В. Разработка и отладка шейдеров. – СПб.: БХВ-Петербург, 2006. – 496 с.
  2. C#, OpenGL, OpenTK: построение, текстурирование и тонирование сферы

Список работ

Рейтинг@Mail.ru