Rekonstruktion der Normale

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Rekonstruktion der Normale

Beitrag von Zudomon »

Ich wollte für mein deferred Rendering die Normale etwas kleiner packen, indem ich nur X und Y speichere.

Soweit ich weiß, geht das, indem man rechnet:

Code: Alles auswählen

z = sqrt(1-x*x-y*y)
Da Z auch negativ werden kann, habe ich noch eine Variable, sagen wir, ZS, in der das Vorzeichen von Z mittels sign() ermittelt wurde.

Rekonstruiere ich nun Z und multipliziere ich anschließend noch mit ZS, so ist das Ergebnis "fast" richtig. Allerdings gibt es um den 0 Bereich eine kleine Anomalie und ich bekomme diese weder behoben noch begreife ich überhaupt, was da falsch sein soll.

In dem Screen wird die Z*0.5+0.5 Komponente dargestellt... man Erkennt einen "Sprung" um die 0 Koordinate.
20160411_1.jpg
Was ich seltsam finde: Simuliere ich die zwei Komponenten nur, also übergebe im G-Buffer XYZ und ermittel ich dann beim deferred Rendering sign(Z) und berechne daraus dann per XY den Z Wert, tritt der Fehler genauso auf, was nicht ungewöhnlich ist. Aber ungewöhnlich finde ich, wenn ich die Normale unmittelbar vorher nochmal normalisiere, dann klappt es.
Also scheint es darauf hinzudeuten, dass die Pixelnormale im G-Buffer nicht normalisiert ist, aber bevor sie im G-Buffer landet, normalisiere ich sie schon!
Das Texturformat für die Normale ist D3DFMT_A16B16G16R16F.
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Krishty »

Hast du beim Schreiben und Lesen sRGB-Konvertierung deaktiviert? Die würde die Werte ja verändern.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

D3DRS_SRGBWRITEENABLE ist immer aus. Ich glaube das müsste ich das ja auch sonst an den anderen Texturen merken.
Aber es muss definitiv was zwischen G-Buffer schreiben und lesen sein...

Damit ich erstmal nicht alles umschreiben muss und die Normalen noch im [0-1] sind, muss ich da erst nochmal *2-1 rechnen:

Schreiben:

Code: Alles auswählen

  float3 nn=normalize(nor.xyz*2-1);
  res.col1 = float4(nn, amb);
Lesen:

Code: Alles auswählen

    float4 n = tex2D(S_DEFNOR, t); 

    //n.xyz = normalize(n.xyz);      
    float na = sqrt(1.0-dot(n.xy,n.xy));
    n.z = na*sign(n.z);        
Wird die auskommentierte Zeile aktiviert, dann klappt es (aber natürlich nur, weil in n.xyz noch die komplette Normale drin steht.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

Der Fall hat sich erledigt... nutze ich als Format D3DFMT_A32B32G32R32F, dann klappt es.
Hätte nicht gedacht, dass D3DFMT_A16B16G16R16F nicht genau genug ist...
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

Vielleicht hat noch jemand eine Idee, wie ich die Genauigkeit besser ausnutzen kann, damit es vielleicht mit 16F auch klappt.

Hab nun schon einiges probiert, aber es will nicht so recht: 1/n, n+2 und pow(n, 4). Wobei letzteres noch das beste Ergebnis bringt bisher.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Rekonstruktion der Normale

Beitrag von Spiele Programmierer »

Die Koordinaten der Normalen befinden sich doch immer im Bereich [-1; 1].
Gleitkommazahlen scheinen mir da sehr ungünstig.
DirectX 9 scheint leider keine vorzeichenbehafteten Texturformate zu unterstützen, aber wenn du die Normalenkomponenten mal 1/2 plus 1/2 rechnest , können sich auch in den Bereich [0; 1] komprimiert werden. Dann kannst du das in dem Bereich viel genaure D3DFMT_G16R16 nutzen. Oder vielleicht sogar D3DFMT_G8R8.
Benutzeravatar
unbird
Beiträge: 9
Registriert: 17.12.2015, 08:35

Re: Rekonstruktion der Normale

Beitrag von unbird »

Also bei D3DFMT_A16B16G16R16F müsste man meiner Meinung nach nicht einmal umcodieren, das ist ja ein vorzeichenbehaftetes Format. Trotzdem, ich denke der Fehler liegt hier:

Code: Alles auswählen

float3 nn=normalize(nor.xyz*2-1);
Erst normalisieren, dann encodieren.

Code: Alles auswählen

float3 nn=normalize(nor.xyz)*2-1;
Apropos: Hier eine Zusammenstellung einiger Encodings (die z-Sign-Variante fehlt allerdings) : Compact Normal Storage for small G-Buffers.

Hoffe das hilft...

Edit: Sorry, brainfart. Jetzt bin ich verwirrt. Wieso 2*n - 1 beim encodieren ??? Müsste doch eigentlich n*0.5 + 0.5 sein.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

unbird hat geschrieben:Also bei D3DFMT_A16B16G16R16F müsste man meiner Meinung nach nicht einmal umcodieren, das ist ja ein vorzeichenbehaftetes Format. Trotzdem, ich denke der Fehler liegt hier:

Code: Alles auswählen

float3 nn=normalize(nor.xyz*2-1);
Edit: Sorry, brainfart. Jetzt bin ich verwirrt. Wieso 2*n - 1 beim encodieren ??? Müsste doch eigentlich n*0.5 + 0.5 sein.
Zudomon hat geschrieben:Damit ich erstmal nicht alles umschreiben muss und die Normalen noch im [0-1] sind, muss ich da erst nochmal *2-1 rechnen
Zudomon hat geschrieben:Der Fall hat sich erledigt... nutze ich als Format D3DFMT_A32B32G32R32F, dann klappt es.
Hätte nicht gedacht, dass D3DFMT_A16B16G16R16F nicht genau genug ist...
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Krishty »

Zudomon hat geschrieben:Damit ich erstmal nicht alles umschreiben muss und die Normalen noch im [0-1] sind, muss ich da erst nochmal *2-1 rechnen
Nein. [-1, +1] * 0.5 + 0.5 ergibt [0, 1].
[-1, +1] * 2 - 1 ergibt [-3, +1].
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

Krishty hat geschrieben:
Zudomon hat geschrieben:Damit ich erstmal nicht alles umschreiben muss und die Normalen noch im [0-1] sind, muss ich da erst nochmal *2-1 rechnen
Nein. [-1, +1] * 0.5 + 0.5 ergibt [0, 1].
[-1, +1] * 2 - 1 ergibt [-3, +1].
Die Normalen sind aber, wie da steht, von [0-1]... und [0, +1] * 2 -1 = [-1, +1]
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Krishty »

Das bedeutet, die Normalen sind bei der Berechnung bereits [0, 1] und wenn du sie in die Textur schreibst, stehen sie in der Textur als [-1, +1]?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

Krishty hat geschrieben:Das bedeutet, die Normalen sind bei der Berechnung bereits [0, 1] und wenn du sie in die Textur schreibst, stehen sie in der Textur als [-1, +1]?
Ja, ist etwas schwer nachzuvollziehen... aber das ganze rührt daher: Vorher hatte ich nur ein einfaches RGBA8 Format, deswegen hat jeder Shader die Normalen in den Bereich von [0, +1] gebracht. Da ich nun aber die Gleitkommaformate benutze, konnte ich diese ja wieder in den normalen Bereich von [-1, +1] umrechnen. Natürlich fällt dieser Zwischenschritt weg, wenn ich das dann in den ganzen Shadern einzeln angepasst habe.
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Krishty »

Okay, jetzt versteh’ ich’s :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2367
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Jonathan »

Naja, das ist doch echt ein häufiges Problem, zu dem man eine Millionen Quellen findet, wenn man sucht. Folgender, erster, Treffer sieht doch zum Beispiel schon gut aus:

http://aras-p.info/texts/CompactNormalStorage.html
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Krishty »

1. wurde das oben schon gepostet
2. ist die Frage nicht, wie man das normalerweise macht, sondern, warum gerade um 0 herum Artefakte auftreten und warum scheinbar die Normalisierung verlorengeht (16-Bit-half-Genauigkeit ist zumindest für mich *keine* Erklärung)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

Es liegt aber tatsächlich daran, dass nicht genug Genauigkeit vorhanden ist. Mit 32 Bit ist es absolut Artefaktfrei... wenn ich 16 Bit nehme, dann ist es annehmbar, wenn ich zum kodieren vorher pow(n.xy, 4) * sign(n.xy) rechne... entsprechendes zum dekodieren (die minimale Anomalie ist aktzeptabel).
Für mich ist das eine eindeutig. Aber ich vermute mal, dass das genau das richtige Thema für dich (Krishty) ist :D, wo du, falls es die Zeit nun zulässt, analysierst, was da Sache ist. Falls du dem auf den Grund gehst, bin ich gespannt, ob meine Vermutung richtig ist. Ich denke mal, dass gerade das sqrt dafür verantwortlich ist. Es streckt einen Bereich (welchen weiß ich jetzt nicht), wie wenn man eine Lupe drauf hält und dadurch ist die Genauigkeit an der Stelle nicht mehr hoch genug.
Benutzeravatar
unbird
Beiträge: 9
Registriert: 17.12.2015, 08:35

Re: Rekonstruktion der Normale

Beitrag von unbird »

Ich vermute mal das Problem rührt daher, dass Du zuerst über 8 bit gehst. Mit der 0.5*n+0.5 Kodierung kann man die 0 nicht exakt darstellen:

0.5 * 0 + 0.5 = 0.5, -> *255 = 127.5, also 127 oder 128 nach rundern, ergo 0.5 daneben. Recht viel.

Volle float-Texture oder diese ^4 Kodierung verstecken das Problem nur. Schreib besser mal erst Deine Shader auf deferred um und versuch dann nochmals 16F (oder noch besser NICHT float, denn der half-float verschenkt nämlich auch bits). Zudem hat D3D9 die Einschränkung bei multi-Render-Targets, dass alle Formate gleich dick sein müssen (Bit-Anzahl). All Deine GBuffer-Texturen auf 128 bit und Deine Bandbreite explodiert.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Rekonstruktion der Normale

Beitrag von Zudomon »

unbird hat geschrieben:Ich vermute mal das Problem rührt daher, dass Du zuerst über 8 bit gehst.
Das kann nicht sein, weil es ausschließlich im Shader passiert. Also im Shader hab ich ja sowas wie (n*0.5+0.5)*2-1. Deine Vermutung würde stimmen, wenn man den Wert zwischendurch in eine 8-Bit Textur legen würde.
unbird hat geschrieben:Zudem hat D3D9 die Einschränkung bei multi-Render-Targets, dass alle Formate gleich dick sein müssen (Bit-Anzahl).
Ich glaube, das ist nicht so. Wobei das hier Herstellerabhängig war. Kann aber nicht mehr sagen, in welcher Präsentation ich das gelesen hatte. Allerdings weiß ich noch, dass ATI so langsam wird, wie das größte RT-Format, bei NVidia ist das nicht so. Demnach ist klar, dass man selbst entscheiden kann, welche Formate man bei MRT rein gibt. Allerdings müssen sie die gleiche Breite und Höhe haben.
unbird hat geschrieben: (oder noch besser NICHT float, denn der half-float verschenkt nämlich auch bits)
Also XYZ Normalen, 8 Bit ist aber ein Himmelweiter Unterschied zu 16 Bit - Float. Wenn gewünscht, kann ich gerne mal ein Vergleichsbild machen.


Vielleicht könnte man, um das Artefakt besser zu vermeiden, vielleicht noch wie beim triplanar Mapping die größte Komponente Suchen und dann in X, Y oder Z Richtung aufteilen, statt immer nur Z zu nutzen und somit das Artefakt bei Z ~ 0 zu bekommen. Allerdings vermute ich, dass sowas mehr Instruktions benötigt als das, was ich jetzt mache. Das sign() braucht zwar ne ganze Menge Instruktionen, das habe ich aber schon durch ein step(0, n)*2-1 ersetzt, welches hier meine Anforderungen auch besser abdeckt, da sign(0) auch 0 zurückliefert, was tötlich ist, wenn ich die Z-Komponente als Depth Wert verwende. Heißt nämlich, dass mein Depth Wert 0 annehmen würde, wenn n.z = 0 wäre.
Antworten