2D 하향식 타 일식 유수 흐름을 어떻게 렌더링합니까? 영감을 얻은 하향식 타일 기반의 상당히

Dwarf Fortress에서 영감을 얻은 하향식 타일 기반의 상당히 그래픽적인 2D 게임을 만들고 있습니다. 나는 많은 타일을 다루는 게임 세계에서 강을 구현할 시점에 있으며 각 타일의 빨간색 방향으로 아래에 표시된 것처럼 각 타일의 흐름 방향을 계산했습니다.

방향이있는 강 타일의 예

그래픽 스타일을 참조하기 위해 내 게임의 현재 모습은 다음과 같습니다.

그래픽 스타일의 게임 내 샷

내가 필요한 것은 각 강 타일에서 흐르는 물에 애니메이션을 적용하여 흐름이 주변 타일과 혼합되어 타일 가장자리가 분명하지 않도록하는 기술입니다.

내가 찾은 가장 가까운 예는 http://www.rug.nl/society-business/centre-for-information-technology/research/hpcv/publications/watershader/에 설명되어 있지만 잘 모르겠습니다. 무슨 일이 일어나고 있는지 이해할 수있는 시점에서? 다이내믹 라이팅을 구현하기 위해 셰이더 프로그래밍에 대해 충분히 이해하고 있지만 링크 된 기사에서 취한 접근 방식에 대해 잘 알 수 없습니다.

누군가 위의 효과가 어떻게 달성되는지 설명하거나 원하는 결과를 얻을 수있는 다른 접근법을 제안 할 수 있습니까? 위의 솔루션의 일부는 타일을 겹치고 (어떤 조합인지 확실하지 않지만) 왜곡에 사용되는 노멀 맵을 회전시키고 (특정 사항에 대해서는 전혀 알지 못함) 과거에 조금 잃어 버린 것으로 생각합니다. 어떤 도움이라도!



답변

왜곡 이 잘 생기는 타일이 없었기 때문에 대신 Kenney 타일을 사용 하여 조롱 한 효과 버전이 있습니다 .

타일 ​​맵에 흐르는 물을 보여주는 애니메이션.

빨간색 = 오른쪽 흐름과 녹색 = 위쪽 흐름과 같은 흐름 맵을 사용하고 있습니다. 노란색입니다. 각 픽셀은 하나의 타일에 해당하며 왼쪽 하단 픽셀은 월드 좌표계에서 (0, 0)의 타일입니다.

8x8

그리고 다음과 같은 웨이브 패턴 텍스처 :

여기에 이미지 설명을 입력하십시오

Unity의 hlsl / CG 스타일 구문에 가장 익숙하므로 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;
}


답변