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

Метод мапування нормалей

В попіксельному освітленні використовуються інтерпольовані по трикутнику нормалі, які передані з вершинного у фрагментний шейдер (затінення по Фонгу). Кожна інтерпольована нормаль є залежною від трьох нормалей, які відповідають вершинам трикутника. Метод мапування нормалей дозволяє замінити інтерпольовані однорідні нормалі в кожному фрагменті на унікальні нормалі, які створені дизайнером чи отримані з більш деталізованих 3D моделей. Зміна нормалі в кожному фрагменті призводить до зміни результатів освітлення. На наступному малюнку наведено модель з простими інтерпольованими нормалями та з нормалями, модифікованими методом мапування нормалей. Створюється відчуття, ніби модель є рельєфною, хоча насправді візуалізується гладка площина.
Просте освітлення + Карта нормалей = Освітлення з мапуванням нормалей

Інформація про те, яка повинна бути нормаль для кожної точки моделі передається у фрагментний шейдер з допомогою карти нормалей. В цій текстурі у кожному текселі збережено одну нормаль. Нормаль вибирається з текстури з допомогою тих самих текстурних координат, що використовуються для дифузної текстури (або інших). Нормалі в карті нормалей є збереженими в просторі дотичних зображення. Осі X та Y простору йдуть паралельно до зміни ширини та висоти зображення, а вісь Z є перпендикулярною зображенню, тобто напрямлена на нас. Формування карти нормалей описане у цій статті.
Для розрахунку освітлення всі вектори, які використовуються в рівнянні освітлення, повині бути в одному просторі. Але вектори у вершинному шейдері (наприклад, напрям від вершини до світла) є у просторі моделі, а прочитана нормаль з карти нормалей є у просторі дотичних зображення. Можна перетворити нормаль у простір моделі, але це перетворення потрібно виконувати у фрагментному шейдері (текстурні координати для вибірки необхідної нормалі ще невідомі у вершинному шейдері) для кожного фрагменту трикутника, що є дуже затратно. Можна перетворити усі необхідні для освітлення вектори у вершинному шейдері з простору моделі у простір дотичних, і це вимагає менше обчислень, бо виконується тільки для вершин.
Під простором дотичних у цьому випадку мається на увазі простір, який є дотичним до текструрних координат моделі. Кожна вершина має свій унікальний простір дотичних. Вісь Z є рівною нормалі у вершині, а осі X та Y відповідають напряму зміни текстурних координат вздовж осей S та T. Осі X та Y також називаються векторами дотичної та бідотичної.
Цей простір дотичних визначається трійкою векторів, які утворюють ортонормальний базис. Можна записати ці вектори у вигляді матриці розміром 3х3. Помноживши матрицю на вектор у системі координат моделі, ми перетворюємо вектор у простір дотичних. Нормаль вершини є відомою. Потрібно розрахувати вектор дотичної та бідотичної. У цій статті описано як розраховувати дотичні та бідотичні вектори вектори. Домноження на базис можна інтерпретувати як проектування на осі чи як поворот (матриця повороту, як і матриця виду, містить в собі три ортонормальні вектори системи координат в яку перетворюється вектор).
Перетворення вектора в простір дотичних
Після обчислення дотичних і бідотичних векторів, потрібно передати ці данні в шейдер, як вершинний атрибут (аналогічне створення буфера як для позиції чи нормалей). В статті про розрахунок дотичних векторів було зазначено, що вектор бідотичних не потрібно повністю зберігати, і його можна відновити з вектора нормалі, дотичного вектора та значення знаку детермінанта матриці базиса. Таким чином необхідно створити тільки один новий чотирьохкомпонентний атрибут, де перші три компоненти (xyz) відповідають вектору дотичних, а четвертий (w) – знаку детермінанта.
vec4 tangent; // attribute to vertex shader
tangent.xyz = tangentVector;
tangent.w   = determinant(Mto_tangent)
Бідотична відновлюється у вершинному шейдері за наступною формулою.
B = (N x T.xyz) * T.w
Складаємо з нормалі, дотичної та бідотичної матрицю перетворення у простір дотичних. Помноживши матрицю на вектори (напряму до світла, половинний вектор, відбитий та ін.), отримуємо ці вектори у просторі дотичних. Нові вектори передаються у фрагментний шейдер і одразу ж повинні бути нормалізовані. Вершинний шейдер наведений нище:
// VERTEX SHADER FOR NORMAL MAPPING
#version 330

// ATTRIBUTES
layout(location = 0) in vec3 i_position;
layout(location = 1) in vec3 i_normal;
layout(location = 2) in vec2 i_texcoord1;
layout(location = 3) in vec4 i_tangent; // xyz - tangent, w - sign

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

// TO FRAGMENT SHADER
out vec2 v_texcoord1;
out vec3 v_directionToLight;
out vec3 v_directionToCamera;

void main(void){
  vec4 worldPos = u_model_mat * vec4(i_position,1.0);
  gl_Position = u_proj_mat * u_view_mat * worldPos;

  // TRANSFORM NORMALS AND TANGENT WITH TO WORLD SPACE
  vec3 modelNormal = u_normal_mat * i_normal;
  vec3 modelTangent = u_normal_mat * i_tangent.xyz;

  // CALCULATE OUTPUTS IN WORLD SPACE
  v_texcoord1 = i_texcoord1;
  v_directionToLight = normalize(u_light_position-worldPos.xyz);
  v_directionToCamera = normalize(u_camera_position-worldPos.xyz);

  // RESTORE BITANGENT
  vec3 bitangent = i_tangent.w*cross(modelNormal,modelTangent.xyz);

  // TRANSFORM DIRECTION TO LIGHT AND TO CAMERA TO TANGENT SPACE
  v_directionToLight = vec3( dot(modelTangent.xyz, v_directionToLight),
          dot(bitangent, v_directionToLight),
          dot(modelNormal, v_directionToLight));

  v_directionToCamera = vec3( dot(modelTangent.xyz, v_directionToCamera),
          dot(bitangent,v_directionToCamera),
          dot(modelNormal, v_directionToCamera));
}
Нормаль, яку ми читаємо з текстури нормалей вже є у просторі дотичних. Так як вона закодована у значення кольору RGB (як згадувалося в статті про формування карти нормалей), то потрібно її відновити з інтервалу [0,255] в [-1,1]:
Відновлення нормалі з RGB
Освітлення у фрагментному шейдері розраховується повністю у просторі дотичних. Усі обчислення дифузного та дзеркального відбиття залишаються такими самими, тільки вектори є у іншому просторі. Рельєфність моделі можна масштабувати, змішуючи немодифіковану нормаль з нормаллю з карти нормалей. Немодифікована нормаль в просторі дотичних рівна (0,0,1).
// FRAGMENT SHADER FOR NORMAL MAPPING
#version 330

// UNIFORMS
uniform sampler2D u_colorTexture;
uniform sampler2D u_normalTexture;
uniform vec3 u_lightColor;

// FROM VERTEX SHADER
in vec2 v_texcoord1;
in vec3 v_directionToLight;
in vec3 v_directionToCamera;

void main(void){
  // RESTORE NORMAL FROM NORMAL TEXTURE
  vec3 norfromtex = normalize( texture(u_normalTexture, v_texcoord1).xyz * 2.0 - 1.0);

  // DECREASE DETAILS BY MIXING NORMAL WITH UNIFORM NORMAL
  float factor = 1;
  vec3 N = normalize(norfromtex*factor + vec3(0,0,1)*(1.0-factor));

  // PERFORM LIHGTING AS USUAL, BUT ALL VECTORS ARE IN TANGENT SPACE
  vec3 colfromtex = texture( u_colorTexture, v_texcoord1).xyz;
  vec3 L = v_directionToLight;
  vec3 H = normalize(v_directionToLight+v_directionToCamera);
  float idiff = diffuseSimple(N,L);
  float ispec = specularSimple(N,H);
  gl_FragColor.xyz = (vec3(iambi) + idiff + ispec)*colfromtex*u_lightColor;
  gl_FragColor.w = 1.0;
}
Метод рельєфного мапування (Bump mapping) використовується для аналогічної зміни нормалей в кожному фрагменті. Але замість передачі розрахованих нормалей у шейдер в формі текстури нормалей, у фрагментний шейдер передається текстура з висотами. Нормаль розраховується у шейдері аналогічно як для карти нормалей. Метод вимагає менше памяті ніж метод мапування нормалей, але є повільнішим, так як потребує 4+ вибірок з текстури висот для розрахунку нормалі в кожному фрагменті.



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