从我的工作 Shadertoy 转换到 Unity Compute Shader 时,流体模拟出现异常行为

7
我正在尝试在Unity中使用计算着色器复制我在Shadertoy上工作的2D流体,并希望很快将其移植到3D。但是当我按照相同的方式复制算法时,会出现一些非常奇怪的行为(可以在我录制的这个视频中看到)。我已经尝试了调试所有我能想到的东西,但是我无法弄清楚它们为什么不同。在此捕获中我正在可视化向量矩阵(与查看我的shadertoy时按空格键相同)。
我创建了一个Pastebin网页,其中包含我用于执行驱动速度矩阵的Navier-Stokes方程式的代码核心部分。模拟的主要内容如下:
float4 S(RWTexture2D<float4> target, uint2 id)
{
    return target[(id.xy + resolution)%resolution];
}

void Fluid(RWTexture2D<float4> target, uint2 id, float2 offset, float4 values, inout float2 velocity, inout float pressure, inout float divergence, inout float neighbors)
{
    float2 v = S(target, id.xy + offset);
    float4 s = S(target, id.xy + offset.xy - v);
    
    float2 o= normalize(offset);
    
    velocity += o * (s.w - values.w);
    
    pressure += s.w;
    
    divergence += dot(o, s.xy);
    
    ++neighbors;
}

void StepVelocity(uint3 id, RWTexture2D<float4> write, RWTexture2D<float4> read, bool addJet)
{
    //sample our current values, then sample the values from the cell our velocity is coming from
    float4 values = S(read, id.xy);
    values = S(read, id.xy - values.xy);
    
    float2 velocity = float2(0,0);
    float pressure = 0;
    float neighbors = 0;
    float divergence = 0;
    
    //sample neighboring cells
    Fluid(read, id.xy, float2(0, 1), values, velocity, pressure, divergence, neighbors);
    Fluid(read, id.xy, float2(0, -1), values, velocity, pressure, divergence, neighbors);
    Fluid(read, id.xy, float2(1, 0), values, velocity, pressure, divergence, neighbors);
    Fluid(read, id.xy, float2(-1, 0), values, velocity, pressure, divergence, neighbors);

    velocity = velocity / neighbors;
    divergence = divergence / neighbors;
    pressure = pressure/neighbors;
    
    values.w = pressure-divergence;
    values.xy -= velocity;
    
    if (addJet && distance(id.xy, float2(resolution / 2.0, resolution / 2.0)) < 10)
        values = float4(0, .25, 0, values.w);
    
    write[id.xy] = values;
}

这应该是相当简单的,我尽力注释了Shadertoy,以便易于理解(它们是相同的代码,适用于不同的环境)。 这里是驱动模拟的C#代码。 我知道请求别人深入研究我的代码很不方便,但我完全不确定自己做错了什么,这让我疯了。我在Shadertoy上有完全相同的算法,但在Unity计算着色器中表现得非常奇怪,我无法弄清楚原因。每次“步进”时,我都会确保切换读/写纹理,以便清晰地向前移动模拟而不产生干扰。
对于修复问题的任何想法/提示将不胜感激。
1个回答

4

我改变了一些东西来使它工作:

首先,像素坐标需要偏移0.5。在计算着色器中,坐标是整数,但在ShaderToy中传递的坐标来自SV_Position输入,该输入已经应用了这个偏移。

SV_Position描述像素位置。在所有着色器中都可用以获得带有0.5偏移的像素中心。

gl_FragCoord也是如此。默认情况下它偏移0.5。当您向位置添加速度时,这很重要。如果没有偏移,各个方向上的行为将不同。

其次,我使用了采样器来进行输入,点过滤似乎在这里不起作用。否则代码基本相同,应该很容易理解。

以下是它的外观:

进入图片描述

FluidSimulation.cs:

public class FluidSimulation : MonoBehaviour
{
    private RenderTexture In;
    private RenderTexture Out;
    private RenderTexture DrawIn;
    private RenderTexture DrawOut;
    
    private int nthreads = 8;
    private int threadresolution => (resolution / nthreads);
    private int stepKernel;

    [Range(8, 1024)] public int resolution = 800;
    [Range(0, 50)] public int stepsPerFrame = 8;
    public ComputeShader Compute;
    public Material OutputMaterial;

    void Start() => Reset();   

    private void Reset()
    {
        In = CreateTexture(RenderTextureFormat.ARGBHalf);
        Out = CreateTexture(RenderTextureFormat.ARGBHalf);
        DrawIn = CreateTexture(RenderTextureFormat.ARGBHalf);
        DrawOut = CreateTexture(RenderTextureFormat.ARGBHalf);
        
        stepKernel = Compute.FindKernel("StepKernel");
        Compute.SetFloat("resolution", resolution);        
    }

    void Update()
    {
        for(int i = 0; i<stepsPerFrame; i++) Step();
    }

    void Step()
    {
        Compute.SetTexture(stepKernel, "In", In);
        Compute.SetTexture(stepKernel, "Out", Out);
        Compute.SetTexture(stepKernel, "DrawIn", DrawIn);
        Compute.SetTexture(stepKernel, "DrawOut", DrawOut);     

        Compute.Dispatch(stepKernel, threadresolution, threadresolution, 1);
        
        OutputMaterial.SetTexture("_MainTex", DrawOut);

        SwapTex(ref In, ref Out);
        SwapTex(ref DrawIn, ref DrawOut);        
    }

    protected RenderTexture CreateTexture(RenderTextureFormat format)
    {
        RenderTexture tex = new RenderTexture(resolution, resolution, 0, format);

        //IMPORTANT FOR GPU SHADERS, allows random access (like gpus will do)
        tex.enableRandomWrite = true;
        tex.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;        
        tex.filterMode = FilterMode.Bilinear;
        tex.wrapMode = TextureWrapMode.Clamp;
        tex.useMipMap = false;
        tex.Create();

        return tex;
    }

    void SwapTex(ref RenderTexture In, ref RenderTexture Out)
    {
        RenderTexture tmp = In;
        In = Out;
        Out = tmp;       
    }
}

计算着色器:

#pragma kernel StepKernel

float resolution;

Texture2D<float4> In;
SamplerState samplerIn;
RWTexture2D<float4> Out;

Texture2D<float4> DrawIn;
SamplerState samplerDrawIn;
RWTexture2D<float4> DrawOut;

float4 Sample(Texture2D<float4> t, SamplerState s, float2 coords) {
    return t.SampleLevel(samplerIn, coords / resolution, 0);
}

void Fluid(float2 coord, float2 offset, inout float2 velocity, inout float pressure, inout float divergence, inout float neighbors)
{
    // Sample buffer C, which samples B, which samples A, making our feedback loop    
    float4 s = Sample(In, samplerIn, coord + offset - Sample(In, samplerIn, coord + offset).xy);

    // gradient of pressure from the neighboring cell to ours
    float sampledPressure = s.w;    

    //add the velocity scaled by the pressure that its exerting
    velocity += offset * sampledPressure;

    // add pressure
    pressure += sampledPressure;

    // divergence of velocity
    divergence += dot(offset, s.xy);

    //increase number of neighbors sampled
    neighbors++;
}

float4 StepVelocity(float2 id) {

    //sample from the previous state    
    float4 values = Sample(In, samplerIn, id - Sample(In, samplerIn, id).xy);
    
    float2 velocity = float2(0, 0);
    float divergence = 0.;
    float pressure = 0., neighbors = 0.;

    Fluid(id.xy, float2( 0., 1.), velocity, pressure, divergence, neighbors);
    Fluid(id.xy, float2( 0.,-1.), velocity, pressure, divergence, neighbors);
    Fluid(id.xy, float2( 1., 0.), velocity, pressure, divergence, neighbors);
    Fluid(id.xy, float2(-1., 0.), velocity, pressure, divergence, neighbors);

    //average the samples
    velocity /= neighbors;
    divergence /= neighbors;
    pressure /= neighbors;

    //output pressure in w, velocity in xy
    values.w = pressure - divergence;
    values.xy -= velocity;
    
    float2 p1 = float2(.47, .2);
    if (length(id.xy - resolution * p1) < 10.) {
        values.xy = float2(0, .5);
    }
    float2 p2 = float2(.53, .8);
    if (length(id.xy - resolution * p2) < 10.) {
        values.xy = float2(0, -.5);
    }
    return values;
}

float4 StepFluid(float2 id) {

    for (int i = 0; i < 4; i++)
        id -= Sample(In, samplerIn, id).xy;

    float4 color = Sample(DrawIn, samplerDrawIn, id);

    float2 p1 = float2(.47, .2);
    if (length(id.xy - resolution * p1) < 10.) {
        color = float4(0, 1, 0, 1);
    }
    float2 p2 = float2(.53, .8);
    if (length(id.xy - resolution * p2) < 10.) {
        color = float4(1, 0, 0, 1);
    }
    color *= .999;
    return color;
}

[numthreads(8, 8, 1)]
void StepKernel (uint3 id : SV_DispatchThreadID)
{     
    float2 coord = float2(id.x + .5, id.y + .5);
    Out[id.xy] = StepVelocity(coord);
    DrawOut[id.xy] = StepFluid(coord);
}

我还想提一件事,您可以通过单击ShaderToy编辑器窗口底部的analyze按钮,查看您的ShaderToy着色器的翻译后的HLSL代码。 我不知道这个功能的存在,但在尝试将来自ShaderToy的着色器转换为Unity时,它可能非常有用。


非常感谢您帮我解决这个问题!它让我疯狂了,现在我可以看到我已经非常接近了,只需要记住一些技术细节。我稍微修改了我的代码以添加偏移量并使用采样器/双线性过滤,现在它按预期工作了!您让我的周末变得愉快,朋友。我非常感激您的帮助。 - Turmolt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接