這段時間研究了一下順序無關的半透明實體繪製實現方法,順便完善了OSG顯示引擎半透明實體繪製部分,看了看相關資料,正確性、效率最好的是基於權重函數的混合算法和GPU端鏈表法,還有個基於矩的數學方法,繪製的效果比基於權重函數的混合算法效果更好,太複雜,俺也懶得看了,看也看不懂,只有DX代碼,放棄了。兩種方法存在的問題羅列一下,給其他有興趣實現的OIT透明繪製的朋友做個參考,本人比較水:)。先來張我心中的女神的圖片,內衣是基於權重混合函數實現的:)---,(我自己都覺得自己是藝術家了,看什麼都心靜如水:)。。。,我老婆說這些美女(還有幾個沒穿衣服的)皮膚太油不真實(皮膚繪製也是個課題,難點),她也沒怪我拿個裸體模型天天在她眼前顯擺)。
內內特寫:)----, 這就是推動俺不斷學習的動力,比看倉老師的片子還有成就感。
再放個車的圖片,香車美女。。。
下面簡單說說算法公式,其實不知原理,套公式一樣能實現,只是不明白原理的話,出了問題浪費點精氣神,好在公式簡單明瞭,如果想了解更詳細些,搜一下大神的論文Weighted Blended Order-Independent Transparency,就是G3D 革新引擎的作者 Morgan McGuire,這個傢伙是個牛人,Nvidia 的人。
-
Weighted Blended OIT
算法的演進:
第一版 Meshkin 2007 年的首先提出的 Sort-independent alpha blending 論文,公式如下
簡單明瞭, , C0 是背景顏色, 從該公式可以看出他只是簡單的將源顏色求和 ,再加上 目標顏色 * (1-源顏色a值求和) 做爲混合後的最終顏色,該公式也不是人家瞎整,雖然不具備通用性,但對於顏色相近和a值較小的情況下效果最好,公式推斷可以看看論文,如何把順序相關的因子排除掉,爲後續 平均加權 OIT 方法奠定了堅實的基礎。
第二版 Bavoil and Myers 的加權平均法 ,也算是對上一方法的改進版,公式如下
該公式基於加權求和的方式對 Meshkin 的方法做了改進,正確性提高了很多,而且更具有通用性,但當a 爲 0 的時候,本來應該不貢獻顏色的片元也參與了加權求和,使顏色變淡,總是透明的。該公式也有個缺點,後續版本也一樣,如果不透明的實體使用該公式繪製,C0項目 爲0, 但公式前半部分 變爲 Ci 求和項 除於 ai 求和項, 平均了顏色,所以明明不透明的實體也變得透明瞭。
第三版 由 Morgan McGuire 2013 年提出,對上述公式加以改進,解決了a 爲0 ,紅字部分沒解決。公式如下:
對a 求和改成 乘法了,全透明實體的繪製問題解決了, 該公式考慮到 片元深度和 a 的影響 ,加入了w()權重函數。一般我們認爲離的近的半透明物體罩着後面的物體,看上去顏色也最貼最前面的物體的顏色,論文裏幾個效果比較好的權重公式:
照公式套,實現上還是比較簡單,基於加權平均和權重的混合OIT實現方法,無非是求和,平均。
下面我給出我的實現代碼, demo 程序就不給了,代碼粘貼拷貝很容易 使用OSG後處理來實現。
void WB_OITRenderPass::initialize()
{
_pass = new osg::Group();
_pass->setName("WB_OIT");
_accumTexture = createTexture2D(getFrameBufferWidth(), getFrameBufferHeight(), GL_RGBA16F_ARB, GL_RGBA, GL_FLOAT);
_accumTexture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
_accumTexture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
_accumTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
_accumTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
_accumAlphaTexture = createTexture2D(getFrameBufferWidth(), getFrameBufferHeight(), GL_R16F, GL_RED, GL_FLOAT);
_accumAlphaTexture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
_accumAlphaTexture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
_accumAlphaTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
_accumAlphaTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
// Accum pass.
_accumPass = createRTTCamera(getFrameBufferWidth(), getFrameBufferHeight(), false, GL_COLOR_BUFFER_BIT);
_accumPass->setName("WB_OIT_AccumPass");
_accumPass->attach(osg::Camera::COLOR_BUFFER0, _accumTexture);
_accumPass->attach(osg::Camera::COLOR_BUFFER1, _accumAlphaTexture);
_accumPass->attach(osg::Camera::DEPTH_BUFFER, getContext()->_depthBuffer);
_accumPass->addChild(getContext()->getPipeline()->getSceneRoot());
_accumPass->setCullCallback(new PassCallback(getContext()->getPipeline()));
_accumPass->setClearColor(osg::Vec4(0.0, 0.0, 0.0, 1.0));
osg::StateSet* ss = setShaderProgram(_accumPass, "trans_accum", osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
ss->setMode(GL_CULL_FACE, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
osg::Depth* depth = new osg::Depth;
depth->setFunction(osg::Depth::LEQUAL);
depth->setWriteMask(false);
ss->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
osg::BlendFunc* bf = new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE, osg::BlendFunc::ZERO, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
ss->setAttributeAndModes(bf, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
// Draw pass
_drawPass = createRTTCamera(getFrameBufferWidth(), getFrameBufferHeight(), true, GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
_drawPass->attach(osg::Camera::COLOR_BUFFER, getContext()->_outputTextureWithLum);
_drawPass->attach(osg::Camera::DEPTH_BUFFER, getContext()->_tempDepthBuffer);
_drawPass->setName("WB_OIT_DrawPass");
ss = setShaderProgram(_drawPass, "trans_draw");
ss->setTextureAttributeAndModes(0, _accumTexture);
ss->addUniform(new osg::Uniform("Accumulate", 0));
ss->setTextureAttributeAndModes(1, _accumAlphaTexture);
ss->addUniform(new osg::Uniform("AccumulateAlpha", 1));
ss->setTextureAttributeAndModes(2, getContext()->_outputTexture);
ss->addUniform(new osg::Uniform("Opacity", 2));
_pass->addChild(_accumPass);
_pass->addChild(_drawPass);
getContext()->addPass(this);
}
shader 部分代碼:
#version 420 core
in vec4 osg_Vertex;
in vec3 osg_Normal;
in vec4 osg_MultiTexCoord0;
uniform mat4 osg_ViewMatrix;
uniform mat4 osg_ViewMatrixInverse;
uniform mat4 osg_ModelViewMatrix;
uniform mat4 osg_ModelViewProjectionMatrix;
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 WorldNormal;
void main()
{
TexCoords = osg_MultiTexCoord0.xy;
mat4 worldMatrix = osg_ViewMatrixInverse * osg_ModelViewMatrix;
WorldPos = (worldMatrix * osg_Vertex).xyz;
mat3 normalMatrix = mat3(worldMatrix);
WorldNormal = normalize(normalMatrix * osg_Normal);
gl_Position = osg_ModelViewProjectionMatrix * osg_Vertex;
}
#version 420 core
#extension GL_ARB_shader_image_load_store : enable
layout (early_fragment_tests) in;
#include "chunk_math.glsl"
#include "forward_pbr_shading_parameters.frag"
#include "chunk_shadowmap.frag"
#include "chunk_light.frag"
#include "tone_mapping.frag"
float weight(float z, float a)
{
return clamp(pow(min(1.0, a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - z * 0.9, 3.0), 1e-2, 3e3);
}
vec4 shading()
{
vec4 _albedo = getAlbedo();
float _roughness = getRoughness();
float _metallic = getMetallic();
float _ao = getAo();
vec3 camPos = getCameraPosition();
vec3 worldNormal = normalize(getWorldNormal());
vec4 worldPos = getWorldPosition();
float _ssao = 1.0;
float _shadow = 0.0;
#if !defined(MATERIAL_IS_TRANSPARENT) && !defined(MAP_IS_TRANSPARENT)
if(transparency < 0.0001)
{
_ssao = getSSAO();
_shadow = getShadow(vec4(WorldPos,1.0), WorldNormal);
}
#endif
vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + _albedo.rgb * metallic;
vec3 diffuse = _albedo.rgb * (1.0 - metallic);
vec3 Lo = vec3(0.0);
vec3 ambient = vec3(0.0);
#ifdef NUMBER_LIGHTS
for (int i = 0; i < NUMBER_LIGHTS; ++i)
{
Light light = Lights[i];
Lo += CalcPointOrDirectionalLight( light, camPos, worldPos.xyz, worldNormal, F0, diffuse, metallic, roughness, ambient );
}
#endif
//Ambient lighting
#ifdef IRRADIANCEMAP
vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 kD = 1.0 - kS;
kD *= 1.0 - _metallic;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * _albedo;
ambient = (kD * diffuse) * _ao;
#else
ambient = ambient * _albedo.rgb * _ao;
#endif
Lo = Lo*( 1.0 - _shadow);
vec3 color = ambient* _ssao + Lo;
#ifdef IRRADIANCEMAP
//vec3 I = normalize(WorldPos - camPos);
//vec3 R = reflect(I, normalize(WorldNormal));
//color = texture(irradianceMap, R).rgb;
//color = texture(irradianceMap,TexCoords2).rgb;
#endif
color = tonemap(color);
vec4 outputColor = vec4(color, _albedo.a);
return outputColor;
}
layout (location = 0) out vec4 out_accumColor;
layout (location = 1) out float out_accumAlpha;
void main()
{
vec4 color = shading();
color.rgb *= color.a;
float w = weight(gl_FragCoord.z, color.a);
out_accumColor = vec4(color.rgb * w, color.a);
out_accumAlpha = color.a * w;
}
#version 420 core
in vec4 osg_MultiTexCoord0;
in vec4 osg_Vertex;
uniform mat4 osg_ModelViewProjectionMatrix;
out vec2 TexCoord;
void main()
{
TexCoord = osg_MultiTexCoord0.xy;
gl_Position = osg_ModelViewProjectionMatrix * osg_Vertex;
}
#version 420 core
#include "chunk_math.glsl"
in vec2 TexCoord;
uniform sampler2D Accumulate;
uniform sampler2D AccumulateAlpha;
uniform sampler2D Opacity;
layout (location = 0) out vec4 fragColor;
void main()
{
ivec2 fragCoord = ivec2(gl_FragCoord.xy);
vec4 accum = texelFetch(Accumulate, fragCoord, 0);
float r = accum.a;
accum.a = texelFetch(AccumulateAlpha, fragCoord, 0).r;
vec4 color = vec4(accum.rgb / clamp(accum.a, 0.0001, 50000.0), r);
color.rgb = pow(color.rgb, vec3(1.0/2.2));
vec4 opaqueColor = texelFetch(Opacity, fragCoord, 0).rgba;
vec3 outputColor = mix(color.rgb, opaqueColor.rgb, color.a);
//luminance 爲實現FXAA反走樣計算亮度值,如果只是測試,a爲 1 就可以了。
fragColor = vec4(outputColor, luminance(outputColor));
}
結論: Weighted Blended OIT ,效果還過得去,比非OIT 繪製正確性合理的多,但畢竟是去掉了公式中順序相關項推出來的透明融合,存在一些缺陷:
1) 不透明實體被繪製成透明實體,見上文紅字部分,如果紋理帶a通道,透明通道內不能實現鏤空效果。
2) 繪製的不是十分正確,只是整體上過的去;
-
GPU 端鏈表法 實現OIT
直接上源碼,至於實現原理過程,OpenGL 編程指南介紹的很清楚了,最後說下該方法存在的問題:
程序初始化部分,這部分代碼最有價值的是 OSG 原子計數器, TBO 存儲的實現。很慶幸OSG 雖然沒落了,但API更新還算及時,支持 計算,幾何着色器, 原子操作, TBO 等等。
void PPLL_OITPass::initialize()
{
_OITRoot = new osg::Group();
_FinalOIT = createTexture2D(getViewportWidth(), getViewportHeight(), GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
_FinalOIT->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
_FinalOIT->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
_FinalOIT->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
_FinalOIT->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
//Head pointer texture.
_head_pointer_texture = createTexture2D(getViewportWidth(), getViewportHeight(), GL_R32UI, GL_RED_INTEGER_EXT, GL_UNSIGNED_INT);
_head_pointer_texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
_head_pointer_texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
_head_pointer_texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
_head_pointer_texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
_head_pointer_image = new osg::BindImageTexture(0,
_head_pointer_texture,
osg::BindImageTexture::READ_WRITE, GL_R32UI,
0,
false,
0);
// 原子計數器創建部分
osg::ref_ptr<osg::UIntArray> atomicCounterArray = new osg::UIntArray;
atomicCounterArray->push_back(0);
osg::ref_ptr<osg::AtomicCounterBufferObject> acbo = new osg::AtomicCounterBufferObject;
acbo->setUsage(GL_STREAM_COPY);
atomicCounterArray->setBufferObject(acbo.get());
osg::ref_ptr<osg::AtomicCounterBufferBinding> acbb = new osg::AtomicCounterBufferBinding(0, atomicCounterArray.get(), 0, sizeof(GLuint));
acbb->setUpdateCallback(new ResetAtomicCounter);
//創建鏈表
#define OIT_LAYERS 3
int linked_list_buffer_item_size = 2048 * 2048 * OIT_LAYERS;
osg::ref_ptr<osg::UIntArray> linked_list_buffer = new osg::UIntArray;
osg::ref_ptr<osg::PixelDataBufferObject> pdbo = new osg::PixelDataBufferObject();
pdbo->setUsage(GL_DYNAMIC_COPY);
pdbo->setTarget(GL_TEXTURE_BUFFER);
pdbo->setDataSize(linked_list_buffer_item_size * sizeof(GLuint) * 4);
linked_list_buffer->setBufferObject(pdbo);
osg::ref_ptr<osg::TextureBuffer> tbo = new osg::TextureBuffer;
tbo->setBufferData(linked_list_buffer);
tbo->setInternalFormat(GL_RGBA32UI_EXT);
osg::BindImageTexture* linked_list_image = new osg::BindImageTexture(1,
tbo.get(),
osg::BindImageTexture::WRITE_ONLY, GL_RGBA32UI_EXT,
0,
false,
0);
//每繪製一幀前,用前序渲染初始化鏈表紋理。紅寶書裏直接通過綁定PBO 操作初始化,我這裏直接做一次離屏渲染。
_OITClearHeadPointerPass = createRTTCamera(getViewportWidth(), getViewportHeight(), true, GL_DEPTH_BUFFER_BIT);
_OITClearHeadPointerPass->attach(osg::Camera::DEPTH_BUFFER, getContext()->_tempDepthBuffer);
osg::StateSet* ss = setShaderProgram(_OITClearHeadPointerPass, "clear_head_pointer");
ss->setAttribute(_head_pointer_image);
//生成鏈表的過程。
_OITPass = createRTTCamera(getViewportWidth(), getViewportHeight(), false, 0);
_OITPass->setName("OIT_Trans_accum");
_OITPass->attach(osg::Camera::DEPTH_BUFFER, getContext()->_depthBuffer);
_OITPass->addChild(getContext()->getPipeline->getSceneRoot());
_OITPass->setCullCallback(new PassCallback(getContext()->getPipeline()));
_OITPass->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
ss = setShaderProgram(_OITPass, "build_lists", osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
ss->setAttribute(_head_pointer_image);
ss->setAttribute(linked_list_image);
ss->setAttribute(acbb);
ss->setMode(GL_BLEND, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
ss->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
ss->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
ss->addUniform(new osg::Uniform("itemCount", linked_list_buffer_item_size));
//使用鏈表通過排序,混合生成最終圖像。
_OITDrawPass = createRTTCamera(getViewportWidth(), getViewportHeight(), true);
_OITDrawPass->attach(osg::Camera::DEPTH_BUFFER, getContext()->_tempDepthBuffer);
_OITDrawPass->attach(osg::Camera::COLOR_BUFFER0, _FinalOIT);
ss = setShaderProgram(_OITDrawPass, "resolve_lists");
ss->setAttribute(_head_pointer_image);
ss->setAttribute(linked_list_image);
ss->setTextureAttributeAndModes(0, getContext()->_outputTexture);
ss->addUniform(new osg::Uniform("Final", 0));
_OITRoot->addChild(_OITClearHeadPointerPass);
_OITRoot->addChild(_OITPass);
_OITRoot->addChild(_OITDrawPass);
getContext()->addPass(this);
}
void PPLL_OITPass::uninitialize()
{
getContext()->removePass(this);
}
#version 420 core
in vec4 osg_MultiTexCoord0;
in vec3 osg_Normal;
in vec4 osg_Vertex;
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 WorldNormal;
uniform mat4 osg_ViewMatrixInverse;
uniform mat4 osg_ModelViewMatrix;
uniform mat4 osg_ViewMatrix;
uniform mat4 osg_ModelViewProjectionMatrix;
void main()
{
TexCoords = osg_MultiTexCoord0.xy;
mat4 worldMatrix = osg_ViewMatrixInverse * osg_ModelViewMatrix;
WorldPos = (worldMatrix * osg_Vertex).xyz;
mat3 normalMatrix = mat3(worldMatrix);
WorldNormal = normalize(normalMatrix * osg_Normal);
gl_Position = osg_ModelViewProjectionMatrix * osg_Vertex;
}
#version 420 core
layout (early_fragment_tests) in;
layout (binding = 0, r32ui) uniform uimage2D head_pointer_image;
layout (binding = 1, rgba32ui) uniform writeonly uimageBuffer list_buffer;
layout (binding = 0, offset = 0) uniform atomic_uint list_counter;
#include "chunk_math.glsl"
#include "forward_pbr_shading_parameters.frag"
#include "chunk_light.frag"
#include "tone_mapping.frag"
vec4 shading()
{
vec4 _albedo = getAlbedo();
float _roughness = getRoughness();
float _metallic = getMetallic();
float _ao = getAo();
vec3 camPos = getCameraPosition();
vec4 worldPos = getWorldPosition();
vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + _albedo.rgb * metallic;
vec3 diffuse = _albedo.rgb * (1.0 - metallic);
vec3 worldNormal;
worldNormal = WorldNormal;
vec3 Lo = vec3(0.0);
vec3 ambient = vec3(0.0);
#ifdef NUMBER_LIGHTS
for (int i = 0; i < NUMBER_LIGHTS; ++i)
{
Light light = Lights[i];
Lo += CalcPointOrDirectionalLight( light, camPos, worldPos.xyz, worldNormal, F0, diffuse, metallic, roughness, ambient );
}
#endif
//Ambient lighting
#ifdef IRRADIANCEMAP
vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 kD = 1.0 - kS;
kD *= 1.0 - _metallic;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * _albedo;
ambient = (kD * diffuse) * _ao;
#else
ambient = ambient * _albedo.rgb * _ao;
#endif
vec3 color = ambient + Lo;
color = tonemap(color);
//Gamma correction
color = pow(color, vec3(1.0/2.2));
#ifdef IRRADIANCEMAP
//vec3 I = normalize(WorldPos - camPos);
//vec3 R = reflect(I, normalize(WorldNormal));
//color = texture(irradianceMap, R).rgb;
//color = texture(irradianceMap,TexCoords2).rgb;
#endif
return vec4(color, _albedo.a);
}
uniform int itemCount;
void main(void)
{
uint index;
uint old_head;
uvec4 item;
index = atomicCounterIncrement(list_counter) + 2;
if(index > itemCount-1)
{
index = 1;
old_head = 0;
imageAtomicExchange(head_pointer_image, ivec2(gl_FragCoord.xy), uint(index));
item.x = old_head;
item.y = packUnorm4x8(vec4(1.0,0.0,0.0,1.0));
item.z = floatBitsToUint(gl_FragCoord.z);
imageStore(list_buffer, int(0), item);
}
else
{
old_head = imageAtomicExchange(head_pointer_image, ivec2(gl_FragCoord.xy), uint(index));
vec4 surface_color = shading();
item.x = old_head;
item.y = packUnorm4x8(surface_color);
item.z = floatBitsToUint(gl_FragCoord.z);
imageStore(list_buffer, int(index-1), item);
}
}
#version 420 core
in vec4 osg_Vertex;
in vec4 osg_MultiTexCoord0;
uniform mat4 osg_ModelViewProjectionMatrix;
out vec2 TexCoord;
void main(void)
{
TexCoord = osg_MultiTexCoord0.xy;
gl_Position = osg_ModelViewProjectionMatrix * osg_Vertex;
}
#version 420 core
#pragma import_defines ( USE_FXAA )
#include "chunk_math.glsl"
// The per-pixel image containing the head pointers
layout (binding = 0, r32ui) uniform uimage2D head_pointer_image;
// Buffer containing linked lists of fragments
layout (binding = 1, rgba32ui) uniform uimageBuffer list_buffer;
// This is the output color
layout (location = 0) out vec4 finalColor;
// This is the maximum number of overlapping fragments allowed
#define MAX_FRAGMENTS 40
// Temporary array used for sorting fragments
uvec4 fragment_list[MAX_FRAGMENTS];
in vec2 TexCoord;
uniform sampler2D Final;
void main(void)
{
int current_index;
uint fragment_count = 0;
current_index = int(imageLoad(head_pointer_image, ivec2(gl_FragCoord).xy).x) - 1;
while (current_index >= 0 && fragment_count < MAX_FRAGMENTS)
{
uvec4 fragment = imageLoad(list_buffer, int(current_index));
fragment_list[fragment_count] = fragment;
current_index = int(fragment.x) -1;
fragment_count++;
}
uint i, j;
if (fragment_count > 1)
{
for (i = 0; i < fragment_count - 1; i++)
{
for (j = i + 1; j < fragment_count; j++)
{
uvec4 fragment1 = fragment_list[i];
uvec4 fragment2 = fragment_list[j];
float depth1 = uintBitsToFloat(fragment1.z);
float depth2 = uintBitsToFloat(fragment2.z);
if (depth1 < depth2)
{
fragment_list[i] = fragment2;
fragment_list[j] = fragment1;
}
}
}
}
vec3 backgroundColor = texture(Final, TexCoord.xy).rgb;
for (i = 0; i < fragment_count; i++)
{
vec4 modulator = unpackUnorm4x8(fragment_list[i].y);
backgroundColor = mix(backgroundColor.rgb, modulator.rgb, modulator.a);
}
finalColor = vec4(backgroundColor, luminance(backgroundColor));
}
鏈表法OIT 無疑繪製半透明實體是最正確的,但它有不可克服的缺陷:
1)資源佔用未可預知, 要預先分配,對複雜透明實體,層次比較深,很容易把鏈表預分配的內存資源吃盡,導致繪製不正確。鏈表一個項佔用 64字節, 比如滿屏2K屏,一個透明層佔用 2048 *2048*64 bytes = 256M ;
2)物體縮小後會閃爍,放大後閃爍消失,不清楚是不是原子操作問題還是沒有mipmap.
如果對正確性要求不是很高,基於權重混合的OIT 方法足夠了,而且效率也比較高。