Tworzenie gry w C# z użyciem silnika Ogre - cz.7

21.07.2011 - Mateusz Osowski
TrudnośćTrudność

Mapowanie wypukłości (Bump mapping)

Mapowanie wypukłości jest ogólną nazwą dla technik, które pozwalają odwzorować zniekształcenia geometrii nie ingerując w nią. Najprostszą z tych technik jest mapowanie wektorów normalnych (normal mapping). W poprzedniej implementacji oświetlenia pobieraliśmy wektor normalny na wejściu vertex-programu. Każdy wierzchołek posiadał swój wektor normalny. W normal mappingu informację o wektorze normalnym pobieramy z dodatkowej tekstury. Wektor normalny jest zakodowany jako kolor - trzy składowe, r,g,b z wartościami w przedziale [0,1] przedstawiają wektor ze współrzędnymi z wartościami w przedziale [-1,1], przykładowo wektor (0,0,1) jest zakodowany jako kolor (0.5,0.5,1), czyli taki fiołkowy.



W przypadku, gdy tekstura będzie wykorzystana wielokrotnie, na różnych ścianach, nie da na niej odwzorować wektorów normalnych powierzchni obiektu w stosunku 1:1. Posiłkujemy się wtedy klasycznym wektorem normalnym z geometrii (tym, którego używaliśmy przy najprostszym oświetlaniu), a wektor zapisany w teksturze jest niezależny od powierzchni. Mamy wtedy do czynienia z tzw. tangent space normal mapping. Ponieważ wektor normalny z tekstury jest niezależny, musimy przekształcić wektor kierunku światła do bazy przestrzeni stycznej do powierzchni wobec której chcemy, aby wektor był względny (czyli lokalnego zestawu trzech osi) . Bazę tą wyznaczają trzy ortogonalne wektory-osie: wektor normalny, wektor styczny i wektor binormalny. Wektor normalny dostajemy bezpośrednio od silnika. Wektor styczny wymaga obliczenia, przebiega on wzdłuż krawędzi mapowanej tekstury, na szczęście robi to za nas konwerter plików modeli. Wektor binormalny jest iloczynem wektorowym poprzednich dwóch wektorów.

Programy cieniujące

Zaczniemy od vertex-programu. Ustalmy wejście i wyjście:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct VertexIn
{
  float4 Position : POSITION;
  float2 TexCoord : TEXCOORD0;
  float3 Normal : NORMAL;
  float3 Tangent : TANGENT;
};
 
struct VertexOut
{
  float4 Position : POSITION;
  float2 TexCoord : TEXCOORD0;
  float3 LightDir : TEXCOORD1;
};
SimpleBumpVP.cg
Program na wejściu przyjmuje dodatkowy atrybut wierzchołka - wektor styczny na semantyce TANGENT. Na wyjściu nie przekazujemy już wektora normalnego, ponieważ będziemy go odczytywać we fragment-programie z tekstury. LightDir jest kierunkiem światła w przestrzeni stycznej do wierzchołka.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VertexOut vertex_func(VertexIn In,
  uniform mat4x4 WorldViewProj,
  uniform float4 LightPosition
        )
{
  VertexOut Out;
  Out.Position = mul(WorldViewProj, In.Position);       
  Out.TexCoord = In.TexCoord;
 
  float3 lightDir = normalize(LightPosition.xyz -  (In.Position * LightPosition.w));
  float3 binormal = cross(In.Tangent, In.Normal);
  float3x3 tbnMatrix = float3x3(In.Tangent, binormal, In.Normal);
  Out.LightDir = mul(tbnMatrix, lightDir);
 
  return Out;
}
SimpleBumpVP.cg


Tym razem zapisujemy kierunek światła w przestrzeni obiektu do zmiennej lokalnej, by przekształcić go do przestrzeni stycznej. Macierz zmiany bazy tworzymy we wierszu 12 z odpowiednich wektorów.

Fragment-program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct FragmentIn
{
  float2 TexCoord : TEXCOORD0;
  float3 LightDir : TEXCOORD1;
};
 
struct FragmentOut
{
  float4 Color : COLOR;
};
 
FragmentOut fragment_func(FragmentIn In,
  uniform sampler2D DiffuseMap : TEXUNIT0,
  uniform sampler2D NormalMap : TEXUNIT1
) 
{
  FragmentOut Out;
  float3 normal = (tex2D(NormalMap, In.TexCoord) - 0.5) * 2.0;
  Out.Color = tex2D(DiffuseMap, In.TexCoord) * dot(normal, In.LightDir);
  return Out;
}
SimpleBumpFP.cg


W odróżnieniu od poprzedniego fragment-programu tym razem korzystamy z tekstury do określenia koloru, a także pobieramy wektor normalny z drugiej tekstury (NormalMap). Tekstury z mapą wektorów normalnych oczekujemy na drugiej jednostce teksturującej (TEXUNIT1).

Zastosowanie

Aby przetestować działanie nowego shadera musimy posiadać model z wygenerowanymi wektorami stycznymi oraz tekstur:

Nowy model i shadery

W materiale musimy uwzględnić mapę wektorów normalnych:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
material Man/TEXFACE/Armour.png
{
  receive_shadows on
  technique
  {
    pass
    {
      vertex_program_ref VertexProgram/SimpleBump
      {
       param_named_auto WorldViewProj worldviewproj_matrix
       param_named_auto LightPosition light_position_object_space 0 
      }
      fragment_program_ref FragmentProgram/SimpleBump
      {
      }
      texture_unit
      {
       texture Armour.png
      }
      texture_unit
      {
        texture ArmourBump.png
      }
    }
  }
}
Man.material


Od teraz na siatce postaci powinna jawić się tekstura, a także dodatkowe detale:





Jak skrócić pliki materiałów?

Nie trudno zauważyć, że pliki materiałów szybko się rozrastają wraz ze stopniem skomplikowania użytych programów. Ogre obsługuje jednak dziedziczenie materiałów. Aby nie powielać kodu powyższego skryptu na wielu materiałach, wystarczy utworzyć szablonowy materiał:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
material Template/SimpleBump
{
  receive_shadows on
  technique
  {
    pass
    {
      vertex_program_ref VertexProgram/SimpleBump
      {
       param_named_auto WorldViewProj worldviewproj_matrix
       param_named_auto LightPosition light_position_object_space 0 
      }
      fragment_program_ref FragmentProgram/SimpleBump
      {
      }
      texture_unit DiffuseMap
      {
       texture Armour.png
      }
      texture_unit NormalMap
      {
        texture ArmourBump.png
      }
    }
  }
}
Templates.material


Nazwaliśmy jednostki teksturujące, aby móc się do nich odnosić przy dziedziczeniu:

1
2
3
4
5
material Man/TEXFACE/Armour.png : Template/SimpleBump
{
  set_texture_alias DiffuseMap Armour.png
  set_texture_alias NormalMap ArmourBump.png
}
Man.material


Skróciliśmy tym samym plik materiału postaci ponad pięciokrotnie.

Wszystko to osiągnęliśmy bez dokonywania jakichkolwiek zmian w kodzie źródłowym gry. Oczywiście używane przez nas programy cieniujące są bardzo proste i nie uwzględniają wielu elementów oświetlenia. Takimi problemami jak światło odbite (specular) - połysk i wygasanie wraz z odległością (attenuation).
5
Twoja ocena: Brak Ocena: 5 (1 ocena)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com