Autor Wątek: Bi/TangentMap - czy tak powinna wyglądać?  (Przeczytany 1168 razy)

Offline Montjet

  • Użytkownik

# Wrzesień 11, 2016, 23:03:48
Witam,
zaprogramowałem w OpenGL renderowanie terenu (przesuwa się z kamerą - chunk x zawsze będzie po środku kamery) - tylko wysokość się zmienia (obliczam texcoord height mapy "w biegu").
Problemy jakie napotkałem to globalne normal'ne oraz tangent'y - chyba nie mam innego wyjścia jak zrobić z nich mapy.

Zaprogramowałem więc prosty konwerter height map'y do tangent map'y.
Niestety nie wiem czy powinna tak ona wyglądać?
Po lewej tangent map'a, na środku bitangent, a po prawej height map'a (jakaś przykładowa z internetu pobrana).


Współrzędna Z to wysokość terenu - maksymalne Z (w tym projekcie) to 200.0.
Kod odpowiedzialny za tę mapę:
math::Vector3f* tangent = new math::Vector3f[bitmap.Width() * bitmap.Height()];
math::Vector3f* bitangent = new math::Vector3f[bitmap.Width() * bitmap.Height()];
for (unsigned i = 0; i < bitmap.Width() * bitmap.Height(); ++i)
{
tangent[i] = math::Vector3f(0.0, 0.0, 0.0);
bitangent[i] = math::Vector3f(0.0, 0.0, 0.0);
}

float stepTexCoord = 1.0f / static_cast<float>(bitmap.Width());
for (GLuint y = 0; y < bitmap.Height() - 1; ++y)
for (GLuint x = 0; x < bitmap.Width() - 1; ++x)
{
math::Vector3f v1 = math::Vector3f(x, y, static_cast<float>(bitmap.GetPixels(x, y).rgbBlue) * MaxHeight / 255);
math::Vector3f v2 = math::Vector3f(x, y + 1, static_cast<float>(bitmap.GetPixels(x, y + 1).rgbBlue) * MaxHeight / 255);
math::Vector3f v3 = math::Vector3f(x + 1, y, static_cast<float>(bitmap.GetPixels(x + 1, y).rgbBlue) * MaxHeight / 255);

math::Vector3f v4 = math::Vector3f(x + 1, y + 1, static_cast<float>(bitmap.GetPixels(x + 1, y + 1).rgbBlue) * MaxHeight / 255);

math::Vector3f Edge1 = v2 - v1;
math::Vector3f Edge2 = v3 - v1;

math::Vector3f Edge3 = v2 - v4;
math::Vector3f Edge4 = v3 - v4;

math::Vector2f TexC1 = math::Vector2f(x * stepTexCoord, y * stepTexCoord);
math::Vector2f TexC2 = math::Vector2f(x * stepTexCoord, (y + 1) * stepTexCoord);
math::Vector2f TexC3 = math::Vector2f((x + 1) * stepTexCoord, y * stepTexCoord);

math::Vector2f TexC4 = math::Vector2f((x + 1) * stepTexCoord, (y + 1) * stepTexCoord);

math::Vector2f Delta1 = TexC2 - TexC1;
math::Vector2f Delta2 = TexC3 - TexC1;

math::Vector2f Delta3 = TexC2 - TexC4;
math::Vector2f Delta4 = TexC3 - TexC4;

float f = 1.0f / (Delta1.x() * Delta2.y() - Delta2.x() * Delta1.y());
float f2 = 1.0f / (Delta3.x() * Delta4.y() - Delta4.x() * Delta3.y());

math::Vector3f Tangent, Bitangent;
Tangent.x() = f * (Delta2.y() * Edge1.x() - Delta1.y() * Edge2.x());
Tangent.y() = f * (Delta2.y() * Edge1.y() - Delta1.y() * Edge2.y());
Tangent.z() = f * (Delta2.y() * Edge1.z() - Delta1.y() * Edge2.z());

Bitangent.x() = f * (-Delta2.x() * Edge1.x() - Delta1.x() * Edge2.x());
Bitangent.y() = f * (-Delta2.x() * Edge1.y() - Delta1.x() * Edge2.y());
Bitangent.z() = f * (-Delta2.x() * Edge1.z() - Delta1.x() * Edge2.z());

math::Vector3f Tangent2, Bitangent2;
Tangent2.x() = f2 * (Delta4.y() * Edge3.x() - Delta3.y() * Edge4.x());
Tangent2.y() = f2 * (Delta4.y() * Edge3.y() - Delta3.y() * Edge4.y());
Tangent2.z() = f2 * (Delta4.y() * Edge3.z() - Delta3.y() * Edge4.z());

Bitangent2.x() = f2 * (-Delta4.x() * Edge3.x() - Delta3.x() * Edge4.x());
Bitangent2.y() = f2 * (-Delta4.x() * Edge3.y() - Delta3.x() * Edge4.y());
Bitangent2.z() = f2 * (-Delta4.x() * Edge3.z() - Delta3.x() * Edge4.z());

tangent[x + y * bitmap.Width()] += Tangent;
tangent[x + (y + 1) * bitmap.Width()] += Tangent;
tangent[x + 1 + y * bitmap.Width()] += Tangent;

tangent[x + 1 + (y + 1) * bitmap.Width()] += Tangent2;
tangent[x + (y + 1) * bitmap.Width()] += Tangent2;
tangent[x + 1 + y * bitmap.Width()] += Tangent2;

bitangent[x + y * bitmap.Width()] += Bitangent;
bitangent[x + (y + 1) * bitmap.Width()] += Bitangent;
bitangent[x + 1 + y * bitmap.Width()] += Bitangent;

bitangent[x + 1 + (y + 1) * bitmap.Width()] += Bitangent2;
bitangent[x + (y + 1) * bitmap.Width()] += Bitangent2;
bitangent[x + 1 + y * bitmap.Width()] += Bitangent2;
}
for (unsigned i = 0; i < bitmap.Width() * bitmap.Height(); ++i)
{
tangent[i].normalize();
bitangent[i].normalize();
}

BYTE* pixels = new BYTE[bitmap.Width() * bitmap.Height() * 3];
for (GLuint y = 0; y < bitmap.Height(); ++y)
for (GLuint x = 0; x < bitmap.Width(); ++x)
{
pixels[(x + y * bitmap.Width()) * 3] = static_cast<BYTE>(tangent[x + y * bitmap.Width()].x() * 255.0);
pixels[(x + y * bitmap.Width()) * 3 + 1] = static_cast<BYTE>(tangent[x + y * bitmap.Width()].y() * 255.0);
pixels[(x + y * bitmap.Width()) * 3 + 2] = static_cast<BYTE>(tangent[x + y * bitmap.Width()].z() * 255.0);
}


Bitmap::Save(dst, bitmap.Width(), bitmap.Height(), pixels, 24u, bitmap.ImageFormat());
delete[] pixels;

pixels = new BYTE[bitmap.Width() * bitmap.Height() * 3];
for (GLuint y = 0; y < bitmap.Height(); ++y)
for (GLuint x = 0; x < bitmap.Width(); ++x)
{
pixels[(x + y * bitmap.Width()) * 3] = static_cast<BYTE>(bitangent[x + y * bitmap.Width()].x() * 255.0);
pixels[(x + y * bitmap.Width()) * 3 + 1] = static_cast<BYTE>(bitangent[x + y * bitmap.Width()].y() * 255.0);
pixels[(x + y * bitmap.Width()) * 3 + 2] = static_cast<BYTE>(bitangent[x + y * bitmap.Width()].z() * 255.0);
}


Bitmap::Save(dst2, bitmap.Width(), bitmap.Height(), pixels, 24u, bitmap.ImageFormat());
delete[] pixels;
delete[] tangent;
delete[] bitangent;

#edit
Zrobiłem kilka zmian - przed dodaniem lokalnych ( w bierzącej pętli) tangent i bitangent do finalnych znormalizowałem je oraz przy tworzeniu wartości składowych bitmapy przekształciłem wartości z <-1, 1> na <0, 1> w efekcie otrzymałem takie mapy:

« Ostatnia zmiana: Wrzesień 12, 2016, 23:11:34 wysłana przez Montjet »

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Avaj

  • Użytkownik

  • +2
# Wrzesień 12, 2016, 19:40:56
Po co ci normalmapa dla heightmapy? I po co tangenty/bitangenty jeśli teren jest statyczny?

Offline Montjet

  • Użytkownik

# Wrzesień 12, 2016, 22:53:24
Źle to ująłem - współrzędne X i Y zmieniają się z pozycją kamery, czyli przykładowo chunk 5 zawsze będzie po środku kamery.
Zmienia się współrzędna Z (wysokość mapy) :
uniform vec2 camPos;
...
vec2 position = vec2(pos + camPos);
vec2 texCoord = abs(position / terrainInfo.x);
vec2 intTexCoord = vec2(floor(texCoord.x), floor(texCoord.y));
globalCoord = texCoord - intTexCoord;
...
height = texture(heightMap, vec3(globalCoord, indexMap)).a*terrainInfo.w;

oraz wyliczam indeks height mapy dla każdego wierzchołka z osobna w shaderze.

Globalne normalne, tangenty i bitangenty terenu potrzebne mi są do normal mapy tekstur, zgodnie z tym kursem:
http://ogldev.atspace.co.uk/www/tutorial26/tutorial26.html

#edit
Ewidentnie jest coś źle wyliczane (w tym przypadku normalne):

Zielonymi strzałkami zaznaczyłem przyrost wysokości mapy,
czerwonymi kółkami normalne, które chowają się za mapą (jest ich niewiele ale jednak jest),
czarnymi kółkami normalne które mają zły kierunek - czerwony kolor normalnej dotyka podłoża mapy, a niebieski jest ponad nią.
« Ostatnia zmiana: Wrzesień 12, 2016, 23:29:06 wysłana przez Montjet »

Offline mihu

  • Użytkownik
    • mihu

  • +1
# Wrzesień 13, 2016, 01:52:40
Nie bardzo rozumiem ten algorytm - być może jest poprawny, ale nie wiem skąd to się wzięło. W każdym razie wynikowe tekstury wyglądają dość dziwnie.

Moim zdaniem, jeżeli heightmapa jest dana funkcją f(x,y), to:
tangent(x, y) = (1, 0, (df/dX)(x, y)), bitangent(x, y) = (0, 1, (df/dY)(x, y)), a wektor normalny to ich iloczyn wektorowy (wszystko oczywiście znormalizowane).
Pochodne cząstkowe możesz przybliżyć metodą różnic skończonych - np. centralnych:
(df/dX)(x, y) = (f(x+1, y) - f(x-1, y)) / 2dX

Nie musisz tego liczyć do tekstury, możesz to policzyć w vertex shaderze (samplując kilkukrotnie teksturę mapy wysokości w sąsiednich punktach) i przekazując dalej do pixel shadera.