Вот мы и добрались уже до 13 урока – Normal Mapping.
В уроке №8, где говорилось про освещение, мы говорили про то,
как можно сделать затенение поверхностей с помощью нормалей треугольника. Была
лишь проблема в том, что каждая вершина могла иметь лишь одну нормаль: внутри
самого треугольника нормали интерполируются из нормалей трех окружающих вершин,
и мы ничего не могли с этим поделать, а вот цветом всегда можно было управлять
с помощью текстур. Идея normal mapping в том, чтобы дать нам такой же контроль
над нормалями.
Текстуры Нормалей
В каждом RGB текселе закодирован XYZ вектор: компоненты цветов
закодированы в интервале от 0 до 1, а компоненты вектора от -1 до 1, поэтому
нужно применить вот такую формулу, чтобы преобразовать цвет в нормаль:
normal = (2*color)-1
// такое нужно повторить на каждой компоненте вектора
В целом эта текстура голубого цвета, так как нормали
направлены «от поверхности».
Эта текстура маппится точно так же, как и диффузная, но
проблема в том, как же преобразовать нашу нормаль из индивидуального
пространства треугольника(тангенциальное пространство, или image space) в
пространство модели.
Касательная и Бикасательная
Так как вы уже знакомы с матрицами, то, я думаю, уже знаете,
что для того, чтобы задать новое пространство(а в данной ситуации
тангенциальное пространство), нам нужно 3 вектора. Вектор UP уже есть: это
нормаль, которую можно вычислить с помощью векторного произведения на
треугольнике. На текстуре нормалей она представлена синим цветом:
Теперь нам нужна касательная Т: вектор параллельный
поверхности. Однако таких векторов может быть сколько угодно:
Какой же вектор нам выбрать? Теоретически, можно выбрать
любой, однако выбрав любой вектор на одной поверхности нужно выбирать такой же
и на всех остальных. Обычная практика для ориентации касательной – направлять
её в ту же сторону, что и текстурные координаты:
Так как нам нужно 3 вектора, чтобы задать базис
пространства, мы должны задать бикасательную- вторую касательную.
А вот и алгоритм: заметьте - deltaPos1 и deltaPos2, это
длины двух сторон прямоугольника, а deltaUV1 и deltaUV2 – разница в координатах
UV:
deltaPos1 = deltaUV1.x
* T + deltaUV1.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B
Нужно лишь решить эту систему для T и B, и у нас будут
необходимые вектора!
Как только у нас появятся вектора T,B,N, то с помощью очень
удобной матрицы мы сможем переходить от тангенциального пространства в
пространство модели:
С помощью этой TBN матрицы можно будет трансформировать
нормали(доставать их из текстуры) в пространство модели. Однако, обычно все
делается наоборот – все трансформируется из пространства модели в
тангенциальное пространство и нормали берутся из текстуры просто так. Все
вычисления можно проводить и в тангенциальном пространстве.
Чтобы сделать это обратное преобразование нам нужно
инвертировать матрицу, что в нашем случае простой ортогональной матрицы, равно
вычислительно гораздо дешевому транспонированию:
Готовим наш VBO
Вычисление касательных и бикасательных
Так как нам для наших расчетов нужны касательные и
бикасательные, нам придется посчитать их для всего меша. Давайте вынесем этот
расчет в отдельную функцию:
void
computeTangentBasis(
// входные параметры
std::vector<glm::vec3> & vertices,
std::vector<glm::vec2> & uvs,
std::vector<glm::vec3> & normals,
// выходные параметры
std::vector<glm::vec3> & tangents,
std::vector<glm::vec3> & bitangents
){
// входные параметры
std::vector<glm::vec3> & vertices,
std::vector<glm::vec2> & uvs,
std::vector<glm::vec3> & normals,
// выходные параметры
std::vector<glm::vec3> & tangents,
std::vector<glm::vec3> & bitangents
){
Для каждой грани нам необходимо подсчитать сторону(deltaPos)
и deltaUV:
for ( int i=0; i<vertices.size(); i+=3){
// копируем значения в переменные
glm::vec3 & v0 = vertices[i+0];
glm::vec3 & v1 = vertices[i+1];
glm::vec3 & v2 = vertices[i+2];
// копируем значения в переменные
glm::vec2 & uv0 = uvs[i+0];
glm::vec2 & uv1 = uvs[i+1];
glm::vec2 & uv2 = uvs[i+2];
// стороны треугольника
glm::vec3 deltaPos1 = v1-v0;
glm::vec3 deltaPos2 = v2-v0;
// дельта UV
glm::vec2 deltaUV1 = uv1-uv0;
glm::vec2 deltaUV2 = uv2-uv0;
// копируем значения в переменные
glm::vec3 & v0 = vertices[i+0];
glm::vec3 & v1 = vertices[i+1];
glm::vec3 & v2 = vertices[i+2];
// копируем значения в переменные
glm::vec2 & uv0 = uvs[i+0];
glm::vec2 & uv1 = uvs[i+1];
glm::vec2 & uv2 = uvs[i+2];
// стороны треугольника
glm::vec3 deltaPos1 = v1-v0;
glm::vec3 deltaPos2 = v2-v0;
// дельта UV
glm::vec2 deltaUV1 = uv1-uv0;
glm::vec2 deltaUV2 = uv2-uv0;
А теперь можно применить выведенную ранее формулу для
вычисления касательной и бикасательной:
float r = 1.0f / (deltaUV1.x *
deltaUV2.y - deltaUV1.y * deltaUV2.x);
glm::vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y)*r;
glm::vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 *
deltaUV2.x)*r;
glm::vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y)*r;
glm::vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 *
deltaUV2.x)*r;
И наконец, заполняем буферы касательных и бикасательных. Так
как эти буферы еще не индексированы, то каждая вершина будет иметь свою
собственную копию:
// Установим одну и ту
же касательную для всех вершин треугольника.
// мы их объединим позже в vboindexer.cpp
tangents.push_back(tangent);
tangents.push_back(tangent);
tangents.push_back(tangent);
// То же самое и для бинормалей
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);
}
// мы их объединим позже в vboindexer.cpp
tangents.push_back(tangent);
tangents.push_back(tangent);
tangents.push_back(tangent);
// То же самое и для бинормалей
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);
bitangents.push_back(bitangent);
}
Индексация
Индексация этого буфера почти такая же, как и та, которую мы
уже делали, но с одним отличием.Если мы нашли одинаковые вершины(одна и та же позиция,
одна и та же нормаль и текстурные координаты), нам не нужно применять на неё
такую же нормаль, нам нужно усреднить нормали этих вершин. Поэтому возьмем наш
старый код и слегка его подправим:
// Пробуем найти
одинаковую вершину
unsigned int index;
bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i], out_vertices, out_uvs, out_normals, index);
unsigned int index;
bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i], out_vertices, out_uvs, out_normals, index);
if ( found ){ // Одинаковая
вершина уже в VBO, используем её !
out_indices.push_back( index );
// Усредняем нормаль и бинормаль
out_tangents[index] += in_tangents[i];
out_indices.push_back( index );
// Усредняем нормаль и бинормаль
out_tangents[index] += in_tangents[i];
out_bitangents[index] += in_bitangents[i];
}else{ // Если не нашли, то добавляем
// Все как и раньше
[...]
}
}else{ // Если не нашли, то добавляем
// Все как и раньше
[...]
}
Обратите внимание, что мы ничего не нормализируем. И это
правильно, тем самым маленькие треугольники будут иметь маленькую нормаль,
касательную и бикасательную, и тем самым будет давать меньший эффект чем
большой треугольник.
Шейдер
Дополнительные буферы
и константы
Нам нужно два дополнительных буфера: один для касательных, а
второй для бикасательных:
GLuint tangentbuffer;
glGenBuffers(1, &tangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3),
&indexed_tangents[0], GL_STATIC_DRAW);
GLuint bitangentbuffer;
glGenBuffers(1, &bitangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof
glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);
glGenBuffers(1, &tangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3),
&indexed_tangents[0], GL_STATIC_DRAW);
GLuint bitangentbuffer;
glGenBuffers(1, &bitangentbuffer);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof
glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);
Так же необходим еще один uniform для текстуры нормали:
[...]
GLuint NormalTexture = loadTGA_glfw("normal.tga");
[...]
GLuint NormalTextureID = glGetUniformLocation(programID,
"NormalTextureSampler");
GLuint NormalTexture = loadTGA_glfw("normal.tga");
[...]
GLuint NormalTextureID = glGetUniformLocation(programID,
"NormalTextureSampler");
А еще один для матрицы ModelView 3*3. Теоретически без
последнего можно было бы и обойтись, но с ней легче, но об этом позже. Нам
необходима будет лишь верхняя левая часть этой матрицы, так как мы будем делать
лишь умножение направлений:
GLuint ModelView3x3MatrixID =
glGetUniformLocation(programID, "MV3x3");
Полный код будет выглядеть так:
// Очищаем
экран
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Включаем шейдер
glUseProgram(programID);
// Вычисляем MVP матрицу
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
glm::mat4 ModelMatrix = glm::mat4(1.0);
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix); // Берем левую верхнюю часть матрицы ModelViewMatrix
glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
// Отправляем трансформацию в текущий шейдер, в uniform ”MPV”
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);
glm::vec3 lightPos = glm::vec3(0,0,4);
glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);
// Биндим диффузную текстуру в слот 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
// Устанавливаем "DiffuseTextureSampler" сэмплер на слот 0
glUniform1i(DiffuseTextureID, 0);
// Биндим текстуру нормалей на слот 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, NormalTexture);
// Устанавливаем "Normal TextureSampler" сэмплер на слот 1
glUniform1i(NormalTextureID, 1);
// первый буферный атрибут - вершины
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
0, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение в буфере
);
// второй атрибут : UV координаты
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(
1, // атрибут
2, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение
);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Включаем шейдер
glUseProgram(programID);
// Вычисляем MVP матрицу
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
glm::mat4 ModelMatrix = glm::mat4(1.0);
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix); // Берем левую верхнюю часть матрицы ModelViewMatrix
glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
// Отправляем трансформацию в текущий шейдер, в uniform ”MPV”
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);
glm::vec3 lightPos = glm::vec3(0,0,4);
glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);
// Биндим диффузную текстуру в слот 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
// Устанавливаем "DiffuseTextureSampler" сэмплер на слот 0
glUniform1i(DiffuseTextureID, 0);
// Биндим текстуру нормалей на слот 1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, NormalTexture);
// Устанавливаем "Normal TextureSampler" сэмплер на слот 1
glUniform1i(NormalTextureID, 1);
// первый буферный атрибут - вершины
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
0, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение в буфере
);
// второй атрибут : UV координаты
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(
1, // атрибут
2, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение
);
// третий
атрибут - нормали
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение в буфере
);
// Четвертый атрибут - касательные
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glVertexAttribPointer(
3, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение
);
// Пятый атрибут - Бикасательные
glEnableVertexAttribArray(4);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glVertexAttribPointer(
4, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0,
(void*)0 // смещение
);
// Индексный буфер
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
// Рисуем треугольники !
glDrawElements(
GL_TRIANGLES, // режим
indices.size(), // количество
GL_UNSIGNED_INT, // тип
(void*)0 // смещение
);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
glDisableVertexAttribArray(4);
// переключаем буферы(показываем на экран)
glfwSwapBuffers();
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение в буфере
);
// Четвертый атрибут - касательные
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
glVertexAttribPointer(
3, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение
);
// Пятый атрибут - Бикасательные
glEnableVertexAttribArray(4);
glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
glVertexAttribPointer(
4, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0,
(void*)0 // смещение
);
// Индексный буфер
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
// Рисуем треугольники !
glDrawElements(
GL_TRIANGLES, // режим
indices.size(), // количество
GL_UNSIGNED_INT, // тип
(void*)0 // смещение
);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
glDisableVertexAttribArray(4);
// переключаем буферы(показываем на экран)
glfwSwapBuffers();
Вершинный шейдер
Как я уже говорил, мы все будем делать в пространстве
камеры, так как тут гораздо проще доставать позиции фрагментов. Именно поэтому
мы умножвем наши векторы T,B,N на матрицу ModelView.
vertexNormal_cameraspace = MV3x3 *
normalize(vertexNormal_modelspace);
vertexTangent_cameraspace = MV3x3 * normalize(vertexTangent_modelspace);
vertexBitangent_cameraspace = MV3x3 * normalize
vertexBitangent_modelspace);
vertexTangent_cameraspace = MV3x3 * normalize(vertexTangent_modelspace);
vertexBitangent_cameraspace = MV3x3 * normalize
vertexBitangent_modelspace);
Эти три вектора определяют TBN матрицу которая
конструируется таким образом:
mat3 TBN = transpose(mat3(
vertexTangent_cameraspace,
vertexBitangent_cameraspace,
vertexNormal_cameraspace
));
Можно использовать векторное произведение вместо построения этой матрицы и транспонирования её.
vertexTangent_cameraspace,
vertexBitangent_cameraspace,
vertexNormal_cameraspace
));
Можно использовать векторное произведение вместо построения этой матрицы и транспонирования её.
Эта матрица поможет вам переходить от пространства камеры в
тангенциальное пространство(Такая же матрица, но с XXX_modelspace может помочь
в переходе от пространства модели в тангенциальное пространство). Мы можем
использовать её для того, чтобы вычислять направление света и взгляда в
тангенциальном пространстве:
LightDirection_tangentspace
= TBN * LightDirection_cameraspace;
EyeDirection_tangentspace = TBN * EyeDirection_cameraspace;
EyeDirection_tangentspace = TBN * EyeDirection_cameraspace;
Фрагментный шейдер
Наши нормали в тангенциальном пространстве получить очень
просто – берем их просто из текстуры:
// локальная нормаль в
тангенциальном пространстве
vec3 TextureNormal_tangentspace = normalize(texture2D(NormalTextureSampler, UV ).rgb*2.0 - 1.0);
vec3 TextureNormal_tangentspace = normalize(texture2D(NormalTextureSampler, UV ).rgb*2.0 - 1.0);
Теперь-то у нас есть все что нужно. Диффузное освещение
использует clamp(dot(n,l),0,1) где n
и l выражены в
тангенциальном пространстве(совсем не важно в каком пространстве делать
векторное произведение, главное, чтобы оба вектора были в одних и тех же
пространствах). Свет отблесков использует clamp( dot( E,R ), 0,1 ), где, опять
же, E, R выражены в тангенциальном пространстве.
Результат
А вот и результат:
Заметьте:
- Кирпичи выглядят выпуклыми, так как нормали смотрят в разные стороны.
- Цемент выглядит ровным, так как в этом месте текстура нормалей синего цвета и почти без вариаций.
Идем вглубь
Ортогонализация
Если вы помните, в вертексном шейдере мы использовали
ортогонализацию вместо инвертирования, так как это быстрее. Но это работает
лишь в том случае, если матрица, которая представляет собой пространство,
ортогональная. Однако это не всегда так. К счастью это легко поправить: нужно
просто сделать касательную перпендикулярной к нормали в конце функции computeTangentBasis():
t = glm::normalize(t - n * glm::dot(n, t));
Понять смысл этой формулы не так-то и легко, поэтому вот вам схемка:
Понять смысл этой формулы не так-то и легко, поэтому вот вам схемка:
Направленность
Обычно нам не нужно заморачиваться этим, но иногда, когда мы
используем симметричные модели, UV координаты направлены в другую сторону, и
вектор T направлен не туда.
Чтобы проверить, инвертирован ли он, или нет, можно сделать
следующий простенький трюк: TBN должен формировать правостороннюю систему
координат, и векторное произведение n,t должно давать вектор той же
направленности, что и b.
Математика говорит «Вектор А направлен в ту же сторону, что
и вектор Б, в том случае, если их скалярное произведение больше 0». Поэтому
можно проверить
if dot( cross(n,t) , b ) > 0.
А если условие не выполняется, то нужно просто инвертировать
t:
if (glm::dot(glm::cross(n, t), b) < 0.0f ){
t = t *-1.0f ;
}
t = t *
}
Specular текстура
Просто ради интереса, давайте добавим еще немного отблесков
с помощью specular текстуры. Она может выглядеть, например, вот так:
И её можно использовать вместо нашего вектора “vec3(0.3,0.3,0.3)”
серого цвета который мы использовали для цвета отблесков в одном из прошлых
уроков.
Обратите внимание, что цемент теперь полностью черный:
текстура говорит, что в этом месте поверхность не имеет отражающей
составляющей.
Отладка
Вообще, я не рекомендую использовать immediate режим, так
как он очень медленный, глючный и вообще, его могут убрать в последующих
версиях.
На рисунке показано, как мы визуализируем наше
тангенциальное пространство с помощью линий.
Для того, чтобы включить immediate режим, необходимо
отключить профиль 3.3:
glfwOpenWindowHint(GLFW_OPENGL_PROFILE,
GLFW_OPENGL_COMPAT_PROFILE);
А потом пропустить наши матрицы через устаревший OpenGL
пайплайн(вы, конечно, могли бы написать свои собственные шейдеры, но так
проще):
glMatrixMode(GL_PROJECTION);
glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
glMatrixMode(GL_MODELVIEW);
glm::mat4 MV = ViewMatrix * ModelMatrix;
glLoadMatrixf((const GLfloat*)&MV[0]);
glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
glMatrixMode(GL_MODELVIEW);
glm::mat4 MV = ViewMatrix * ModelMatrix;
glLoadMatrixf((const GLfloat*)&MV[0]);
Отключаем шейдеры:
glUseProgram(0);
и рисуем наши линии(в данном случае нормали,
нормализированные и умноженные на 0.1, и примененные к каждой вершине):
glColor3f(0,0,1);
glBegin(GL_LINES);
for (int i=0; i<indices.size(); i++){
glm::vec3 p = indexed_vertices[indices[i]];
glVertex3fv(&p.x);
glm::vec3 o = glm::normalize(indexed_normals[indices[i]]);
p+=o*0.1f;
glVertex3fv(&p.x);
}
glEnd();
glBegin(GL_LINES);
for (int i=0; i<indices.size(); i++){
glm::vec3 p = indexed_vertices[indices[i]];
glVertex3fv(&p.x);
glm::vec3 o = glm::normalize(indexed_normals[indices[i]]);
p+=o*0.1f;
glVertex3fv(&p.x);
}
glEnd();
Не стоит использовать этот режим в обычной жизни, лишь для
отладки!
Отладка с помощью цветов
При отладке, может быть полезным визуализировать значение
вектора с помощью цвета. Самый простой способ сделать это- записывать этот
вектор во фрейбуфер вместо самого цвета. Давайте, например, отобразим вектор LightDirection_tangentspace:
color.xyz = LightDirection_tangentspace;
color.xyz = LightDirection_tangentspace;
Давайте разберем, что мы тут видим:
- На правой стороне цилиндра, свет(который рисуется узкой белой полосой), это UP в тангенциальном пространстве. Другими словами, свет идет в направлении нормали треугольника.
- В средней части цилиндра, свет направлен в сторону касательной(по Х+)
Несколько советов:
- В зависимости от того, что вы хотите отобразить, сначала это нужно нормализировать.
- Если вы не можете понять, что вы видите, попробуйте обнулить, например, зеленую и синюю компоненту, а потом красную, а оставить лишь зеленую, а потом лишь синюю.
- Старайтесь не мудрить ничего с альфой. С прозрачностью все может лишь усложниться.
- Если вам нужно визуализировать отрицательные значения, то можно попробовать применить тот же трюк, что мы делали с нашей текстурой нормалей: visualize (v+1.0)/2.0.
Как я уже говорил ранее, очень важно точно знать в каком
пространстве находятся вектора. Нельзя делать скалярное произведение вектора в
пространстве камеры и вектора в пространстве модели.
Очень помогает добавление префикса «…_modelspace» в конце
названий переменных.
Упражнения
- Нормализируйте вектор в indexVBO_TBN прежде чем добавлять его, и посмотрите что получится.
- Визуализируйте другие вектора(например, EyeDirection_tangentspace) в цветовом режиме, и постарайтесь понять, что будут означать все эти разноцветные пятнышки.
Инструменты и Ссылки
- Crazybump, хорошая программа для создания карт нормалей. Платная.
- Nvidia’s photoshop plugin. Бесплатный, а вот фотошоп нет…
- Делаем карту нормалей из нескольких фоток.
- Делаем карту нормалей из одной фотки.
Комментариев нет:
Отправить комментарий