3

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

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

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

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

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

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

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

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

4

2 回答 2

2

我不确定它是否适合您的延迟渲染器,但您可能会考虑加权、混合顺序无关的透明度。有一个不带彩色传输web)的旧版本和一个支持彩色传输web)和许多其他东西的新版本。它非常快,因为它只使用一个不透明、一个透明度和一个合成通道,并且它适用于 OpenGL 3.2+。
我实现了第一个版本,它工作得很好,具体取决于您的场景和适当调整的加权函数,但存在高 alpha 值的问题。我使用论文中的加权函数没有得到好的结果,但只有在使用线性、归一化的眼睛空间 z 值之后。
请注意,当使用 OpenGL < 4.0 时,您不能为每个缓冲区指定混合函数 (glBlendFunci),因此您需要解决这个问题(参见第一篇论文)。

  • 使用这些附件设置帧缓冲区:
    • 0:RGBA8,不透明
    • 1:RGBA16F,累积
    • 2:R16F,显露
  • 清除附件 #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);
}
于 2018-03-07T17:25:15.883 回答
0

我这样做的方式:

  • 以低分辨率渲染整个场景,使用透明表面的抖动渲染(也称为推断渲染),使用绘制 ID 旋转抖动蒙版(任何 ID,只要它在帧中是唯一的),渲染绘制 ID、WorldNormals、FragDepth(请参阅获取World Position from Depth Buffer Value ), BRDF Alpha (see this ) to a framebuffer
  • 照常通过您的照明(仅漫反射和镜面反射)、SSR 或 SSAO
  • 以正常分辨率(也称为“材质通道”)将不透明和“截止”表面渲染到“不透明”帧缓冲区 (OFB)
  • 创建 2 个“透明”帧缓冲区(FB0 和 FB1),其中 FB1 为 FB0 分辨率的一半。
  • 渲染透明表面而不混合到启用深度测试/写入的 FB0
  • Blit FB0 缓冲区深度位到 FB1
  • 使用混合 OIT 将透明表面再次渲染到 FB1,但使用 glDepthFunc(GL_GREATER) 和 glDepthMask(GL_FALSE),手动测试不透明表面深度以在着色器中丢弃(有点慢,但 AFAIK 你不能绑定 2 个深度缓冲区)
  • 为 OFB 生成 mipmap
  • 手动合成从 FB1 到 OFB mip 0 的最远透明表面,从 OFB mip 1 采样并在着色器中向上(较慢但允许失真和粗糙/彩色传输)。
  • 再次为 OFB 生成 mipmap
  • 合成从 FB0 到 OFB mip 0 的最近透明表面,从 OFB mip 1 采样,现在包含来自 FB1 的透明表面

有关如何进行合成以及如何构建“透明”帧缓冲区的信息,请参阅Morgan McGuire 的博客。使用您的绘制 ID 帧缓冲区和法线重建透明表面,我使用简单的加权平均值,其权重对应于当前法线点帧缓冲区的法线(法线点本身给出 1)。

缺点:

  • 这不是单通道,但您的第一个渲染和照明通道是在较低的分辨率下完成的,因此它不会影响性能,而且“最远”的透明表面是正常分辨率的一半。
  • 在回退到 IBL 之前,您只能获得 4 层透明度,如果您将球谐函数用于动态灯光作为后备解决方案,那么您也可以。您可以使用更大的抖动蒙版获得 8 层,但重建表面会更慢。

优势:

  • 它允许像任何延迟渲染器一样的大量灯光
  • 因为你混合了延迟渲染和正向渲染,它允许每个材质环境和 BRDF 查找表,这很方便
  • SSR 和 SSAO 等重查找技术以较低的分辨率完成,这对性能有更大的帮助。
  • 与 SSR 和 SSAO 一样,照明是在低分辨率下完成的
  • 这允许像屏幕空间折射和透明表面传输这样的奇特效果。
  • 有两个透明层意味着折射表面可以扭曲自己,尽管只有最近的表面才能扭曲它们后面的表面(否则你会得到难看的伪影)

我的混合 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”的内容和有关fp 值的内容

这是这个模型的结果,它带有一个肮脏的 POC,你可以看到粗糙的表面传输: 结果

于 2020-12-07T08:56:45.970 回答