[SFML 2.0 RC] Normal Mapping in 2D

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Dummie
Beiträge: 97
Registriert: 09.02.2004, 20:45

[SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Dummie »

Hallo,

ich nutze C++ und SFML 2.0 RC und würde mich gerne an Normal Mapping versuchen. Die 2D-Welt hab ich bisher nie verlassen und daher auch solche eher in 3D-Anwendungen genutzten Effekte ebenfalls gemieden.

Jetzt bin ich aber dabei eine Spielidee zu testen. Es gibt dabei eine Hintergrundgrafik (generiert aus einem Tileset), eine Spielergrafik und eine Grafik, die den Lichtkegel darstellt. Da das Spiel durch nur diese eine Lichtquelle (oder besser Lichtgrafik) sehr dunkel ist, würde ich gerne mal ausprobieren, wie Normal Mapping das ganze beeinflusst. Ich denke, dass es das gut aufwerten könnte und ein paar "Highlights" setzt.

Die Frage ist nur, wie man das am besten umsetzt. Ich habe natürlich bereits viel experimentiert und gelesen, aber der Erfolg blieb aus. Erschwerend ist für mich, dass es sich eher um eine 3D-Technik handelt und daher auch die Beschreibungen darauf ausgelegt sind. Damit komme ich absolut nicht klar.

Es wäre daher echt toll, wenn mir jemand einen Tipp geben könnte, wie man das in 2D umsetzt. Vielleicht ist es ja auch gar nicht möglich, das nur auf Basis von Sprites zu machen? Oder muss ich sogar mit OpenGL eine "echte" Lichtquelle erzeugen und kann meine Lichtgrafik gar nicht nutzen?

Viele liebe Grüße und ein schönes Wochenende
Dummie
Benutzeravatar
Schrompf
Moderator
Beiträge: 4858
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Schrompf »

Genau das mache ich in Splatter (siehe Signatur) auch. Nichts hindert Dich daran, die Standard-Phong-Beleuchtungsformel auch in 2D umzusetzen. Du musst dazu aber auch eine NormalMap-Variante der Szene ausrendern und deren Normalen dann bei der Lichtberechnung verwenden, um pro Pixel die Helligkeit jeder einzelnen Lichtquelle zu berechnen. Wenn Du das im Screen Space machst, ist das ein klassischer Deferred Renderer, aber theoretisch könnte man das auch als Forward Renderer umsetzen. So oder so, an Shadern kommst Du nicht vorbei.

Ok, theoretisch könntest Du das auch mit der Fixed Function Pipeline und dem dot3-Modus einer Texture Stage umsetzen, aber dann müsstest Du zumindest einen Vertex Shader schreiben oder vortransformierte Vertizes auf der CPU generieren. Dann kannst Du es auch gleich als Software Renderer implementieren. Shader sind in jeder Hinsicht überlegen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Dummie
Beiträge: 97
Registriert: 09.02.2004, 20:45

Re: [SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Dummie »

Vielen Dank für deine schnelle und informative Antwort. :)

Das klingt alles erst mal recht simpel (was es ja angeblich auch sein soll), allerdings fällt es mir dann doch nicht so leicht. :D

Okay, ich muss also erst mal die Szene auf den Schirm bringen. Dabei stellt sich mir schon die erste Frage: Müssen die Lichtgrafiken auch schon gerendet werden?

Wenn ich dann also diese Szene habe, dann muss ich aus dieser die Normalmap berechnen. Oder was ja auch gehen müsste, dass ich einfach die entsprechenden Normalmaps der Grafiken direkt render (oder wird das unschön?).

Damit wäre dann der Punkt "Du musst dazu aber auch eine NormalMap-Variante der Szene ausrendern" erfüllt.

Anschließend kommt wohl der Shader zum Einsatz. Da muss ich mich noch etwas einlesen, was dieser konkret in einem 2D Raum zu tun hat. Oft werden da sehr viele Parameter mitgegeben, die mich überfordern. Wird der Shader denn mehrmals aufgerufen? Also für jede Lichtgrafik erneut? Oder werden die Lichtgrafiken alle anfangs auch gerendert?

Wäre echt dankbar für ein paar weitere Anregungen.
Dummie
Beiträge: 97
Registriert: 09.02.2004, 20:45

Re: [SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Dummie »

Ich habe noch sehr viel Energie in das Experimentieren und Lesen von Erläuterungen investiert und dann einfach aus dem Stehgreif einen Shader so gebaut, wie ich es für richtig gehalten habe. Anfangs sah es noch etwas komisch aus, aber es war bereits ein Effekt erkennbar. Letztendlich sieht es nun aber ganz nett aus. :D

Da ich das ganze selber umgesetzt habe, bin ich mir natürlich nicht so sicher, ob es überhaupt korrekt ist. Es sieht aber wirklich deutlich besser aus, als es vorher ausgesehen hat. Ich habe auch einfach mal ein Beispielbild angehangen.

Und zwar gebe ich einfach 3 Texturen in den Shader: Die farbige Szene, die Normal der Szene und eine Textur auf der die Lichtquellen gerendert sind.

Außerdem nutze ich lediglich einen Fragment-Shader und keinen Vertrex-Shader. Wie gesagt, bin ich was diese Techniken angeht absoluter Neuling. Vermutlich ist das alles auch alles andere als effizient, aber ich bin erst mal froh, dass ich überhaupt voran gekommen bin. Wie man am Code, sieht habe ich auch die Lichtrichtung nicht mit drin. Es wird allerdings dann eh aus der Vogelperspektive gespielt, da fällt das vielleicht eh nicht ins Gewicht...

Na ja, hier mal mein Code:

Code: Alles auswählen

uniform sampler2D colormap;
uniform sampler2D normalmap;
uniform sampler2D lightmap;

void main(void)
{
 vec4 cmP = texture2D(colormap, gl_TexCoord[0].st);
 vec4 nmP = texture2D(normalmap, gl_TexCoord[0].st);
 vec4 lmP = texture2D(lightmap, gl_TexCoord[0].st);

 float diffuse = max(dot(nmP, lmP), 0.0) * lmP;

 gl_FragColor = cmP * diffuse * texture2D(lightmap, gl_TexCoord[0].xy);
}
Viele Grüße und ein großes Danke
Patrick :D
Dateianhänge
Beispielbild
Beispielbild
Benutzeravatar
Schrompf
Moderator
Beiträge: 4858
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Schrompf »

Das ist mathematisch leider Unsinn, was Du da tust. Die Standard-Lichtberechung ist ein Punktprodukt zwischen "Richtung zur Lichtquelle" und "Normale". Die Lichtrichtung kommt bei Dir nirgends vor, stattdessen dottest Du anscheinend gegen die Lichtfarbe.

Mach das Folgende:

- reiche pro Vertex die Position in Weltkoordinaten rein
- reiche die Lichtquellen-Position in Weltkoordinaten als FragmentShader-Parameter rein
- im Fragment-Shader berechnest Du die Richtung zum Licht aus (Lichtpos - Vertex-Weltpos)
- normalisierst diesen Vektor
- und machst damit das max( dot( normale, richtungZumLicht), 0.0f)
- das ist die Menge an Licht, die von der Lichtquelle an diesem Pixel wirksam ist
- das multiplizierst Du dann sowohl mit der Lichtfarbe als auch mit der Oberflächenfarbe

Eine LightMap ist hier nutzlos. Du kannst Deine LightMap nicht benutzen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Dummie
Beiträge: 97
Registriert: 09.02.2004, 20:45

Re: [SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Dummie »

Hallo Schrompf,

vielen Dank für deine Antwort. Ich habe damit ein wenig rumexperimentiert, allerdings erhalte ich damit nur ein schwarzes Bild. Vom Ablauf sollte es deiner Erklärung ja jetzt eigentlich entsprechen.

Da ich SFML nutze, könnte ich mir vorstellen, dass was mit den Koordinaten schief geht, da SFML evtl. an einem anderen Ort anzeigt als der Shader denkt? Oder das ich die falsch übergebe. Ich habe ja letztendlich nur X/Y, aber es werden ja oft die anderen Achsen auch verlangt, evtl. geht dabei was kaputt?

Setzen tue ich die Koordinaten für den Shader so:

Code: Alles auswählen

shader.setParameter("lpos", (mousePos.x - light.getSize().x / 2.0) / SCREEN_W, 
									(mousePos.y - light.getSize().y / 2.0) / SCREEN_H, 
									1.0);
Ich habs auch schon ohne die Division durch die Bildschirmauflösung probiert, aber das Ergebnis ist das gleiche. Ansonsten hab ich ja nun keine Lightmap mehr, evtl. fehlt auch einfach die Lichtquelle als solche? :D

Mein Vertex-Shader sieht nun so aus:

Code: Alles auswählen

varying vec4 worldCoord;

void main()
{
 // reiche pro Vertex die Position in Weltkoordinaten rein
 worldCoord = gl_ModelViewMatrix * gl_Vertex;
}
Und mein Pixel-Shader so:

Code: Alles auswählen

uniform sampler2D colormap;
uniform sampler2D normalmap;

// reiche die Lichtquellen-Position in Weltkoordinaten als FragmentShader-Parameter rein
uniform vec3 lpos; 

// kommt aus dem Vertex-Shader
varying vec4 worldCoord;

void main(void)
{
 vec4 cmP = texture2D(colormap, gl_TexCoord[0].st);
 vec4 nmP = texture2D(normalmap, gl_TexCoord[0].st);

 // im Fragment-Shader berechnest Du die Richtung zum Licht aus (Lichtpos - Vertex-Weltpos)
 // normalisierst diesen Vektor
 vec4 lDir = normalize(vec4(lpos, 0.0) - worldCoord);

 // und machst damit das max( dot( normale, richtungZumLicht), 0.0f)
 float amount = max(dot(nmP, lDir), 0.0);

 // das multiplizierst Du dann sowohl mit der Lichtfarbe als auch mit der Oberflächenfarbe
 gl_FragColor = cmP * amount;
}
Wäre echt prima, wenn du mir nochmal weiterhelfen könntest. Mir lässt das keine Ruhe... :lol:

Edit: Ich glaube, dass das irgendwie mit dem Vertex-Shader zusammenhängt. Kann das sein?

Edit 2: Ich hab den Vertex-Shader nun so abgeändert:

Code: Alles auswählen

varying vec4 worldCoord;

void main()
{
// reiche pro Vertex die Position in Weltkoordinaten rein
 worldCoord = gl_ModelViewProjectionMatrix * gl_Vertex;

 gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
 gl_Position = worldCoord;
}
Jetzt ist zumindest etwas sichtbar und ich kann weiter experimentieren.
Dummie
Beiträge: 97
Registriert: 09.02.2004, 20:45

Re: [SFML 2.0 RC] Normal Mapping in 2D

Beitrag von Dummie »

Okay, vom Prinzip scheint es zu klappen, aber es verhält sich nicht so, wie ich es brauche. Als Lichtposition nehme ich einfach die Mauskoordinaten. Ich würde daher erwarten, dass es dort heller ist, wo die Maus sich befindet. Schiebe ich die Maus nach rechts unten, dann wird es immer heller. Schiebe ich sie nach links oben wird es immer dunkler und das Bild wird schließlich komplett schwarz.

Ich möchte allerdings wieder ein Licht haben, wie ich im Bild weiter oben gezeigt habe. Wie kriege ich das damit jetzt kombiniert? Oder ist im Shader doch ein Fehler?

Vertex-Shader:

Code: Alles auswählen

varying vec4 worldCoord;

void main()
{
 worldCoord = gl_ModelViewProjectionMatrix * gl_Vertex;

 gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

 gl_FrontColor = gl_Color;
}
Pixel-Shader:

Code: Alles auswählen

uniform sampler2D colormap;
uniform sampler2D normalmap;

uniform vec3 lpos;

varying vec4 worldCoord;

void main(void)
{
 vec4 cmP = texture2D(colormap, gl_TexCoord[0].st);
 vec4 nmP = texture2D(normalmap, gl_TexCoord[0].st);

 vec4 lDir = vec4(lpos, 0.0) - worldCoord;

 //Evtl. auch notwendig bzw. sinnvoll?
 //nmP = normalize(nmP);

 lDir = normalize(lDir);

 float amount = max(dot(nmP, lDir), 0.0);

 gl_FragColor = gl_Color * amount * cmP;
}
Es wäre echt toll, wenn du mir da noch weiterhelfen könntest, denn es kommt mir so vor, als würde nicht mehr viel fehlen...

Edit: Ich habe noch etwas weiter gebastelt. Meine Lightmap hab ich nun doch wieder mit rein genommen, denn irgendwie muss ich ja die Lichtform usw. bestimmen. Ich denke, dass das Mapping korrekt funktioniert, denn man sieht sehr gut, dass das Licht links unten anders aussieht als rechts oben. Die Frage ist, ob das für ein Spiel in der Vogelperspektive so sinnvoll ist oder ob ich schon wieder etwas falsch gemacht habe.
Dateianhänge
Neuer Versuch
Neuer Versuch
Antworten