Идея очень упрощенного шейдера воды

Posted on

Для тех, хочется одной текстурой и двумя полигонами сделать что-то похожее на воду.

Берем простую текстуру волн на воде

waves

Одеваем ее на очень простую прямоугольную поверхность сначала два раза

float4 tex = textureMap( texture, tiling1,time.xy * _Speed1);
float4 tex2 = textureMap( texture,tiling2,time.yz * _Speed2);

Тут у нас на gpu одна и та же текстура тайлится с двумя разными параметрами, примерно так:

Тайлинг сверху чаще, чем снизу

Теперь смешиваем оверлеем обе текстуры

float4 c = overlay(tex.rgba,tex2.rgba);

Уже сейчас вода выглядит так:

Хочется добавить немного бликов, как будто от ряби на воде отражается свет.

Для этого идем на хитрость – накладываем оверлеем эту же текстуру на первый результат растеризации, но тайлим ее еще чаще и двигаем в обратную сторону. Сохраняем в памяти результат.

float4 tex3 = textureMap( texture,tiling3,time.xy *_Speed3);
tex3 = overlay(tex3,tex);

Чтобы понять, где “высвечивать” блики – переводим текстуру в грейскейл по luminosity алгоритму.

float cc = grayscaleLum(tex3);

Зачем? В этом случае, мы в tex3 будем иметь как бы интенсивность цвета, которая будет в местах пересечения текстуры с частым тайлингом и текстуры с еще более частым тайлингом. В этих местах можно смело цвет выжигать в белый:

c.rgb += (cc>max_high)*(cc-max_high)*shine;

Выражение читается как “если интенсивность цвета (cc) выше, чем установленное (max_high), то разницу этой интенсивности прибавляем к цвету”

С альфой проделываем те же операции

base_alpha+=(cc>alpha_max_high)*(cc-base_alpha)*0.5;

Примерно так.

Можно при желании добавить дно реки.

Можно отправлять текстурщикам рисовать финальные текстуры и равнять по цветам.

Итоговый FLSL шейдер

use namespace flare;
use namespace flare.transforms;
use namespace flare.filters;
use namespace flare.blendMode;

param TIME time;

param float2 tiling1 = float2(1);
param float2 tiling2 = float2(1);
param float2 tiling3 = float2(1);

param float2 _Speed1;
param float2 _Speed2;
param float2 _Speed3;

param float shine = 1;


sampler2D texture;

// grayscale luminosity
float grayscaleLum(float4 color) {
    return 0.21*color.r+0.72*color.g+0.07*color.b;
}

float4 mapWater() {
    const float max_high = 0.65;
    const float alpha_max_high = 0.65;
    const float base_alpha = 0.5;
    
    float4 tex = textureMap( texture, tiling1,time.xy * _Speed1);
    float4 tex2 = textureMap( texture,tiling2,time.yz * _Speed2);

    float4 tex3 = textureMap( texture,tiling3,time.xy *_Speed3);
    tex3 = overlay(tex3,tex);

    float4 c = overlay(tex.rgba,tex2.rgba);

    float cc = grayscaleLum(tex3);
    c.rgb += (cc>max_high)*(cc-max_high)*shine;
    base_alpha+=(cc>alpha_max_high)*(cc-base_alpha)*0.5;
    
    return float4(c.rgb,base_alpha);
}

technique template {
    output sourceFactor = "sourceAlpha";
    output destFactor = "oneMinusSourceAlpha";
    output depthWrite = false;
    output vertex = transform();
    output fragment = mapWater()
}

One Reply to “Идея очень упрощенного шейдера воды”

Leave a Reply