Làm cách nào để hiển thị dòng chảy 2D từ trên xuống dưới lát gạch?


9

Tôi đang làm việc trên một trò chơi 2D đồ họa khá dựa trên gạch từ trên xuống lấy cảm hứng từ Pháo đài Lùn. Tôi đang ở điểm thực hiện một dòng sông trong thế giới trò chơi, bao gồm một số ô và tôi đã tính hướng dòng chảy cho mỗi ô, như được hiển thị bên dưới bởi đường màu đỏ trong mỗi ô.

Ví dụ về gạch sông có hướng

Để tham khảo phong cách đồ họa, đây là cách trò chơi của tôi hiện tại:

Bắn trong trò chơi theo phong cách đồ họa

Những gì tôi cần là một số kỹ thuật để làm động nước chảy trong từng viên gạch sông, để dòng chảy hòa vào các viên gạch xung quanh sao cho các cạnh của gạch không rõ ràng.

Ví dụ gần nhất mà tôi đã tìm thấy những gì tôi đang theo dõi được mô tả tại http://www.rug.nl/society-business/centre-for-inif-t Technology / research / hpcv / publications / watershader / nhưng tôi không hoàn toàn tại điểm có thể hiểu những gì đang xảy ra trong đó? Tôi có đủ hiểu biết về lập trình shader để thực hiện ánh sáng động của riêng mình nhưng tôi hoàn toàn không thể hiểu được cách tiếp cận trong bài viết được liên kết.

Ai đó có thể giải thích làm thế nào đạt được hiệu quả trên hoặc đề xuất một cách tiếp cận khác để có được kết quả tôi muốn? Tôi nghĩ rằng một phần của giải pháp trên là chồng chéo các ô (mặc dù tôi không chắc chắn trong các kết hợp nào) và xoay bản đồ bình thường được sử dụng cho biến dạng (một lần nữa không có ý tưởng nào về chi tiết cụ thể) và quá khứ tôi cảm thấy hơi mất mát, cảm ơn vì bất kỳ trợ giúp!


Bạn có một mục tiêu trực quan cho nước? Tôi nhận thấy liên kết mà bạn trích dẫn đang sử dụng bản đồ bình thường để phản chiếu hình ảnh - một thứ có thể không hoàn toàn đúng theo hướng nghệ thuật phẳng / hoạt hình mà bạn đã thể hiện. Có nhiều cách để điều chỉnh kỹ thuật này theo các phong cách khác, nhưng chúng tôi cần một số hướng dẫn để chúng tôi biết phải nhắm đến điều gì.
DMGregory

Bạn có thể sử dụng giải pháp dòng chảy của mình dưới dạng độ dốc cho các hạt mà bạn thả lỏng trong luồng. Có lẽ đắt tiền, vì bạn sẽ cần rất nhiều trong số họ.
Bram

Tôi sẽ không giải quyết điều này bằng một shader, tôi sẽ làm theo cách đơn giản đã được sử dụng qua nhiều thế kỷ, chỉ cần vẽ nó và có 8 hình vẽ khác nhau về nước và 8 hình vẽ khác nhau của nước chạm vào bờ. Sau đó thêm lớp phủ màu nếu bạn muốn có địa hình khác nhau và thêm ngẫu nhiên vào như rắc đá, cá hoặc bất cứ thứ gì xuống sông. Btw với 8 khác nhau tôi có nghĩa là cứ 45 độ quay để có một sprite khác nhau
Yosh Synergi

@YoshSynergi Tôi muốn dòng sông chảy theo bất kỳ hướng nào thay vì 8 hướng và tôi muốn tránh có ranh giới rõ ràng giữa các cạnh của gạch, tương tự như kết quả đạt được trong trình tạo bóng được liên kết
Ross Taylor-Turner

@Bram đó là một tùy chọn tôi đang xem xét mà tôi có thể đạt được, nhưng cũng nghĩ rằng nó sẽ cần quá nhiều hạt để có hiệu quả, đặc biệt là khi máy ảnh được thu nhỏ rất nhiều
Ross Taylor-Turner

Câu trả lời:


11

Tôi không có bất kỳ gạch tiện dụng nào trông đẹp mắt với độ méo, vì vậy đây là phiên bản của hiệu ứng mà tôi đã chế giễu với các gạch Kenney này :

Hoạt hình hiển thị nước chảy trong tilemap.

Tôi đang sử dụng một sơ đồ như thế này, trong đó màu đỏ = dòng chảy phải và màu xanh lá cây = hướng lên, màu vàng là cả hai. Mỗi pixel tương ứng với một ô, với pixel dưới cùng bên trái là ô ở (0, 0) trong hệ tọa độ thế giới của tôi.

8/8

Và một kết cấu mô hình sóng như thế này:

nhập mô tả hình ảnh ở đây

Tôi quen thuộc nhất với cú pháp kiểu hlsl / CG của Unity, vì vậy bạn sẽ cần điều chỉnh trình tạo bóng này một chút cho bối cảnh glsl của bạn, nhưng điều đó rất đơn giản để làm.

// 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.