Bloom und sRGB
S.T.A.L.K.E.R. ist aus den frühen 00ern, und deshalb kommt es mit einer dicken Portion Bloom.
Bloom wird gern als Gaußfilter implementiert – horizontal blurren, vertikal blurren, zum Bild addieren. Wenn man das auch so umsetzt, erhält man diesen Matsch:
Um das zu vermeiden, und wirklich nur die stark leuchtenden Bildteile strahlen zu lassen, führt man üblicherweise einen Grenzwert ein. Alles, was dunkler als dieser Grenzwert ist, wird im Bloom ignoriert. (Steuerbar hier via
r2_ls_bloom_threshold.)
Das führt dann zum perfekten 00er-Look, bei dem alle hellen Bildteile total bloomen und alle mittelhellen und dunklen Bildteile absolut gar keinen Bloom haben (hier bewusst übertrieben):
Ich bin großer Feind des Thresholds und nutze es grundsätzlich nicht.
Die grundsätzliche Idee – Gauß zum Bild addieren – ist nicht verkehrt. Sie ist sogar relativ nah an dem, was im Auge passiert. Ich bin sogar ein kleines Bisschen Bloom-Freund.
Aber warum sieht das erste Bild oben dann derart vermatscht und verkehrt aus?
Das Problem ist, dass hier HDR versucht wird, ohne die Gammakurve zu beachten. Die Pixel des Back Buffers sind im sRGB-Farbraum mit Gamma ~2.2. Wenn man zwei Pixel addiert, die 10% Helligkeit haben, kommen naiv 20% Helligkeit heraus. Tatsächlich sollten es aber (0.1 ^ 2.2 + 0.1 ^ 2.2) ^ 1/2.2 sein; also eher 14% Helligkeit.
Da der Bloom
sehr viele Pixel aufaddiert, ist der Fehler entsprechend groß – und der Bloom viel zu grell.
Also lasst uns S.T.A.L.K.E.R. so umbauen, dass es sRGB-korrekt rendert!
Das klingt schwieriger, als es ist. Das Spiel baut auf Direct3D 9. Dessen Mittel für sRGB sind relativ beschränkt, und lassen sich wie folgt zusammenfassen:
- Für jede Textur muss SetSamplerState(D3DSAMP_SRGBTEXTURE, TRUE) aufgerufen werden, so dass die Texel im linearen Farbraum geladen werden.
- Vor dem Rendern muss SetRenderState(D3DRS_SRGBWRITEENABLE, TRUE) aufgerufen werden. So werden die gerenderten Pixel beim Schreiben in den Back Buffer in den sRGB-Farbraum konvertiert.
Naja,
ganz so einfach ist es nicht. Wer blind die beiden Aufrufe einbaut, wird merken, dass das Menü grün wird und das Spiel nur noch einen weißen Bildschirm anzeigt. S.T.A.L.K.E.R. nutzt auch Normal Maps und Displacement Maps (bspw. für Hitzeflimmern im Deferred Rendering), und wenn die fälschlicherweise als sRGB interpretiert werden, kracht es gewaltig.
Wir machen’s vorsichtiger. Wir setzen in der Funktion, die den Bloom-Filter implementiert, sRGB und setzen es danach wieder zurück:
// r2_rendertarget_phase_bloom.cpp
void CRenderTarget::phase_bloom()
{
for(int i = 0; i < 256; ++i)
RCache.dbg_SetSS(i, D3DSAMP_SRGBTEXTURE, TRUE);
RCache.dbg_SetRS(D3DRS_SRGBWRITEENABLE, TRUE);
…
for(int i = 0; i < 256; ++i)
RCache.dbg_SetSS(i, D3DSAMP_SRGBTEXTURE, FALSE);
RCache.dbg_SetRS(D3DRS_SRGBWRITEENABLE, FALSE);
}
Das selbe für das Rendering der Geometrie:
// r2_R_render.cpp
void CRender::Render()
{
…
LP_normal.sort();
LP_pending.sort();
for(int i = 0; i < 256; ++i)
RCache.dbg_SetSS(i, D3DSAMP_SRGBTEXTURE, TRUE);
RCache.dbg_SetRS(D3DRS_SRGBWRITEENABLE, TRUE);
…
// Lighting, dependant on OCCQ
render_lights(LP_pending);
for(int i = 0; i < 256; ++i)
RCache.dbg_SetSS(i, D3DSAMP_SRGBTEXTURE, FALSE);
RCache.dbg_SetRS(D3DRS_SRGBWRITEENABLE, FALSE);
// Postprocess
Target->phase_combine();
}
TADAAAA
Ich musste natürlich ein paar Faktoren anpassen, da sich durch sRGB die Gesamthelligkeit des Bildes geändert hat.
Aber der Bloom ist nun an hellen Stellen genau so stark wie vorher, und an mittleren und dunklen Stellen viiiiel schwächer. Außerdem ist die Beleuchtung nun halbwegs sRGB-korrekt. Und das wirkt sich insbesondere auf die indirekte Beleuchtung aus:
Ich zementiere diese Einstellungen noch als Voreinstellung beim ersten Start der Engine, so dass sie jeder User meiner EXE automatisch bekommt. Und dann ist mal wieder ein Update fällig.
Jedenfalls kriegt man einen Hauch von sRGB recht einfach sogar in eine bestehende D3D9-Engine gezimmert. In diesem Fall waren vier Code-Ergänzungen ausreichend, wenngleich mit der Holzhammer-Methode. Wenn man es
richtig machen wollte, würde man nun den Code der Lichter, Vertex-Farben, Materialien, usw. an sRGB anpassen.
Nun, wo der Bloom nicht mehr so stark weichzeichnet, fällt das fehlende Antialiasing stärker auf. Das könnte ich als nächstes angehen.