Decided to check out how water is rendered in Counter-Strike 2
Given how interactive water in CS2 is, I expected to see some cell fluid simulation, but turns out they use just a low-res off-screen buffer with flat 2D decals for various water effects. Reminds me of HL2 water :)
Jun 11, 2025 · 1:18 AM UTC
Buffer size is ¼ of the horizontal and ½ of the vertical screen resolution.
Red is used for waves and caustics.
Green is used for splashes and foam.
Blue is used for dust and trails on mud/junk on the water surface.
Everything is rendered using flat 2D textures.
Water is rendered as flat plane, waves (and all other effects) are created using pixel shader
Shader receives framebuffer and depthbuffer as input. Both caustics, refractions, and reflections are handled entirely in screen space (with some backing from parallax-correct cubemaps)
As a result of this approach, slight pixelization and some screen space artifacts can be noticed.
Although screen space reflections look pretty decent here, reflections aren't very pronounced and parallax-corrected cubemaps hide most of the SSR flaws.
Caustics seem to be divided into procedural and off-screen buffer ones
While the procedural ones are consistent with underwater geometry, the others appear at some arbitrary depth (to save perf on reprojection?), and disappear if you look away (tho it's REALLY hard to notice)
I also noticed one place where foam from one water surface was rendered on another water surface at different height, causing some parallax issues.
Since I've started to examine how CS2 renders a frame, I decided to look at other aspects of it's rendering pipeline as well.
The first thing I wanted to look at is lighting.
CS2 mixes baked lighting with shadow maps, per-pixel lighting and capsule ambient occlusion.
Baked lighting is stored in 3 textures.
One HDR texture for static lights and GI.
One LDR texture for baked light direction. It's also half res on some maps.
And one more texture is used to store from 1 to 4 shadow masks for semi-dynamic per-pixel lights.
Shadow masks are used to combine shadow maps with dynamic props and players with baked penumbra shadows from static geometry. You also get per-pixel shading with proper normal mapping and specularity.
All shadow maps are rendered into one big atlas at the beginning of the frame.
Light probe volumes are used to light dynamic objects.
Data is stored in two large 3D texture atlases. The first one is HDR and is six times larger than other (looks like Valve is still using ambient cubes). The second one is LDR RGBA, looks like a shadow mask.
CS2 uses parallax-corrected cubemaps as it's main method for reflections.
All cubemaps are stored in one large texture array. Pre-filtered mipmap are used for smooth rough reflections (looks really nice when used with roughness maps).
Z-Prepass is performed after shadow maps are rendered and is used as input for capsule-based (probably) AO.
AO is calculated in half-res RGB buffer.
Box is rendered around each dynamic object, with soft shadows from 3 directions stored as RGB. This data is then used in the main rendering pass, where the AO direction is selected based on the light direction from the lightmap.
Decals are rendered after all solid geometry. Some flavor or parallax occlusion mapping is used (no fancy cone step mapping here). Textures are projected using data from z-buffer, which can cause artifacts when anti-aliasing is enabled.
CS2 also uses some flavor of order-independent transparency for particles, glass, smoke and fire. Most likely, moment-based OIT is used.
Two render passes and one composing pass is used for transparent geometry (see moment-based OIT)
First pass uses 3 render buffers (R 32F, RG 32F, RGBA 32F)
Second pass uses previous as input and renders color data in RGBA 16F buffer
Final pass combines this data with framebuffer
Smoke is rendered separately in a bunch of low-res buffers and is combined with OIT buffers later.
A whopping SIX (!) buffers are used (R 32F, RG 32F, RGBA 32F, RGBA 16F, RG 16F, R 8UI).
The first 4 buffers seem to be the same as the OIT buffers, but I'm not sure about the rest.
Two sets of buffers with different resolutions are used for smokes at different distances. Close ones are rendered in ¼ buffers, and distant ones in ½ buffers.
Fire is also rendered separately and merged with OIT later, but only 4 ½ res buffers are used.
The next one in the render is water, but I've already covered it earlier.
Only after all of this the player viewmodel is rendered. CS2 has a separate pass for the viewmodel SSAO, which feels like overkill to me.























































