All articles返回博客

Rendering notes渲染手记

One ocean, three looks.同一片海,三种风格。

Stylized water is not a single recipe. The same geometry and waves can feel graphic, balanced, or almost glass-like depending on how depth, refraction, absorption, and surface detail are combined.

风格化水体并没有唯一配方。即使使用相同的几何与波浪,仅仅改变深度、 折射、吸收和表面细节的组合,也能得到块面鲜明、均衡自然或近似玻璃海的 三种视觉方向。

01

The visual comparison同机位画面对比

These captures use the same camera, scene, lighting, and wave motion. Only the water treatment changes, so differences in shoreline readability, seabed visibility, and surface rhythm are easy to isolate.

下面三张实机截图保持相同的相机、场景、光照和波浪,只改变水面渲染方式。 因此可以直接观察岸线可读性、海底可见度与表面波纹节奏的变化。

Classic layered water with saturated depth bands
AClassic layered water经典分层水面
Balanced stylized water with organic shoreline foam
BBalanced stylized water平衡风格化水面
Bright absorption-based water with visible seabed detail
CAbsorption-based water吸收式透明水面
ApproachStrongest qualityTradeoffBest fit
ClassicBold depth bandsHarder transitionsGraphic, readable scenes
BalancedNatural shore and depthLess dramatic surfaceGeneral gameplay views
AbsorptionTransparency and detailDepth contrast can softenClose, bright shallow water
方案最突出的特点需要取舍之处适合的画面
经典分层深浅色带鲜明过渡相对生硬强调块面与可读性
平衡风格化岸线与深度自然表面冲击力较弱通用的俯视游戏画面
吸收式透明通透与细节丰富深水对比可能变弱明亮的近景浅水

02

Three different priorities三种不同的视觉优先级

A

Graphic clarity块面可读性

The classic approach maps water depth into a few deliberate color bands and quantizes the foam. It is simple to read and strongly stylized, but transparency and shoreline transitions are comparatively abrupt.

经典方案把水深映射到几个明确色带,并对泡沫做阶梯化处理。它的风格 鲜明、信息直接,但浅水透明度和岸线过渡相对生硬。

B

A balanced shoreline均衡的岸线表现

The balanced version reconstructs linear depth, protects foreground objects during refraction, and absorbs RGB channels independently. The result keeps depth readable while producing a softer shore and a more continuous shallow-water transition.

平衡方案重建线性深度,在折射时保护前景物体,并分别计算 RGB 通道吸收。 它在保留水深可读性的同时,让岸线与浅水透底更加连续。

C

Light and transparency通透感与表面细节

The absorption-based version favors a bright seabed, two scrolling normal layers, and a stronger Fresnel response. It gives shallow water more presence, although excessive transparency can weaken the visual boundary between safe shallows and deep water.

吸收式方案突出明亮海底、双层滚动法线和更强的菲涅尔反光。浅水细节 更有存在感,但通透度过高时,也会削弱浅海与深水之间的视觉界线。

03

The techniques that matter真正关键的三段代码

Linear depth线性深度

Start with a stable measure of water thickness.先稳定地计算水柱厚度。

Screen-space depth is not linear, and Compatibility uses a different NDC Z range. Correcting that difference first keeps foam, refraction, and shallow-water coloring consistent.

屏幕深度并非线性值,而且 Compatibility 的 NDC Z 范围不同。先修正 这个差异,才能让泡沫、折射和浅水颜色在不同渲染器中保持一致。

float linear_view_depth(vec2 uv, mat4 inv_projection) {
    float raw_depth = texture(depth_texture, uv).x;
    vec3 ndc = vec3(uv * 2.0 - 1.0, raw_depth);

#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
    ndc.z = raw_depth * 2.0 - 1.0;
#endif

    vec4 view_pos = inv_projection * vec4(ndc, 1.0);
    return -view_pos.z / max(view_pos.w, 0.0001);
}

Protected refraction折射保护

Do not pull foreground objects below the surface.不要把前景物体错误拉入水下。

After distorting the screen UV, compare the sampled depth with the water surface. If the sample belongs to an object in front of the water, fall back to the original UV before applying per-channel exponential absorption.

扰动屏幕 UV 后,把采样深度与水面深度比较。如果采样落在水面前方的 物体上,就退回原始 UV,再进行逐通道指数吸收。

vec2 refracted_uv = clamp(
    SCREEN_UV + view_normal.xy * refraction_strength,
    vec2(0.001),
    vec2(0.999)
);

float refracted_depth =
    linear_view_depth(refracted_uv, INV_PROJECTION_MATRIX);

if (refracted_depth < surface_depth + 0.05) {
    refracted_uv = SCREEN_UV;
}

vec3 scene = texture(screen_texture, refracted_uv).rgb;
vec3 transmittance = exp(-absorption * water_depth);
vec3 water_color = scene * transmittance
    + tint * (vec3(1.0) - transmittance);

Absorption and Fresnel吸收与菲涅尔

Let depth and view angle carry the style.让水深与观察角度共同塑造风格。

Exponential absorption shapes the shallow-to-deep transition. Fresnel then raises reflection toward grazing angles, creating a brighter surface without requiring a full screen-space reflection pass.

指数吸收控制浅水到深水的颜色变化,菲涅尔则在斜视角增强反光,无需 完整的屏幕空间反射,也能获得更明亮的水面层次。

float depth_mix = 1.0 - exp(
    -normalized_depth * beers_law
);

vec3 absorbed = clamp(
    refracted_scene - absorption_color * depth_mix,
    vec3(0.0),
    vec3(1.0)
);

float fresnel = pow(
    1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0),
    fresnel_power
);

vec3 color = mix(absorbed, fresnel_color, fresnel * 0.72);

04

Choose by visual purpose按画面目的选择

Use a classic layered treatment when depth regions must read at a glance. Choose the balanced treatment when shoreline, navigation, and underwater visibility all matter. Favor the absorption-based look when bright shallow water and close-up surface detail are the main attraction.

The three styles can share one wave contract. Rendering may add fragment-level normal detail, but visual displacement should remain aligned with the same water height used by physics and gameplay.

Finally, do not name a performance winner from shader complexity alone. Profile GPU frame time on the target hardware under the same camera, resolution, lighting, and weather.

如果需要一眼辨认水深分区,经典分层水面最直接;如果岸线、航行信息和 水下可见度都很重要,平衡方案更稳妥;如果画面的主角是明亮浅海与近景 表面细节,则更适合吸收式水面。

三种风格可以共用同一套波浪合同。渲染分支可以增加片元层的细节法线, 但视觉位移仍应与物理和玩法使用的水位保持一致。

最后,不应只根据 Shader 复杂度宣布性能胜者。应在相同相机、分辨率、 光照和天气下,针对目标硬件测量 GPU 帧时间。