Вода: затухание по волновому числу и отбраковка по плоской поверхности
Затухание было 1/(1 + d²·0.00012) — на 900 юнитах это 0,01, и озеро с высоты читалось зеркальной плитой. Алиасинг бьёт только по мелким волнам: дальше пикселя. Заменено на затухание по волновому числу каждой волны отдельно, local = 1/(1 + (k·d·0.01)²). Волна длиной 46 юнитов на 900 юнитах ещё видна (0,4), длиной 2,7 — уже нет (0,002). discard считался по волновой нормали: на скользящих углах dot(n, view) проваливался в ноль и поверхность выбивалась дырками, край озера рвался зубцом. Теперь facing берётся по плоской поверхности — это вопрос геометрии, а волновая нормаль идёт только в френель и блик.
This commit is contained in:
parent
593282f661
commit
30be24e38e
2 changed files with 39 additions and 16 deletions
|
|
@ -57,11 +57,11 @@ fn wave(p: vec2<f32>, dir: vec2<f32>, freq: f32, speed: f32, time: f32) -> f32 {
|
|||
/// сжимается, ложбина растягивается. Именно это делает воду похожей на воду;
|
||||
/// ровная синусоида читается как гофрированный металл. Отсюда и требование из
|
||||
/// референсов: «Gerstner, not sine».
|
||||
fn gerstner_normal(p: vec2<f32>, time: f32, falloff: f32) -> vec3<f32> {
|
||||
fn gerstner_normal(p: vec2<f32>, time: f32, distance: f32) -> vec3<f32> {
|
||||
// Длина волны в юнитах, амплитуда, крутизна (0 — чистый синус, ближе к 1 —
|
||||
// острее гребень) и скорость. Длины идут от крупных к мелким.
|
||||
let lengths = array<f32, 5>(46.0, 23.0, 11.0, 5.5, 2.7);
|
||||
let amps = array<f32, 5>(1.30, 0.60, 0.26, 0.11, 0.045);
|
||||
let amps = array<f32, 5>(1.70, 0.78, 0.33, 0.14, 0.06);
|
||||
let steeps = array<f32, 5>(0.60, 0.55, 0.50, 0.40, 0.30);
|
||||
let speeds = array<f32, 5>(0.55, 0.80, 1.15, 1.60, 2.10);
|
||||
let dirs = array<vec2<f32>, 5>(
|
||||
|
|
@ -77,9 +77,11 @@ fn gerstner_normal(p: vec2<f32>, time: f32, falloff: f32) -> vec3<f32> {
|
|||
for (var i = 0u; i < 5u; i = i + 1u) {
|
||||
let dir = normalize(dirs[i]);
|
||||
let k = 6.28318530718 / lengths[i];
|
||||
// Мелкие волны должны умирать на расстоянии быстрее крупных, иначе
|
||||
// вдали поверхность рассыпается в серо-голубой шум.
|
||||
let local = select(falloff, falloff * falloff, i >= 3u);
|
||||
// Алиасинг бьёт по МЕЛКИМ волнам: на далёком расстоянии они уже
|
||||
// меньше пикселя и превращаются в шум. Крупные должны доживать до
|
||||
// горизонта, иначе сверху вода читалась как зеркальная плита (замер:
|
||||
// прежнее затухание 1/(1+d²·0.00012) на 900 юнитах давало 0,01).
|
||||
let local = 1.0 / (1.0 + pow(k * distance * 0.01, 2.0));
|
||||
let amp = amps[i] * local;
|
||||
let qa = steeps[i] * amp;
|
||||
let phase = k * dot(dir, p) - time * speeds[i] * k;
|
||||
|
|
@ -108,11 +110,7 @@ fn gerstner_normal(p: vec2<f32>, time: f32, falloff: f32) -> vec3<f32> {
|
|||
|
||||
/// Нормаль поверхности воды.
|
||||
fn ripple_normal(p: vec3<f32>, base: vec3<f32>, time: f32, distance: f32) -> vec3<f32> {
|
||||
// Затухание подобрано замером кадра: при линейном затухании рябь не гасла
|
||||
// до горизонта и давала череду полос. Дальше ~150 юнитов рябь уже не
|
||||
// читается, а не гаснет — это алиасинг.
|
||||
let falloff = 1.0 / (1.0 + distance * distance * 0.00012);
|
||||
let n = gerstner_normal(p.xz, time, falloff);
|
||||
let n = gerstner_normal(p.xz, time, distance);
|
||||
// Совсем мелкая рябь поверх — только вблизи, иначе мерцает.
|
||||
let micro = 1.0 / (1.0 + distance * 0.08);
|
||||
let detail =
|
||||
|
|
@ -130,19 +128,22 @@ fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
|
|||
// Время приходит из буфера камеры: иначе рябь стояла бы на месте, а если
|
||||
// выводить его из позиции — картинка дёргалась бы при повороте.
|
||||
let n = ripple_normal(in.world_pos, base, camera.time.x, distance);
|
||||
let facing = dot(n, view);
|
||||
// Вода — две грани (поверхность и изнанка); рисуем только лицевую.
|
||||
// Лицевая ли грань — вопрос к ГЕОМЕТРИИ, поэтому считаем по плоской
|
||||
// поверхности, а не по волновой нормали. Иначе на скользящих углах волна
|
||||
// наклоняет нормаль достаточно, чтобы facing провалился в ноль, и поверхность
|
||||
// выбивалась дырками — край озера рвался зубцом.
|
||||
let facing = dot(base, view);
|
||||
if (facing <= 0.02) {
|
||||
discard;
|
||||
}
|
||||
// Френель: насколько поверхность «повёрнута» от взгляда. На горизонтальном
|
||||
// взгляде вода почти зеркалит небо, сверху — видно дно.
|
||||
let fresnel = clamp(pow(1.0 - clamp(facing, 0.0, 1.0), 4.0), 0.02, 1.0);
|
||||
// Френель по волновой нормали: рябь должна дёргать отражение неба.
|
||||
let facing_n = clamp(dot(n, view), 0.0, 1.0);
|
||||
let fresnel = clamp(pow(1.0 - facing_n, 4.0), 0.02, 1.0);
|
||||
let deep = textureSample(base_texture, base_sampler, in.uv).rgb * in.light;
|
||||
// Отражение неба: чем ниже угол, тем темнее и холоднее отражение.
|
||||
let sky_grazing = vec3<f32>(0.70, 0.80, 0.92);
|
||||
let sky_steep = vec3<f32>(0.42, 0.58, 0.74);
|
||||
let sky = mix(sky_steep, sky_grazing, clamp(facing, 0.0, 1.0));
|
||||
let sky = mix(sky_steep, sky_grazing, facing_n);
|
||||
// Блик от солнца: узкий, как у воды, а не матовая подсветка.
|
||||
let sun_dir = normalize(vec3<f32>(0.35, 0.45, 0.82));
|
||||
let reflect = reflect(-view, n);
|
||||
|
|
|
|||
|
|
@ -1820,3 +1820,25 @@ setpos 1296.484375 5623.283203 3195.700195; setang 28.374243 -133.538101 0
|
|||
выше 1000 отсутствует (0 треугольников), рядом с камерой выше 2000 — тоже 0.
|
||||
Значит закрывающая плита — не рельеф, и причина не найдена. Записано здесь
|
||||
честно; E11.3 этот дефект не объясняет и не вызван им.
|
||||
|
||||
### E11.6. Вода: затухание и отбраковка по волновой нормали
|
||||
|
||||
Две правки, обе найдены замером кадра, а не правдоподобием.
|
||||
|
||||
**Затухание бьло по всем волнам сразу.** Было `1/(1 + d²·0.00012)` — на 900
|
||||
юнитах это 0,01, и с высоты озеро читалось зеркальной плитой. Алиасинг бьёт
|
||||
только по **мелким** волнам: они дальше пикселя. Заменено на затухание по
|
||||
волновому числу каждой волны отдельно:
|
||||
|
||||
```
|
||||
local = 1 / (1 + (k · distance · 0.01)²)
|
||||
```
|
||||
|
||||
При таком на 900 юнитах волна длиной 46 юнитов ещё видна (0,4), а длиной
|
||||
2,7 — уже нет (0,002).
|
||||
|
||||
**`discard` считался по волновой нормали.** Вопрос «лицевая ли грань»
|
||||
относится к геометрии, а не к микрорельефу: на скользящих углах волна
|
||||
наклоняла нормаль так, что `dot(n, view)` проваливался в ноль, и поверхность
|
||||
выбивалась дырками — край озера рвался зубцом. Теперь `facing` берётся по
|
||||
плоской поверхности, а волновая нормаль идёт только в френель и блик.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue