Bagaimana cara membuat aliran air diarahkan, ubin vertikal top-down?

9

Saya sedang mengerjakan game 2D grafis berbasis top-down yang terinspirasi oleh Dwarf Fortress. Saya pada titik menerapkan sungai di dunia game, yang mencakup sejumlah ubin, dan saya telah menghitung arah aliran untuk setiap ubin, seperti yang ditunjukkan di bawah ini oleh garis merah di setiap ubin.

Contoh ubin sungai dengan arah

Untuk referensi gaya grafis, inilah tampilan permainan saya saat ini:

Bidikan dalam gaya grafis dalam gim

Yang saya butuhkan adalah beberapa teknik untuk menghidupkan air yang mengalir di masing-masing ubin sungai, sehingga aliran menyatu dengan ubin di sekitarnya sehingga tepi ubin tidak terlihat.

Contoh terdekat yang saya temukan dengan apa yang saya cari dijelaskan di http://www.rug.nl/society-business/centre-for-information-technology/research/hpcv/publications/watershader/ tapi saya tidak cukup pada titik mampu memahami apa yang terjadi di dalamnya? Saya memiliki cukup pemahaman tentang pemrograman shader untuk menerapkan pencahayaan dinamis saya sendiri tetapi saya tidak bisa memahami pendekatan yang diambil dalam artikel terkait.

Bisakah seseorang menjelaskan bagaimana efek di atas tercapai atau menyarankan pendekatan lain untuk mendapatkan hasil yang saya inginkan? Saya pikir bagian dari solusi di atas adalah tumpang tindih ubin (meskipun saya tidak yakin di mana kombinasi) dan memutar peta normal yang digunakan untuk distorsi (sekali lagi tidak tahu secara spesifik) dan melewati itu saya agak kehilangan, terima kasih untuk ada bantuan!

Ross Taylor-Turner
sumber
Apakah Anda memiliki target visual untuk air itu sendiri? Saya perhatikan tautan yang Anda kutip menggunakan peta normal untuk refleksi specular - sesuatu yang mungkin tidak cukup tepat dengan arah seni datar / gaya kartun yang Anda tunjukkan. Ada beberapa cara untuk mengadaptasi teknik ini ke gaya lain, tetapi kami membutuhkan beberapa panduan agar kami tahu apa yang harus kami tuju.
DMGregory
Anda dapat menggunakan solusi aliran Anda sebagai gradien untuk partikel yang Anda lepas di aliran. Mungkin mahal, karena Anda akan membutuhkan banyak dari mereka.
Bram
Saya tidak akan menyelesaikan ini dengan shader, saya akan melakukannya dengan cara sederhana yang digunakan selama berabad-abad, hanya menggambar dan memiliki seperti 8 gambar air yang berbeda dan juga 8 gambar air yang berbeda mengenai pantai. Kemudian tambahkan overlay warna jika Anda ingin memiliki medan yang berbeda dan menambahkan secara acak seperti batu taburan, ikan atau apa pun ke sungai. Btw dengan 8 berbeda saya maksudkan agar setiap 45 derajat dalam rotasi memiliki sprite yang berbeda
Yosh Synergi
@YoshSynergi Saya ingin aliran sungai ke arah mana saja daripada 8 arah, dan saya ingin menghindari batas yang terlihat antara tepi ubin, mirip dengan hasil yang dicapai dalam shader terkait
Ross Taylor-Turner
@Bram itu adalah pilihan yang saya pertimbangkan yang bisa saya capai, tetapi juga berpikir itu akan membutuhkan terlalu banyak partikel untuk menjadi efektif, khususnya ketika kamera banyak diperbesar
Ross Taylor-Turner

Jawaban:

11

Saya tidak memiliki ubin yang terlihat bagus dengan distorsi, jadi inilah versi efek yang saya ejek dengan ubin Kenney ini sebagai gantinya:

Animasi menampilkan air yang mengalir di tilemap.

Saya menggunakan flowmap seperti ini, di mana merah = aliran ke kanan dan hijau = ke atas, kuning menjadi keduanya. Setiap piksel terkait dengan satu ubin, dengan piksel kiri bawah menjadi ubin di (0, 0) di sistem koordinat dunia saya.

8x8

Dan tekstur pola gelombang seperti ini:

masukkan deskripsi gambar di sini

Saya paling akrab dengan sintaks gaya hlsl / CG Unity, jadi Anda harus mengadaptasi shader ini sedikit untuk konteks glsl Anda, tetapi harus langsung dilakukan.

// 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;
}
DMGregory
sumber