Як зробити 2D керований плиткою спрямований води зверху вниз?


9

Я працюю над досить графічною двовимірною грою на основі плитки зверху вниз, натхненною формою Dwarf Fortress. Я зараз втілюю річку в ігровий світ, яка охоплює ряд плиток, і я обчислював напрямок потоку для кожної плитки, як показано нижче червоною лінією в кожній плитці.

Приклад річкової плитки з напрямками

Для ознайомлення з графічним стилем, ось як виглядає моя гра в даний час:

В грі знімок графічного стилю

Мені потрібна певна техніка для анімації течії води в кожну з річкових плиток, щоб потік змішувався з навколишніми плитками, щоб краї плитки не були видимими.

Найближчий приклад, який я знайшов до того, що я шукаю, описаний на веб- сайті http://www.rug.nl/society-business/centre-for-information-technology/research/hpcv/publications/watershader/, але я не зовсім в момент, коли можна зрозуміти, що в ньому відбувається? Мені достатньо розуміння шейдерного програмування, щоб реалізувати своє власне динамічне освітлення, але я не можу повністю схилитись до підходу, застосованого у пов'язаній статті.

Чи може хтось пояснити, як досягається вищезазначений ефект, чи запропонувати інший підхід, щоб отримати бажаний результат? Я думаю, що частина вищевказаного рішення - це перекриття плиток (хоча я не впевнений, у яких комбінаціях) і обертання звичайної карти, що використовується для спотворення (знову не маю уявлення про конкретику) і минулого, що я трохи загубився, дякую за будь-яка допомога!


Чи є у вас візуальна ціль для самої води? Я помічаю, що посилання, яке ви цитуєте, використовує звичайні карти для дзеркальної рефлексії - те, що може не вписатись у напрямі мистецтва в плоскому / мультиплікаційному стилі. Є способи адаптувати техніку до інших стилів, але нам потрібні деякі вказівки, щоб ми знали, на що прагнути.
DMGregory

Ви можете використовувати розчин для потоку як градієнт для частинок, які ви пускаєте в потік. Напевно, дорого, хоча вам їх знадобиться багато.
Брем

Я б не вирішував це за допомогою шейдера, я би робив це простим способом, який використовувався протягом століть, просто намалював його і мав як 8 різних малюнків води, а також 8 різних малюнків води, що потрапляє на берег. Потім додайте кольорове накладення, якщо ви хочете мати різні місцевості, і додайте випадково, як бризки каміння, риби чи будь-чого іншого в річку. Btw з 8 різними я мав на увазі на кожні 45 градусів обертання мати різний спрайт
Yosh Synergi

@YoshSynergi Я хочу, щоб течія річки була в будь-якому напрямку, а не в 8 напрямках, і я хочу, щоб не було видимих ​​меж між краями плитки, подібним до результату, досягнутого в пов'язаному шейдері
Росс Тейлор-Тернер

@Bram, це варіант, який я вважаю, що міг би досягти, але також думаю, що для його ефективності знадобиться занадто багато частинок, особливо коли камера сильно
зменшена

Відповіді:


11

У мене не було зручних плиток, які добре виглядали б із викривленням, тому ось версія ефекту, яку я знущався над цими плитками Кенні :

Анімація, показуючи проточної води в плиткові карти.

Я використовую подібну схему потоків, де червоний = правий потік і зелений = вгору, жовтий - обидва. Кожен піксель відповідає одній плитці, при цьому нижній лівий піксель є плиткою (0, 0) у моїй світовій системі координат.

8х8

І текстура хвильового малюнка, як це:

введіть тут опис зображення

Я найбільше знайомий із синтаксисом стилю hlsl / CG у стилі Unity, тому вам потрібно буде трохи адаптувати цей шейдер для вашого glsl-контексту, але це потрібно зробити прямо.

// Colour texture / atlas for my tileset.
sampler2D _Tile;
// Flowmap texture.
sampler2D _Flow;
// Wave surface texture.
sampler2D _Wave;

// Tiling of the wave pattern texture.
float _WaveDensity = 0.5f;
// Scrolling speed for the wave flow.
float _WaveSpeed  = 5.0f;

// Scaling from my world size of 8x8 tiles 
// to the 0...1
float2 inverseFlowmapSize = (float2)(1.0f/8.0f);

struct v2f
{
    // Projected position of tile vertex.
    float4 vertex   : SV_POSITION;
    // Tint colour (not used in this effect, but handy to have.
    fixed4 color    : COLOR;
    // UV coordinates of the tile in the tile atlas.
    float2 texcoord : TEXCOORD0;
    // Worldspace coordinates, used to look up into the flow map.
    float2 flowPos  : TEXCOORD1;
};

v2f vert(appdata_t IN)
{
    v2f OUT;

    // Save xy world position into flow UV channel.
    OUT.flowPos = mul(ObjectToWorldMatrix, IN.vertex).xy;

    // Conventional projection & pass-throughs...
    OUT.vertex = mul(MVPMatrix, IN.vertex);
    OUT.texcoord = IN.texcoord;
    OUT.color = IN.color;

    return OUT;
}

// I use this function to sample the wave contribution
// from each of the 4 closest flow map pixels.
// uv = my uv in world space
// sample site = world space        
float2 WaveAmount(float2 uv, float2 sampleSite) {
    // Sample from the flow map texture without any mipmapping/filtering.
    // Convert to a vector in the -1...1 range.
    float2 flowVector = tex2Dgrad(_Flow, sampleSite * inverseFlowmapSize, 0, 0).xy 
                        * 2.0f - 1.0f;
    // Optionally, you can skip this step, and actually encode
    // a flow speed into the flow map texture too.
    // I just enforce a 1.0 length for consistency without getting fussy.
    flowVector = normalize(flowVector);

    // I displace the UVs a little for each sample, so that adjacent
    // tiles flowing the same direction don't repeat exactly.
    float2 waveUV = uv * _WaveDensity + sin((3.3f * sampleSite.xy + sampleSite.yx) * 1.0f);

    // Subtract the flow direction scaled by time
    // to make the wave pattern scroll this way.
    waveUV -= flowVector * _Time * _WaveSpeed;

    // I use tex2DGrad here to avoid mipping down
    // undesireably near tile boundaries.
    float wave = tex2Dgrad(_Wave, waveUV, 
                           ddx(uv) * _WaveDensity, ddy(uv) * _WaveDensity);

    // Calculate the squared distance of this flowmap pixel center
    // from our drawn position, and use it to fade the flow
    // influence smoothly toward 0 as we get further away.
    float2 offset = uv - sampleSite;
    float fade = 1.0 - saturate(dot(offset, offset));

    return float2(wave * fade, fade);
}

fixed4 Frag(v2f IN) : SV_Target
{
    // Sample the tilemap texture.
    fixed4 c = tex2D(_MainTex, IN.texcoord);

    // In my case, I just select the water areas based on
    // how blue they are. A more robust method would be
    // to encode this into an alpha mask or similar.
    float waveBlend = saturate(3.0f * (c.b - 0.4f));

    // Skip the water effect if we're not in water.
    if(waveBlend == 0.0f)
        return c * IN.color;

    float2 flowUV = IN.flowPos;
    // Clamp to the bottom-left flowmap pixel
    // that influences this location.
    float2 bottomLeft = floor(flowUV);

    // Sum up the wave contributions from the four
    // closest flow map pixels.     
    float2 wave = WaveAmount(flowUV, bottomLeft);
    wave += WaveAmount(flowUV, bottomLeft + float2(1, 0));
    wave += WaveAmount(flowUV, bottomLeft + float2(1, 1));
    wave += WaveAmount(flowUV, bottomLeft + float2(0, 1));

    // We store total influence in the y channel, 
    // so we can divide it out for a weighted average.
    wave.x /= wave.y;

    // Here I tint the "low" parts a darker blue.
    c = lerp(c, c*c + float4(0, 0, 0.05, 0), waveBlend * 0.5f * saturate(1.2f - 4.0f * wave.x));

    // Then brighten the peaks.
    c += waveBlend * saturate((wave.x - 0.4f) * 20.0f) * 0.1f;

    // And finally return the tinted colour.
    return c * IN.color;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.