sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[27 Жов 2012]

Візуалізація нормалей і ребер з допомогою геометричних шейдерів в OpenGL і GLSL

У цьому уроці показано як з допомогою геометричних шейдерів (Geometry shader) візуалізувати нормалі, дотичні, бідотичні, а також ребра полігональної сітки. Результат роботи геометричного шейдера показаний на наступному малюнку.
Геометричнй шейдер дозволяє з початкових примітивів створювати нові примітиви. Такий шейдер виконується після вершинного шейдера і перед фрагментним шейдером. Тобто вершиний шейдер готує дані для геометричного, а геометричний генерує примітиви, які будуть растеризуватися і оброблятися фрагментним шейдером. Вхідними і вихідними даними для геометричного шейдера можуть бути окремі точки, лінії (дві вершини) чи трикутники (три вершини). При цьому тип вхідних і вихідних даних може відрізнятися. Наприклад, у цьому уроці показано як трикутники (груп з трьох вершин) перетворити у лінії.
Згенеровані нормалі вершин, нормалі трикутників, дотичні, бідотичні а також ребра полігональної сітки
Все просто. Якщо вхідним типом примітива геометричного шейдера є трикутник, то у шейдері є доступ до трьох вершин цього трикутника. Для кожної вершини є доступ до усіх даних, які були записані вершиним шейдером. Щоб візуалізувати нормалі в вершинах, потрібно створити по одній лінії для кожної вершини. Перша точка лінії повинна бути рівна позиції вершини. Другий кінець лінії повинен бути рівним позиції вершини зсунутій вздовж нормалі в вершині (яка передана з вершинного шейдера). Довжину нормалі можна задати через uniform змінну. На малюнку нормалі в вершинах показані зеленим кольором.
Дотичні і бідотичні візуалізуються аналогічно до нормалей. Дотичні і бідотичні показані на малюнку червоними та синіми лініями.
Для того, щоб візуалізувати ребра трикутників потрібно для кожного трикутника створити три лінії. Типом вихідного примітива геометричного шейдера може бути не просто лінія, а масив точок, які послідовно з'єднані (line strip). Для того щоб створити три лінії через line strip, потрібно додати чотири вершини. Спочатку першу вершину трикутника, потім другу, третю і знову першу (щоб замкнути масив ліній). На малюнку ребра показані сірими лініями.
Для візуалізації нормалі трикутника (грані), потрібно з трьох вершин побудувати два вектора: від першої вершини до другої, і від першої до третьої. Потім, з допомогою векторного добутку цих двох векторів, знайти нормаль до грані. Початкова точка лінії є середнім арифметичним з позицій трьох вершин трикутника. Кінець лінії розраховується як зсув вздовж нормалі грані трикутника. Нормалі граней показані на малюнку жовтими лініями.
Варто зауважити, що типи вхідних і вихідних примітивів, і максимальна кількість вихідних примітивів не можуть бути змінені динамічно під час виконання шейдера.
Далі наведено вершинний, геометричний та фрагментний шейдери для візуалізації нормалей, граней та інших розглянутих раніше векторів.
Вершинний шейдер
#version 330

// атрибути
layout(location = 0) in vec3 i_position; // xyz - position
layout(location = 1) in vec3 i_normal; // xyz - normal
layout(location = 2) in vec4 i_tangent; // xyz - tangent, w - handedness

// матриці
uniform mat4 u_model_mat;
uniform mat3 u_normal_mat;

// дані до геометричного шейдера
out vec3 o_normal;
out vec3 o_tangent;
out vec3 o_bitangent;

///////////////////////////////////////////////////////////////////

void main(void)
{
   // позиція в світових координатах
   gl_Position = u_model_mat * vec4(i_position, 1.0);

   // нормаль, дотична і бідотична в світових координатах
   o_normal = normalize(u_normal_mat * i_normal);
   o_tangent = normalize(u_normal_mat * i_tangent.xyz);
   o_bitangent = cross(o_normal, o_tangent) * i_tangent.w;
}
Геометричний шейдер
#version 330

// тип вхідних примітивів
layout(triangles) in;

// тип вихідних примітивів і максимальна кількість створених вершин
// для кожної лінії необхідно дві вершини
// лінії, що виводяться: нормаль, дотична і бідотична для трьох вершин = 18
// чотири вершини для ребер = 4
// одна лінія для нормалі грані = 2

layout(line_strip, max_vertices = 24) out;

///////////////////////////////////////////////////////////////////

// дані від вершинного шейдера для кожної вершини
in vec3 o_normal[];
in vec3 o_tangent[];
in vec3 o_bitangent[];

// матриці
uniform mat4 u_view_mat;
uniform mat4 u_proj_mat;

// модифікатор розміру нормалі
uniform float u_normalScale;

// кольори для різних типів
uniform vec4 u_edgeColor;
uniform vec4 u_faceNormalColor;
uniform vec4 u_normalColor;
uniform vec4 u_tangentColor;
uniform vec4 u_bitangentColor;

// колір для фрагментного шейдера
out vec4 o_color;

///////////////////////////////////////////////////////////////////

void main()
{
   mat4 viewProjection = u_proj_mat * u_view_mat;

   // нормалі кожної вершини трикутника
   vec3 nor[3];
   nor[0] = o_normal[0].xyz;
   nor[1] = o_normal[1].xyz;
   nor[2] = o_normal[2].xyz;

   // позиції кожної вершини трикутника
   // зсунуті трохи вздовж нормалі
   // таким чином можна візуалізувати грані поверх полігональної сітки

   vec4 pos[3];
   pos[0] = viewProjection * vec4(gl_in[0].gl_Position.xyz + nor[0] * 0.01, 1.0);
   pos[1] = viewProjection * vec4(gl_in[1].gl_Position.xyz + nor[1] * 0.01, 1.0);
   pos[2] = viewProjection * vec4(gl_in[2].gl_Position.xyz + nor[2] * 0.01, 1.0);

   // вивід нормалей, дотичних і бідотичних для кожної вершини
   for(int i=0; i < gl_in.length(); i++)
   {
      // дістати позицію вершини
      vec3 P = gl_in[i].gl_Position.xyz;

      // створити нормаль для вершини
      o_color = u_normalColor;
      gl_Position = pos[i];
      EmitVertex();
      gl_Position = viewProjection * vec4(P + o_normal[i].xyz
                        * u_normalScale, 1.0);
      EmitVertex();
      EndPrimitive();

      // створити дотичні для вершини
      o_color = u_tangentColor;
      gl_Position = pos[i];
      EmitVertex();
      gl_Position = viewProjection * vec4(P + o_tangent[i].xyz * u_normalScale, 1.0);
      EmitVertex();
      EndPrimitive();

      // створити бідотичні для вершини
      o_color = u_bitangentColor;
      gl_Position = pos[i];
      EmitVertex();
      gl_Position = viewProjection * vec4(P +
                              o_bitangent[i].xyz * u_normalScale, 1.0);
      EmitVertex();
      EndPrimitive();
   }

   // вивід ребер для трикутника
   o_color = u_edgeColor;
   gl_Position = pos[0];
   EmitVertex();
   gl_Position = pos[1];
   EmitVertex();
   gl_Position = pos[2];
   EmitVertex();
   gl_Position = pos[0];
   EmitVertex();
   // закінчення примітиву після чотирьох вершин дозволяє створити line strip
   EndPrimitive();

   // вивід нормалі для грані
   o_color = u_faceNormalColor;
   // дістати два вектора, які формують трикутник
   vec3 V0 = gl_in[0].gl_Position.xyz - gl_in[1].gl_Position.xyz;
   vec3 V1 = gl_in[2].gl_Position.xyz - gl_in[1].gl_Position.xyz;
   // розрахувати нормаль з двох векторів
   vec3 N = normalize(cross(V1, V0));
   // позиція як сеерднє арифметичне з трьох позицій
   vec3 P = (gl_in[0].gl_Position.xyz + gl_in[1].gl_Position.xyz
                        + gl_in[2].gl_Position.xyz) / 3.0;
   gl_Position = viewProjection * vec4(P, 1.0);
   EmitVertex();
   gl_Position = viewProjection * vec4(P + N * u_normalScale, 1.0);
   EmitVertex();
   EndPrimitive();
}
Фрагментний шейдер
#version 330

// колір від геометричного шейдера
in vec4 o_color;

// колір у фреймбуфер
out vec4 resultingColor;

///////////////////////////////////////////////////////////////////

void main(void)
{
   // вивести колір
   resultingColor = o_color;
}



Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014