Well, it definitely works! Here's a so-retro-it's-anachronistic Thief 2 screenshot:
![]()
EDIT: TL;DR: Go straight to this post, and maybe also this.
Well, if anyone wanted a retro 8-bit mode, here it is. Set d3d_disp_sw_cc to 2 and extract cc-8bit-palette.7z to the game install directory. Alternatively, put the code below in shaders/cc.fx. The former is just the latter compiled using the fxc compiler. The latter may take some time to compile when switching to the game - not just when starting a mission, but even after just visiting a menu screen, it seems. Please note that especially in TDP with set_tex_filter 0, the difference to full 24-bit color may be surprisingly subtle.
Anyone who's better at HLSL than I am (not a high bar), feel free to fix this mess.
Code:// Palettize to 8-bit palette (DARKPAL.PCX). static const float3 LUMINANCE_VECTOR = float3(0.2125, 0.7154, 0.0721); float g_fGamma; float g_fSaturation; float g_fContrast; float g_fBrightness; float4 g_fColorFilter; float2 g_fScreenSize; // the frame texture sampler s0 : register(s0); float4 pal[64] = { { 0x000000, 0xdddddd, 0xb6b6b6, 0x969696, }, { 0x7c7c7c, 0x666666, 0x545454, 0x454545, }, { 0x393939, 0x2f2f2f, 0x272727, 0x202020, }, { 0x1a1a1a, 0x161616, 0x121212, 0x0f0f0f, }, { 0x0c0c0c, 0x0a0a0a, 0x080808, 0x060606, }, { 0x050505, 0x040404, 0x030303, 0x7a8449, }, { 0x707a43, 0x65703d, 0x5b6536, 0x0087fe, }, { 0x007df2, 0x0073e6, 0x0069da, 0x005fce, }, { 0xb18c82, 0xa38178, 0x96776e, 0x886c64, }, { 0x7b615a, 0x6d5650, 0x604c46, 0x52413c, }, { 0x443632, 0x372b28, 0x29211e, 0x1c1614, }, { 0x0e0b0a, 0xa30202, 0x980202, 0x8e0202, }, { 0x830202, 0x780101, 0x6d0101, 0x630101, }, { 0x580101, 0x4d0101, 0x380101, 0x220000, }, { 0x0d0000, 0x083015, 0x072b13, 0x062510, }, { 0x05200e, 0x041a0b, 0x031509, 0x020f06, }, { 0x8e6d43, 0x87683f, 0x81623c, 0x7a5d38, }, { 0x735734, 0x6d5231, 0x664c2d, 0x60472a, }, { 0x573f25, 0x4d3820, 0x44301a, 0x3a2915, }, { 0x312110, 0x3e1210, 0x3a110f, 0x36100e, }, { 0x320e0d, 0x2e0d0c, 0x2a0c0b, 0x260b0a, }, { 0x220a09, 0x1d0807, 0x180606, 0x120504, }, { 0x0d0303, 0x321d46, 0x2d1a3f, 0x271736, }, { 0x20132c, 0x1a1023, 0x140c1b, 0x0e0912, }, { 0xee9a47, 0xde9042, 0xcf863e, 0xbf7c39, }, { 0xaf7134, 0xa0672f, 0x905d2b, 0x815326, }, { 0x714922, 0x613f1d, 0x513519, 0x412b14, }, { 0x312110, 0x312110, 0x2e1f0f, 0x2b1d0e, }, { 0x271a0d, 0x24180c, 0x21160b, 0x1e140a, }, { 0x1b1209, 0x170f07, 0x140d06, 0x110b05, }, { 0x0a0603, 0xffffbd, 0xffb510, 0xffad52, }, { 0xffff7b, 0xf1e657, 0xe4ce34, 0xd6b510, }, { 0x8c7b5a, 0x867555, 0x7f6e4f, 0x79684a, }, { 0x726145, 0x6c5b40, 0x65543a, 0x5c4c33, }, { 0x54432c, 0x4b3b25, 0x42321e, 0x3a2a17, }, { 0x312110, 0x505b30, 0x4b552d, 0x464f2a, }, { 0x404927, 0x3b4423, 0x363e20, 0x31381d, }, { 0x2c321a, 0x262c17, 0x212614, 0x1c2011, }, { 0x171b0d, 0xad10b5, 0x9c29ad, 0xb51894, }, { 0x9400c6, 0x6300ad, 0x4a0080, 0x310052, }, { 0x6d7787, 0x666f7e, 0x5f6775, 0x575f6c, }, { 0x505764, 0x494f5a, 0x424752, 0x3b4048, }, { 0x33373e, 0x2b2e34, 0x222529, 0x1a1c1f, }, { 0x121315, 0x5a3139, 0x542e35, 0x4e2b31, }, { 0x48272e, 0x42242a, 0x3c2126, 0x361e22, }, { 0x311b1f, 0x2b171b, 0x231316, 0x1b0e11, }, { 0x130a0c, 0xe71821, 0xce2118, 0xff8c18, }, { 0xd97415, 0xb25c12, 0x8c440f, 0x652c0c, }, { 0x6d5370, 0x664e69, 0x5e4760, 0x564158, }, { 0x4d3b4e, 0x463546, 0x3d2e3d, 0x352835, }, { 0x2d222d, 0x241b24, 0x1c151c, 0x130e13, }, { 0x0b080b, 0x9c4a4a, 0xa86161, 0xb57777, }, { 0xc18e8e, 0xcea5a5, 0xdabbbb, 0xe6d2d2, }, { 0xf3e8e8, 0xffffff, 0x0055c2, 0x004bb6, }, { 0x0041a2, 0xb5bdff, 0x8c8ce7, 0x9cadef, }, { 0x7e9de2, 0x5f8ed4, 0x417ec7, 0x226eb9, }, { 0xded684, 0xcfc87b, 0xc1ba73, 0xb2ac6a, }, { 0xa49e61, 0x959058, 0x878250, 0x746f44, }, { 0x605c39, 0x4d4a2d, 0x393721, 0x262416, }, { 0x12110a, 0x74959d, 0x97b0b6, 0xbacace, }, { 0xdce5e7, 0xffffff, 0x3a5b4b, 0x466e57, }, { 0x538063, 0x5f936f, 0x6ba57b, 0x00378d, }, { 0x002d79, 0x002364, 0x001950, 0x000f3b, }, { 0x0000ff, 0x0000ff, 0xff00ff, 0x000000, }, }; float3 getPal(int i) { float pc; switch (i%4) { case 0: pc = pal[i/4].x; break; case 1: pc = pal[i/4].y; break; case 2: pc = pal[i/4].z; break; case 3: pc = pal[i/4].w; break; } return float3(floor(pc/(1<<16)), fmod(floor(pc/(1<<8)), 256.0), fmod(pc, 256.0)) / 255.0; } float3 palettize(float3 vColor) { float3 cd, s, pc; float d2, sd2 = 10; [loop] for (int c = 0; c < 64; c++) { pc = getPal(c); cd = vColor.rgb - pc; d2 = cd.r*cd.r+cd.g*cd.g+cd.b*cd.b; if (d2 < sd2) { s = pc; sd2 = d2; } } [loop] for (int c = 64; c < 128; c++) { pc = getPal(c); cd = vColor.rgb - pc; d2 = cd.r*cd.r+cd.g*cd.g+cd.b*cd.b; if (d2 < sd2) { s = pc; sd2 = d2; } } [loop] for (int c = 128; c < 192; c++) { pc = getPal(c); cd = vColor.rgb - pc; d2 = cd.r*cd.r+cd.g*cd.g+cd.b*cd.b; if (d2 < sd2) { s = pc; sd2 = d2; } } [loop] for (int c = 192; c < 256; c++) { pc = getPal(c); cd = vColor.rgb - pc; d2 = cd.r*cd.r+cd.g*cd.g+cd.b*cd.b; if (d2 < sd2) { s = pc; sd2 = d2; } } return s; } float4 SatGammaPS(in float2 uv : TEXCOORD0, uniform int bDoSat, uniform int bDoGamma, uniform int bDoContrBright) : COLOR { float4 vColor = tex2D(s0, uv); vColor.xyz = palettize(vColor.xyz); if (bDoSat) { float lumi = dot(vColor.xyz, LUMINANCE_VECTOR); vColor.xyz = lerp(lumi.xxx, vColor.xyz, g_fSaturation) * g_fColorFilter.xyz; } if (bDoGamma) { vColor.xyz = pow(vColor.xyz, g_fGamma); } if (bDoContrBright) { vColor.xyz = saturate(vColor.xyz * g_fContrast + g_fBrightness); } return vColor; } // apply saturation/color filter, brightness/contrast and gamma technique TeqBrSatGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 1, 1); } } // apply brightness/contrast and gamma technique TeqBrGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 1, 1); } } // apply brightness/contrast and saturation/color technique TeqBrSat { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 0, 1); } } // apply saturation/color filter and gamma technique TeqSatGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 1, 0); } } // apply only gamma technique TeqGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 1, 0); } } // apply only brightness/contrast technique TeqBr { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 0, 1); } } // apply only saturation/color technique TeqSat { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 0, 0); } } // plain copy technique TeqCopy { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 0, 0); } }
Last edited by jermi; 13th May 2023 at 02:59.
Well, it definitely works! Here's a so-retro-it's-anachronistic Thief 2 screenshot:
![]()
Ooooh, I need to try that!
Dude, TG looks amazing - the Bonehoard so sinister again. Was only a second's delay so yeah, I'm never switching this off.
(Oh and never mind my registration date, I've been lurking 20 years. This needed to be commented on.)
Seems like doing a nearest-color search on the original Thief software palette for every pixel of the screen would have quite the performance impact. Is it really noticeably different from just banging each pixel down to RGB333 or something like that?
EDIT: Yeah, this drags my frame rate down from a solid 60FPS to 30FPS. Just quantizing the color gamut runs a lot faster, though of course it's only an approximation of the palettized effect.
This quantizes the output to the equivalent of RGB444, aka 12-bit color. Since shaders represent colors as floats, there's no need to stick to bit multiples, so the "roughness" can be adjusted to whatever degree you like. And each channel doesn't have to be quantized the same, so you could tweak it to give more resolution to the reds and greens of the Thief palette and less to blues.Code:// Quantize displayed colors static const float3 LUMINANCE_VECTOR = float3(0.2125, 0.7154, 0.0721); float g_fGamma; float g_fSaturation; float g_fContrast; float g_fBrightness; float4 g_fColorFilter; float2 g_fScreenSize; // the frame texture sampler s0 : register(s0); float4 SatGammaPS(in float2 uv : TEXCOORD0, uniform int bDoSat, uniform int bDoGamma, uniform int bDoContrBright) : COLOR { float4 vColor = tex2D(s0, uv); vColor.x = round(vColor.x * 16.0) / 16.0; vColor.y = round(vColor.y * 16.0) / 16.0; vColor.z = round(vColor.z * 16.0) / 16.0; if (bDoSat) { float lumi = dot(vColor.xyz, LUMINANCE_VECTOR); vColor.xyz = lerp(lumi.xxx, vColor.xyz, g_fSaturation) * g_fColorFilter.xyz; } if (bDoGamma) { vColor.xyz = pow(vColor.xyz, g_fGamma); } if (bDoContrBright) { vColor.xyz = saturate(vColor.xyz * g_fContrast + g_fBrightness); } return vColor; } // apply saturation/color filter, brightness/contrast and gamma technique TeqBrSatGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 1, 1); } } // apply brightness/contrast and gamma technique TeqBrGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 1, 1); } } // apply brightness/contrast and saturation/color technique TeqBrSat { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 0, 1); } } // apply saturation/color filter and gamma technique TeqSatGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 1, 0); } } // apply only gamma technique TeqGamma { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 1, 0); } } // apply only brightness/contrast technique TeqBr { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 0, 1); } } // apply only saturation/color technique TeqSat { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(1, 0, 0); } } // plain copy technique TeqCopy { pass P0 { PixelShader = compile ps_3_0 SatGammaPS(0, 0, 0); } }
And now enjoy some really retro System Shock 2.
Last edited by ZylonBane; 27th Jun 2020 at 15:43.
I was mostly just figuring out if it's even possible to do exact palettization. Doing a per-pixel linear search through a 256-color palette is of course slow - but not necessarily too slow. On my machine, this shader drops the frame rate from 240+ to around 105 at the start of "Bafford's" on a FullHD screen.
Quantization is unlikely to be a good approximation of a palette. That's kind of the whole point of having a palette in the first place. But I'm not here to promote palettization or anything like that, just showing that it's possible. For the record, I never played in 8-bit mode, so I don't really even know how this compares.
This is jermi's version for comparison. It still looks interesting, like a slightly desaturated full colour mode, but it's impossible to play
![]()
I'm glad you like it, but it literally doesn't look like software mode to a tee, since software mode forced every pixel to map to one of the 256 palette colors. What my code does is basically run a posterization filter on the screen. Maybe Jermi's filter doesn't look as "bad" as you were expecting because the Thief software palette was very well optimized for its assets.
Is there is a way to port this to ReShade?
I'm guessing yes.
Oh my God, finally!
I've always thought the original bland look of the first game was the most creepy and loved it for it. The 3d Acceleration had removed almost all of it, expecially in Thief 2.
So is this working? For both Thief 1 and Thief 2?
And by the way, Zylon, that SS2 with the retro-look looks sweet! I don't see any framerate drops...
I remember playing Thief in software mode prior to 2000. If this gets a proper release, I will definitely grab it and play it.
I don't know how you guys are able to use the Old Dark exe, but it won't work for me no matter what I do (compatibility modes, &c), either won't run, or gets to the menu, and crashes when I start or load a game.
I'm definitely curious to try Old Dark and compare it to the shader, I'm pretty certain Zylonbane's shader replicates it perfectly from what I remember, but need to confirm
Take two. I also can't get TDP to run in software mode, so I'm using OldDark DromEd 1.37 "solid world" view as a reference.
Based on a look at the leaked code, the software renderer quantizes lightmaps to 4 bits (16 levels). After seeing that, and also looking at the TDP palette in HSV, I figured that the best simple approximation is to just quantize the HSV value component. So that's what this shader does, along with a couple of other simple tricks.
- Extract cc-faux-8bit-sw-mode.7z into the game install directory.
- Put these is cam_ext.cfg:
Code:d3d_disp_sw_cc 2 disable_32bit_fampal tex_filter_mode 0 mipmap_bias -1.0
![]()
Last edited by jermi; 13th May 2023 at 03:00.
I need to try it pronto.
I thought this mod had been done at least once before, but I can't find it, so I made my own. This is faux software mode water. To install, extract thief-faux-swwater.7z to your mod_path. Again, I can't get TDP to run in software mode, so I don't really know how this compares.
It's so ugly it's almost cute.
![]()
Last edited by jermi; 26th Jun 2022 at 09:42.
Looks pretty accurate to me.
Cause I used/created illumination maps for all the lited textures in Thief 1/2 ESRGAN Packs. So all the bright windows are looking lighter like little bloom effect.I'm not sure why the window's orange tone get lighter with Akven's ESRGAN pack, though.
Yeah, that would be awesome. Thief Gold has some places in pitch black while Thief 2 is completely unplayable, way to dark.
And everytime I try to run Thief with reshade or sweetfx it crashes.
Nice!