sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[28 Січ 2013]

Штрихування та Gooch шейдінг в GLSL

В першій частині цього уроку показано, як швидко заштрихувати об'єкт з допомогою GLSL. В другій частині уроку показано реалізацію нефотореалістичного освітлення з допомогою техніки Gooch shading. На наступних малюнках показаний результат роботи:
Просте штрихування та накладання візерунків
Gooch шейдінг з допомогою теплого та світлого кольорів
Комбінування штрихування на Gooch шейдінга
Штрихування (Hatching)
Штрихування (hatching) - метод, який використовується для створення нефотореалістичного ефекту затінення з допомогою малювання штрихів різної густини. Чим темнішою повинна бути частина об'єкта, тим більше на неї наноситься штрихів. Можуть використовуватися штрихи різної довжини, ширини, під різними кутами, з різною відстанню один від одного, з різною густиною розміщення. Коли штрихи розміщуються під кутом один до одного, то метод називається перехресним штрихуванням (cross-hatching). Можна застосовувати штрихи під різними кутами, щоб виділити контрастні обласні об'єкта чи різні кольори об'єкта. Для кращого виділення форми об'єкта може використовуватися штриховка кривими.
Для симуляції штриховки, яка виділяє форму об'єкта, наприклад, такої штриховки, що йде вздовж контурів об'єкта, використовуються методи визначення напряму штрихів в залежності від форми об'єкта. Для цього необхідно розрахувати текстуру, яка буде визначати напрям штрихів. Для параметричних об'єктів, таких як циліндр чи сфера, можна використати їхні параметричні текстурні координати для визначення напряму штрихів. Тобто для параметричних фігур можливо зробити штриховку вздовж градієнту зміни параметрів, без попереднього розрахунку напряму штрихів. Для інших об'єктів такого зробити не можна, так як текстурні координати не є постійними, і можуть перериватися.
Просте штрихування в GLSL
Але в цьому уроці буде розглянута найпростіша версія штриховки, яка не бере до уваги напрям контурів і форму об'єкта, а тільки освітленість об'єкта. Також мінусом наступного прикладу є те, що шриховка не є сталою на об'єкті, а залежить від місця на екрані, де розміщений об'єкт. Тобто штрихи не будуть залишатися на одному місці, якщо пересувати камеру.
Суть методу полягає у наступному. Спочатку необхідно створити текстури зі штрихами, які відповідають світлим та темним областям об'єкта. Наприклад, світлі обласні об'єкта повинні залишатися без штриків, а темні мають бути майже повністю заштрихованими. Приклад 8ми таких текстур показано на наступному малюнку:
Текстури для штрихування
В простому GLSL шейдері штрихування спочатку розраховується освітлення об'єкта, по простому методу Бліна-Фонга, потім значення інтенсивності освітлення визначає, яка текстура зі штрихами буде використовуватися для кожного фрагменту об'єкта. Чим менша інтенсивність освітлення, тим з більшою кількістю штрихів повинна бути текстура. Для того, щоб не отримувати помітних переходів між двома текстурами, використовується змішування текстур. Наприклад, якщо інтенсивність освітлення відповідає середній кількості штрихів на двох текстурах, то проводиться семплінг двох текстур, і відповідно змішуються кольори.
Також необхідною умовою для того, щоб не було видимих переходів між об'єктами, є те, щоб текстури не містили елементів з малою частотою (тобто великих елементів і з такою формою, що помітно виділяється від інших). Текстури будуються таким чином, що кожна темніша містить у собі усі елементи з попередньої світлішої. Тобто спочатку малюється текстура з найменшою кількістю штрихів, а наступна текстура використовує попередню, як основу, і додає нові штрихи, і тд.
Для реалізації штрихування можна використати декілька текстур, і в GLSL шейдері з допомогою інструкцій if вибирати, які текстури повинні бути використані. Якщо текстур багато, то кількість інструкцій if збільшується відповідно, а це негативно впливає на швидкодію GLSL шейдера. Але існує інший метод, якому не потрібно перебирати текстури.
Можна використати 3D текстури, які є частиною OpenGL. Такі текстури представляють собою багатошарові текстури, де кожна текстура має однакову ширину і висоту. Семплінг таких текстур відбувається подібно до звичайних. Спочатку необхідно вказати значення по X і по Y, де необхідно проводити семплінг. Для того щоб обрати, яка з текстур семплюється, використовується третє значення текстурних координат. Якщо значення вказує на колір між двома текстурами, то проводиться вибірка з текстури з шару нищє, потім з текстури шаром вище, і автоматично проводиться інтерполяція між кольорами. Ця особливість 3D текстур повністю заміняє необхідність у використанні окремих текстур та багатьох перевірок через інструкції if. Тут показано як завантажити 3D текстуру.
Крім штрихів можна використовувати різні візерунки. Звичайно це вже не можна вважати штрихуванням, але... :) Далі наведено текстури, які були використані при рендерінгу першого зображення в уроці.
Текстури з візерунком   Круги та точки   Шахова дошка   Вертикальне та горизонтальне штрихування  
Кроки, які необхідно виконати для штриховки:
  • 1) створити і завантажити 3D текстуру з різними рівнями штрихів
  • 2) для кожного фрагмента розрахувати інтенсивність світла в межах 0 до 1
  • 3) використати інтенсивність світла та позицію фрагмента на екрані для семплінгу з 3D текстури
  • Вершинний шейдер для штрихування та Gooch shading
    Вершинний шейдер для штрихування та Gooch shading. Просто передача необхідних для освітлення векторів у фрагментний шейдер:
    #version 330

    // attributes
    layout(location = 0) in vec3 i_position; // xyz - position
    layout(location = 1) in vec3 i_normal; // xyz - normal
    layout(location = 1) in vec2 i_texcoord0; // xy - texture coords

    // uniforms
    uniform mat4 u_model_mat;
    uniform mat4 u_view_mat;
    uniform mat4 u_proj_mat;
    uniform mat3 u_normal_mat;
    uniform vec3 u_light_position;
    uniform vec3 u_camera_position;

    // вихідні дані для фрагментного шейдера
    out vec3 o_normal;
    out vec3 o_lightVector;
    out vec3 o_viewVector;
    out vec2 o_texcoords;

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

    void main(void)
    {
       // перетворити позицію та нормаль у світові координати
       vec4 positionWorld = u_model_mat * vec4(i_position, 1.0);
       vec3 normalWorld = u_normal_mat * i_normal;

       // розрахувати дані для фрагментного шейдера
       o_lightVector = u_light_position - positionWorld.xyz;
       o_viewVector = u_camera_position - positionWorld.xyz;
       o_texcoords = i_texcoord0;
       o_normal = normalWorld;

       // проекція позиції на екран
       gl_Position = u_proj_mat * u_view_mat * positionWorld;
    }
    Фрагментний шейдер для штриховки (hatching)
    Фрагментний шейдер для штрихування:
    #version 330

    // дані з вершиного шейдера
    in vec3 o_normal;
    in vec3 o_lightVector;
    in vec3 o_viewVector;
    in vec2 o_texcoords;

    // семплер 3D текстури
    layout(location = 0) uniform sampler3D u_colorTexture;

    // колір, який запишеться у фреймбуфер
    out vec4 resultingColor;

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

    void main(void)
    {
       // нормалізуємо верктори для розрахунку освітлення
       vec3 normalVector = normalize(o_normal);
       vec3 lightVector = normalize(o_lightVector);
       vec3 viewVector = normalize(o_viewVector);

       // розраховуємо інтенсивність освітлення
       float ambient = 0.025f;
       float diffuse = clamp(dot(lightVector, normalVector), 0, 1);
       vec3 reflectedVector = reflect(-lightVector, normalVector);
       float specular = 0;
       if(diffuse > 0)
       {
          specular = pow(dot(viewVector, reflectedVector), 32);
       }
       float lightIntensity = clamp(diffuse + specular + ambient, 0, 1);

       // дістаємо розмір 3D текстури
       ivec3 sizeOfTex = textureSize(u_colorTexture, 0);

       // текстурні координати по XY формуються як позиція фрагмента на екрані
       // поділена на ширину текстури. Тож кожен піксель текстури буде відповідати
       // пікселю на екрані

       vec2 texCoordXY = gl_FragCoord.xy/sizeOfTex.x;
       // вибірка шару текстури здійснюється по інтенсивності світла
       float texCoordZ = lightIntensity;

       // дістаємо значення штрихування
       vec3 hatching = texture(u_colorTexture, vec3(texCoordXY, texCoordZ)).rgb;

       // змішуємо штрихування з освітленням
       resultingColor.rgb = hatching * (1.0 + lightIntensity * 2)/3;
       resultingColor.a = 1;
    }
    Також штрихування в GLSL шейдері можна розрахувати вручу (без текстур). Для кожного фрагменту в залежності від яскравості обирається кількість штрихів, і розраховується чи фрагмент лежить на штрисі. Якщо фрагмент лежить на штрисі, то йому призначається чорний колір, інакше - білий:
    Штрихування в шейдері без текстур
    resultingColor = vec4(1.0, 1.0, 1.0, 1.0);

    if (lightIntensity < 0.85)
    {
       // штрих з лівого верхнього кута до нижнього лівого
       if (mod(gl_FragCoord.x + gl_FragCoord.y, 10.0) == 0.0)
       {
          resultingColor = vec4(0.0, 0.0, 0.0, 1.0);
       }
    }

    if (lightIntensity < 0.75)
    {
       // штрих з правого верхнього кута до нижнього лівого
       if (mod(gl_FragCoord.x - gl_FragCoord.y, 10.0) == 0.0)
       {
          resultingColor = vec4(0.0, 0.0, 0.0, 1.0);
       }
    }

    if (lightIntensity < 0.5)
    {
       // штрих з лівого верхнього кута до нижнього лівого
       if (mod(gl_FragCoord.x + gl_FragCoord.y - 5.0, 10.0) == 0.0)
       {
          resultingColor = vec4(0.0, 0.0, 0.0, 1.0);
       }
    }

    if (lightIntensity < 0.25)
    {
       // штрих з правого верхнього кута до нижнього лівого
       if (mod(gl_FragCoord.x - gl_FragCoord.y - 5.0, 10.0) == 0.0)
       {
          resultingColor = vec4(0.0, 0.0, 0.0, 1.0);
       }
    }
    Gooch shading
    Gooch shading використовується для того, щоб замінити фотореалістичний рендерінг на рендерінг, який акцентує увагу на структурі та формі об'єкта. Замість використання світла та тіні, використовується принцип теплого та холодного кольору. У стандартній моделі освітлення дифузне освітлення всього лиш модулює базовий колір об'єкта. В Gooch shading інтенсивність дифузного освітлення визначає як повинні змішуватися теплий та холодний колір. Теплим кольором може бути жовтий, а холодним - синій. Бажано, щоб кольори буди трошки бляклими. Теплий і холодний кольори можна змішувати з кольором об'єкта у різних пропорціях.
    Різні теплі і холодні кольори в Gooch Shading
    Для Gooch shading може використовуватися такий самий вершинний шейдер як для штрихування. В наступному фрагментному шейдері розраховується та виводиться колір розрахований методом Gooch Shading, а також пояснюється як змішуються кольори. Також, колір розрахований з допомогою Gooch shading можна змішати з кольором штрихування. Така комбінація показана на третьому малюнку.
    #version 330

    // дані з вершинного шейдера
    in vec3 o_normal;
    in vec3 o_lightVector;
    in vec3 o_viewVector;
    in vec2 o_texcoords;

    // дифузний колір об'єкта
    uniform vec3 u_objectColor;
    // холодний колір об'єкта
    uniform vec3 u_coolColor;
    // теплий колір об'єкта
    uniform vec3 u_warmColor;
    // врахування базового кольору об'єкта для холодних частин об'єкта
    uniform float u_alpha;
    // врахування базового кольору об'єкта для теплих частин об'єкта
    uniform float u_beta;

    // колір, що виводиться у фрейбуфер
    out vec4 resultingColor;

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

    void main(void)
    {
       // нормалізуємо вектори для освітлення
       vec3 normalVector = normalize(o_normal);
       vec3 lightVector = normalize(o_lightVector);
       // інтерсивність дифузного освітлення [-1, 1]
       float diffuseLighting = dot(lightVector, normalVector);
       // приводимо інтенсивність освітлення з меж [-1; 1] до [0, 1]
       float = (1.0 + diffuseLighting)/2;

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

       // холодний колір з врахуванням кольору об'єкта
       vec3 coolColorMod = u_coolColor + u_objectColor * u_alpha;
       // теплий колір з врахуванням кольору об'єкта
       vec3 warmColorMod = u_warmColor + u_objectColor * u_beta;
       // інтерполяція теплого та холодного кольорів в
       // залежності від освітлення. Чим менша інтенсивність освітлення,
       // тим більше використовується холодного кольору

       vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);

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

       // збереження кольору
       resultingColor.rgb = colorOut;
       resultingColor.a = 1;
    }



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