Opengl es 使用与输入和输出相同的纹理渲染到自定义帧缓冲区

Opengl es 使用与输入和输出相同的纹理渲染到自定义帧缓冲区,opengl-es,glsl,framebuffer,render-to-texture,Opengl Es,Glsl,Framebuffer,Render To Texture,ShaderToy中的某些片段着色器(例如流体动力学)使用与输入和输出相同的缓冲区。但当我尝试在我的C/C++代码中这样做时,它不起作用(我渲染奇怪的棋盘工件,比如不一致的视觉内存)。为了解决这个问题,我必须使用两种不同的帧缓冲区A、B和翻转纹理(首先将A渲染为B,然后将B渲染回A) 我知道,由于内存一致性问题,OpenGL不允许使用相同的纹理作为输入和输出(?)。 但难道没有比使用两个帧缓冲区更优雅的解决方案吗?例如,使用一些锁或临时缓存(我不知道有什么同步标志来处理这个问题) 编辑-回答评论

ShaderToy中的某些片段着色器(例如流体动力学)使用与输入和输出相同的缓冲区。但当我尝试在我的C/C++代码中这样做时,它不起作用(我渲染奇怪的棋盘工件,比如不一致的视觉内存)。为了解决这个问题,我必须使用两种不同的帧缓冲区A、B和翻转纹理(首先将A渲染为B,然后将B渲染回A)

我知道,由于内存一致性问题,OpenGL不允许使用相同的纹理作为输入和输出(?)。 但难道没有比使用两个帧缓冲区更优雅的解决方案吗?例如,使用一些锁或临时缓存(我不知道有什么同步标志来处理这个问题)

编辑-回答评论/问题的详细信息:

vec4 solveFluid(sampler2D smp, vec2 uv, vec2 w, float time, vec3 mouse, vec3 lastMouse)
{
    const float K = 0.2;
    const float v = 0.55;
    
    vec4 data = textureLod(smp, uv, 0.0);
    vec4 tr = textureLod(smp, uv + vec2(w.x , 0), 0.0);
    vec4 tl = textureLod(smp, uv - vec2(w.x , 0), 0.0);
    vec4 tu = textureLod(smp, uv + vec2(0 , w.y), 0.0);
    vec4 td = textureLod(smp, uv - vec2(0 , w.y), 0.0);
    
    vec3 dx = (tr.xyz - tl.xyz)*0.5;
    vec3 dy = (tu.xyz - td.xyz)*0.5;
    vec2 densDif = vec2(dx.z ,dy.z);
    
    data.z -= dt*dot(vec3(densDif, dx.x + dy.y) ,data.xyz); //density
    vec2 laplacian = tu.xy + td.xy + tr.xy + tl.xy - 4.0*data.xy;
    vec2 viscForce = vec2(v)*laplacian;
    data.xyw = textureLod(smp, uv - dt*data.xy*w, 0.).xyw; //advection
    
    vec2 newForce = vec2(0);
    data.xy += dt*(viscForce.xy - K/dt*densDif + newForce); //update velocity
    data.xy = max(vec2(0), abs(data.xy)-1e-4)*sign(data.xy); //linear velocity decay
    
    #ifdef USE_VORTICITY_CONFINEMENT
    data.w = (tr.y - tl.y - tu.x + td.x);
    vec2 vort = vec2(abs(tu.w) - abs(td.w), abs(tl.w) - abs(tr.w));
    vort *= VORTICITY_AMOUNT/length(vort + 1e-9)*data.w;
    data.xy += vort;
    #endif
    
    data.y *= smoothstep(.5,.48,abs(uv.y-0.5)); //Boundaries
    
    data = clamp(data, vec4(vec2(-10), 0.5 , -10.), vec4(vec2(10), 3.0 , 10.));
    
    return data;
}
OpenGL(取决于GL版本)有一些非常具体的规则 当使用同一纹理作为渲染目标时,可以和不能执行 和采样器输入。如果您的用例可以在此集合中实现 需求的定义与否不清楚,因为您没有解释什么 正是你需要或想要在这里做的

基本上,我想实现流体动力学解算器(例如,上面链接的ShaderToy的解算器)以及其他偏微分方程解算器。这意味着每个像素的输出取决于相邻像素的卷积掩模(导数、拉普拉斯函数、平均值)。也可能有一些移动(平流),这意味着从远处的像素读取值

目前,我意识到伪影主要出现在我读/写不同位置的像素时,即它是非本地的(例如,像素[100100]取决于像素[10,10])

来自Shadertoy的简单流体解算器示例:

vec4 solveFluid(sampler2D smp, vec2 uv, vec2 w, float time, vec3 mouse, vec3 lastMouse)
{
    const float K = 0.2;
    const float v = 0.55;
    
    vec4 data = textureLod(smp, uv, 0.0);
    vec4 tr = textureLod(smp, uv + vec2(w.x , 0), 0.0);
    vec4 tl = textureLod(smp, uv - vec2(w.x , 0), 0.0);
    vec4 tu = textureLod(smp, uv + vec2(0 , w.y), 0.0);
    vec4 td = textureLod(smp, uv - vec2(0 , w.y), 0.0);
    
    vec3 dx = (tr.xyz - tl.xyz)*0.5;
    vec3 dy = (tu.xyz - td.xyz)*0.5;
    vec2 densDif = vec2(dx.z ,dy.z);
    
    data.z -= dt*dot(vec3(densDif, dx.x + dy.y) ,data.xyz); //density
    vec2 laplacian = tu.xy + td.xy + tr.xy + tl.xy - 4.0*data.xy;
    vec2 viscForce = vec2(v)*laplacian;
    data.xyw = textureLod(smp, uv - dt*data.xy*w, 0.).xyw; //advection
    
    vec2 newForce = vec2(0);
    data.xy += dt*(viscForce.xy - K/dt*densDif + newForce); //update velocity
    data.xy = max(vec2(0), abs(data.xy)-1e-4)*sign(data.xy); //linear velocity decay
    
    #ifdef USE_VORTICITY_CONFINEMENT
    data.w = (tr.y - tl.y - tu.x + td.x);
    vec2 vort = vec2(abs(tu.w) - abs(td.w), abs(tl.w) - abs(tr.w));
    vort *= VORTICITY_AMOUNT/length(vort + 1e-9)*data.w;
    data.xy += vort;
    #endif
    
    data.y *= smoothstep(.5,.48,abs(uv.y-0.5)); //Boundaries
    
    data = clamp(data, vec4(vec2(-10), 0.5 , -10.), vec4(vec2(10), 3.0 , 10.));
    
    return data;
}
目前,我意识到伪影主要出现在我读/写不同位置的像素时,即它是非本地的(例如,像素[100100]取决于像素[10,10])

是的,这在GPU上永远不会起作用,因为对各个片段着色器调用的顺序没有特别的保证。因此,如果调用写入像素
[100100]
将看到调用写入
[10,10]
的结果,或者原始数据将是完全随机的。根据规范,在当前的读/写场景中进行读取时会得到未定义的值,因此理论上,您甚至不会得到一个或另一个值,但会看到部分写入或完全不同的值(尽管在现实硬件上不太可能出现这种情况)

在渲染管道中,任何这种规模的顺序保证都没有意义,因此也没有可以手动添加的特定同步方法来解决此问题

为了解决这个问题,我必须使用两种不同的帧缓冲区A、B和翻转纹理(首先将A渲染为B,然后将B渲染回A)


是的,乒乓球方法是您在这个用例中应该做的。老实说,在这种情况下,它不应该招致任何显著的性能损失,因为您似乎对每个输出像素都写入了一次,所以您不需要额外的“未触及”像素副本。因此,所有的成本都是额外的内存。

可能(
gl\u LastFragData
)OpenGL(取决于gl版本)有一些非常具体的规则,说明当使用相同的纹理作为渲染目标和采样器输入时,可以做什么和不能做什么。您的用例是否可以在这组需求中实现还不清楚,因为您没有解释您在这里需要或想要做什么。谢谢。是的,它不会增加性能成本(如果我拆分偶数帧和奇数帧),但会增加程序的复杂性