簡體   English   中英

是否有任何適用於延遲着色的與順序無關的透明技術?

[英]Are there any Order-Independent Transparency techniques suitable for deferred shading?

我已經為我的 OpenGL 引擎調查了許多與順序無關的透明度方法,起初我想我想使用加權平均混合,以最大限度地提高速度。

但是,我的引擎使用延遲着色,在選擇混合技術時需要考慮到這一點。 理想情況下,我想要一種不會要求我實現前向着色以用於半透明對象的技術。

在許多情況下,我需要使用透明度:

  • 草/頭發(抗鋸齒切口)
  • 玻璃(七彩混色)
  • 淡入淡出的對象
  • 煙/雲
  • 水/液體(會涉及折射,我知道真正的 OIT 在這里是不可能的)
  • Sparks/Magic/Fire(不需要點亮,可以使用添加劑混合,不用擔心這些)

為了速度,我願意犧牲圖像的正確性(因此我最初選擇了加權平均混合)。 我不需要每一層半透明物體都被點亮,但我至少希望最前面的像素被正確點亮。

我正在使用 OpenGL 3.x+ 核心上下文,所以我想避免任何需要 OpenGL 4.x 的東西(就像使用一樣可愛),但我可以自由使用 OpenGL 2 中沒有的任何東西。 X。

我的問題是:延遲着色的最佳順序無關透明度技術是什么?,和/或:使用延遲着色時對半透明對象進行照明/着色的最佳方法是什么?

PS有沒有更好的方法來渲染不依賴混合的抗鋸齒切口(草/頭發/葉子)? 純 alpha 測試往往會產生丑陋的混疊。

我不確定它是否適合您的延遲渲染器,但您可能會考慮加權、混合順序無關的透明度。 有一個沒有彩色傳輸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);
}

我這樣做的方式:

  • 使用透明表面的抖動渲染(也稱為推斷渲染)以低分辨率渲染整個場景,使用繪制 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
  • 使用 Blended 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,您可以看到粗糙的表面傳輸: 結果

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM