是否有适用于延迟着色的顺序无关透明度技术?

时间:2014-08-12 13:36:15

标签: c++ opengl transparency deferred-shading

我已经为我的OpenGL引擎调查了许多与顺序无关的透明度方法,起初我以为我想使用加权平均混合来最大化速度。

但是,我的引擎使用延迟着色,我需要在选择混合技术时考虑到这一点。理想情况下,我想要一种不会要求我实现前向着色以用于半透明对象的技术。

在许多情况下,我需要使用透明度:

  • 草/头发(抗锯齿镂空)
  • 玻璃(彩色混合)
  • 淡入淡出的物体
  • 烟雾/云
  • 水/液体(会涉及折射,我知道真正的OIT在这里是不可能的)
  • Sparks / Magic / Fire(不需要点亮,可以使用添加剂混合,不用担心这些)

我愿意为了速度而牺牲图像的正确性(因此我最初选择加权平均混合)。我不需要点亮每一层半透明物体,但我至少希望最前面的像素能够正常点亮。

我正在使用OpenGL 3.x + Core Context,所以我想避免任何需要OpenGL 4.x的东西(尽可能使用它),但我可以自由使用任何不可用的东西。 OpenGL 2.x。

我的问题是:延迟着色的最佳顺序无关透明度技术是什么?和/或:在使用延迟着色时,点亮/遮蔽半透明对象的最佳方法是什么?

P.S。有没有更好的方法来渲染不依赖混合的抗锯齿切口(草/毛/叶)?纯alpha测试往往会产生难看的锯齿。

2 个答案:

答案 0 :(得分:0)

我不确定它是否适合您的延迟渲染器,但您可以考虑加权,混合顺序无关的透明度。有一个older version w/o colored transmissonweb)和一个newer version that supports colored transmissionweb)以及许多其他内容。它非常快,因为它只使用一个不透明,一个透明度和一个合成传递,它适用于OpenGL 3.2+ 我实现了第一个版本,它运行得很好,具体取决于你的场景和适当调整的加权函数,但是有高alpha值的问题。我没有通过论文中的加权函数获得良好的结果,但仅在使用线性,标准化的眼睛空间z值之后。 请注意,使用OpenGL时< 4.0你不能指定每个缓冲区的混合函数(glBlendFunci),所以你需要解决这个问题(参见第一篇论文)。

  • 使用这些附件设置帧缓冲区:
    • 0:RGBA8,不透明
    • 1:RGBA16F,累积
    • 2:R16F,revealage
  • 清除附件#0到您的屏幕清晰颜色(r,g,b,1),#1到(0,0,0,1)和#2到0。
  • 将不透明几何体渲染到附件#0和深度缓冲区。

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);

  • 将透明几何体渲染到附件#1和#2。关闭深度缓冲区写入,但启用深度测试。

    glDepthMask(GL_FALSE);
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFuncSeparate(GL_ONE,GL_ONE,GL_ZERO,GL_ONE_MINUS_SRC_ALPHA);

写入累积和揭示目标的片段着色器部分如下所示:

uniform mat4 projectionMatrix;

layout (location = 1) out vec4 accum;
layout (location = 2) out float revealage;

/// @param color Regular RGB reflective color of fragment, not pre-multiplied
/// @param alpha Alpha value of fragment
/// param wsZ Window-space-z value == gl_FragCoord.z
void writePixel(vec3 color, float alpha, float wsZ) {
    float ndcZ = 2.0 * wsZ - 1.0;
    // linearize depth for proper depth weighting
    //See: https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh
    //or: https://stackoverflow.com/questions/11277501/how-to-recover-view-space-position-given-view-space-depth-value-and-ndc-xy
    float linearZ = (projectionMatrix[2][2] + 1.0) * wsZ / (projectionMatrix[2][2] + ndcZ);
    float tmp = (1.0 - linearZ) * alpha;
    //float tmp = (1.0 - wsZ * 0.99) * alpha * 10.0; // <-- original weighting function from paper #2
    float w = clamp(tmp * tmp * tmp * tmp * tmp * tmp, 0.0001, 1000.0);
    accum = vec4(color * alpha* w, alpha);
    revealage = alpha * w;
}
  • 绑定附件纹理#1和#2,并通过使用合成着色器绘制四边形将它们合成到附件#0。

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE_MINUS_SRC_ALPHA,GL_SRC_ALPHA);

组合的片段着色器如下所示:

uniform sampler2DMS accumTexture;
uniform sampler2DMS revealageTexture;

in vec2 texcoordVar;
out vec4 fragmentColor;

void main() {
    ivec2 bufferCoords = ivec2(gl_FragCoord.xy);
    vec4 accum = texelFetch(accumTexture, bufferCoords, 0);
    float revealage = accum.a;
    // save the blending and color texture fetch cost
    /*if (revealage == 1.0) {
        discard;
    }*/
    accum.a = texelFetch(revealageTexture, bufferCoords, 0).r;
    // suppress underflow
    if (isinf(accum.a)) {
        accum.a = max(max(accum.r, accum.g), accum.b);
    }
    // suppress overflow
    if (any(isinf(accum.rgb))) {
        accum = vec4(isinf(accum.a) ? 1.0 : accum.a);
    }
    vec3 averageColor = accum.rgb / max(accum.a, 1e-4);
    // dst' = (accum.rgb / accum.a) * (1 - revealage) + dst * revealage
    fragmentColor = vec4(averageColor, revealage);
}

答案 1 :(得分:0)

我的方式:

  • 以低分辨率渲染整个场景,并为透明表面(又称inferred rendering)进行抖动渲染,使用绘图ID(只要框架中唯一,只要是唯一的ID)旋转抖动蒙版,然后绘制绘图ID, WorldNormals,FragDepth(请参阅Getting World Position from Depth Buffer Value),BRDF Alpha(请参阅this)到帧缓冲区
  • 照常进行照明(仅漫反射和镜面反射),SSR或SSAO通过
  • 以常规分辨率(也称为“材料通过”)将不透明和“切割”表面渲染到“不透明”帧缓冲区(OFB)
  • 创建2个“透明”帧缓冲区(FB0和FB1),其中FB1为FB0分辨率的一半。
  • 在启用深度测试/写入的情况下渲染透明曲面而不融合到FB0
  • 将FB0缓冲区深度位分配到FB1
  • 使用Blended OIT将透明表面再次渲染到FB1,但使用glDepthFunc(GL_GREATER)和glDepthMask(GL_FALSE),手动测试不透明表面深度以在着色器中丢弃(速度稍慢,但AFAIK您无法绑定2个深度缓冲区)< / li>
  • 为OFB生成mipmap
  • 手动合成从FB1到OFB mip 0最远的透明表面,从OFB mip 1采样并在着色器中向上采样(速度较慢,但​​允许失真和粗糙/有色透射)。
  • 再次为OFB生成mipmaps
  • 从FB0到OFB mip 0组合最近的透明表面,从OFB mip 1采样,现在它包含FB1的透明表面

有关如何进行合成以及如何构造“透明”帧缓冲区的信息,请参见Morgan McGuire's blog。 使用绘图ID帧缓冲区和法线来重建透明曲面,我使用简单的加权平均值,其权重对应于当前法线点帧缓冲区的法线(法线点本身为1)。

缺点:

  • 这不是单遍,而是您的第一次渲染和光照遍是在较低的分辨率下完成的,因此不会降低性能,而且“最远的”透明表面是常规分辨率的一半。
  • 在退回到IBL之前,您只能获得4层透明性。如果您将spherical harmonics用于动态光源作为后备解决方案,则可以。使用较大的抖动蒙版可以得到8层,但是重建表面会比较慢。

上方:

  • 它可以像任何延迟渲染器一样提供大量灯光
  • 由于混合使用了“延后”和“正向”渲染,因此可以使用每种材质的环境和方便的BRDF查找表
  • 诸如SSR和SSAO之类的大量查找技术都是在较低的分辨率下完成的,这有助于提高性能。
  • 像SSR和SSAO一样,照明是在低分辨率下完成的
  • 这可以实现奇特的效果,例如屏幕空间折射和透明表面透射。
  • 具有两个透明层意味着折射表面会变形,尽管只有最近的表面才能使它们后面的表面变形(否则,您会得到难看的伪像)

我的Blended OIT的重量函数(具有相同不透明度的更近的表面总是获得更高的重量):

void WritePixel(vec3 premultipliedReflect, float coverage)
{
    float z = abs(CameraSpaceDepth);
    float w = clamp(pow(abs(1 / z), 4.f) * coverage * coverage, 6.1*1e-4, 1e5);

    out_0 = vec4(premultipliedReflect, coverage) * w;
    out_1 = vec4(1 - coverage); //so you can render without blending
}

我的合成功能:

vec4 accum = texelFetch(in_Buffer0, ivec2(gl_FragCoord.xy), 0);
float r = texelFetch(in_Buffer1, ivec2(gl_FragCoord.xy), 0).r;
out_Buffer0 = vec4(accum.rgb / clamp(accum.a, 6.1*1e-4, 6.55*1e5), r);

有关“ CameraSpaceDepth”的信息,请参见this;有关fp值的信息,请参见this

这是this model的结果,其POC脏乱,您可以看到粗糙的表面传输: Result