ฉันจะสร้างการไหลของน้ำแบบเรียงบนลงล่าง 2 มิติได้อย่างไร?


9

ฉันกำลังทำงานในเกม 2D ที่เป็นภาพกราฟิกบนพื้นฐานที่ได้รับแรงบันดาลใจจากป้อมปราการ Dwarf ฉันอยู่ในจุดที่ใช้แม่น้ำในโลกของเกมซึ่งครอบคลุมจำนวนแผ่นและฉันได้คำนวณทิศทางการไหลสำหรับแต่ละไทล์ดังที่แสดงด้านล่างโดยเส้นสีแดงในแต่ละไทล์

ตัวอย่างของกระเบื้องแม่น้ำที่มีทิศทาง

สำหรับการอ้างอิงถึงสไตล์กราฟิกนี่คือลักษณะของเกมของฉันในขณะนี้:

ภาพกราฟิกในเกม

สิ่งที่ฉันต้องการคือเทคนิคบางอย่างในการเคลื่อนไหวของน้ำที่ไหลในแผ่นกระเบื้องของแม่น้ำแต่ละสายเพื่อให้การไหลของมันกลมกลืนไปกับแผ่นกระเบื้องโดยรอบ

ตัวอย่างที่ใกล้เคียงที่สุดที่ฉันได้พบกับสิ่งที่ผมอธิบายไว้หลังจากที่http://www.rug.nl/society-business/centre-for-information-technology/research/hpcv/publications/watershader/แต่ฉันไม่ได้ค่อนข้าง เมื่อถึงจุดที่สามารถเข้าใจว่าเกิดอะไรขึ้น ฉันมีความเข้าใจเพียงพอเกี่ยวกับการเขียนโปรแกรม shader ที่จะใช้แสงแบบไดนามิกของตัวเอง แต่ฉันไม่สามารถเข้าใจวิธีการที่นำมาใช้ในบทความที่เชื่อมโยงได้

ใครช่วยอธิบายถึงผลข้างต้นที่เกิดขึ้นหรือแนะนำวิธีการอื่นเพื่อให้ได้ผลลัพธ์ที่ฉันต้องการ ฉันคิดว่าส่วนหนึ่งของวิธีแก้ปัญหาข้างต้นซ้อนทับกระเบื้อง (แม้ว่าฉันไม่แน่ใจว่าจะใช้ชุดค่าผสมใด) และหมุนแผนที่ปกติที่ใช้สำหรับการบิดเบือน (อีกครั้งไม่มีแนวคิดเฉพาะเจาะจง) และในอดีตว่าฉันหายไปเล็กน้อยขอบคุณ ความช่วยเหลือใด ๆ !


คุณมีเป้าหมายที่มองเห็นได้สำหรับน้ำหรือไม่? ฉันสังเกตเห็นลิงก์ที่คุณอ้างถึงใช้แผนที่ปกติเพื่อการสะท้อนแสง - บางอย่างที่อาจไม่ผสมผสานกันอย่างเหมาะสมในทิศทางศิลปะแบบแบน / การ์ตูนที่คุณแสดง มีวิธีในการปรับเทคนิคให้เข้ากับสไตล์อื่น ๆ แต่เราต้องการแนวทางบางอย่างเพื่อให้เรารู้ว่าจะตั้งเป้าหมายอะไร
DMGregory

คุณสามารถใช้โซลูชันการไหลของคุณเป็นตัวไล่ระดับสำหรับอนุภาคที่ปล่อยในกระแส แม้ว่าอาจมีราคาแพงตามที่คุณต้องการมาก
Bram

ฉันจะไม่แก้ปัญหานี้ด้วย shader ฉันจะทำมันด้วยวิธีง่าย ๆ ที่ใช้กันมานานหลายศตวรรษแค่วาดมันและมีภาพวาดน้ำที่แตกต่างกัน 8 แบบและอีก 8 ภาพวาดของน้ำที่กระทบชายฝั่ง จากนั้นเพิ่มการซ้อนทับสีหากคุณต้องการมีภูมิประเทศที่แตกต่างกันและเพิ่มการสุ่มที่เหมือนหินโรยปลาหรืออะไรก็ตามลงในแม่น้ำ Btw กับ 8 ที่แตกต่างกันฉันหมายถึงทุก ๆ 45 องศาในการหมุนที่จะมีสไปรต์ที่แตกต่างกัน
Yosh Synergi

@YoshSynergi ฉันต้องการให้แม่น้ำไหลไปในทิศทางใดมากกว่า 8 ทิศทางและฉันต้องการหลีกเลี่ยงการมองเห็นรอยต่อระหว่างขอบกระเบื้องคล้ายกับผลลัพธ์ที่ได้จาก shader ที่เชื่อมโยง
Ross Taylor-Turner

@Bram ที่เป็นตัวเลือกที่ฉันคิดว่าฉันสามารถทำได้ แต่ยังคิดว่ามันจะต้องใช้อนุภาคจำนวนมากเกินไปที่จะมีประสิทธิภาพโดยเฉพาะอย่างยิ่งเมื่อกล้องถูกซูมออกมาก
Ross Taylor-Turner

คำตอบ:


11

ฉันไม่ได้ใช้กระเบื้องใด ๆ ที่ดูดีพร้อมกับการบิดเบือนดังนั้นนี่เป็นเอฟเฟกต์รุ่นที่ฉันล้อเลียนกับกระเบื้อง Kenney เหล่านี้แทน:

นิเมชั่นแสดงน้ำไหลใน tilemap

ฉันกำลังใช้ผังงานเช่นนี้โดยที่สีแดง = ไหลไปทางขวาและสีเขียว = ขึ้นไปสีเหลืองเป็นทั้งคู่ แต่ละพิกเซลสอดคล้องกับไทล์เดียวโดยที่พิกเซลด้านล่างซ้ายเป็นไทล์ที่ (0, 0) ในระบบพิกัดโลกของฉัน

8x8

และลวดลายของคลื่นเป็นดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่

ฉันคุ้นเคยกับไวยากรณ์ hlsl / CG-style ของ Unity มากที่สุดดังนั้นคุณจะต้องปรับ shader นี้ให้เข้ากับบริบท 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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.