- Implement WBOIT
This commit is contained in:
Jozufozu 2025-02-15 18:13:16 -08:00
parent c438fb57ca
commit 5d16ebda60
10 changed files with 310 additions and 16 deletions

View file

@ -31,7 +31,9 @@ public class IndirectPrograms extends AtomicReferenceCounted {
private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl"); private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl");
private static final ResourceLocation DOWNSAMPLE_FIRST = Flywheel.rl("internal/indirect/downsample_first.glsl"); private static final ResourceLocation DOWNSAMPLE_FIRST = Flywheel.rl("internal/indirect/downsample_first.glsl");
private static final ResourceLocation DOWNSAMPLE_SECOND = Flywheel.rl("internal/indirect/downsample_second.glsl"); private static final ResourceLocation DOWNSAMPLE_SECOND = Flywheel.rl("internal/indirect/downsample_second.glsl");
public static final List<ResourceLocation> UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND);
private static final ResourceLocation FULLSCREEN = Flywheel.rl("internal/indirect/fullscreen.vert");
private static final ResourceLocation OIT_COMPOSITE = Flywheel.rl("internal/indirect/oit_composite.frag");
private static final Compile<InstanceType<?>> CULL = new Compile<>(); private static final Compile<InstanceType<?>> CULL = new Compile<>();
private static final Compile<ResourceLocation> UTIL = new Compile<>(); private static final Compile<ResourceLocation> UTIL = new Compile<>();
@ -45,11 +47,13 @@ public class IndirectPrograms extends AtomicReferenceCounted {
private final PipelineCompiler pipeline; private final PipelineCompiler pipeline;
private final CompilationHarness<InstanceType<?>> culling; private final CompilationHarness<InstanceType<?>> culling;
private final CompilationHarness<ResourceLocation> utils; private final CompilationHarness<ResourceLocation> utils;
private final CompilationHarness<ResourceLocation> fullscreen;
private IndirectPrograms(PipelineCompiler pipeline, CompilationHarness<InstanceType<?>> culling, CompilationHarness<ResourceLocation> utils) { private IndirectPrograms(PipelineCompiler pipeline, CompilationHarness<InstanceType<?>> culling, CompilationHarness<ResourceLocation> utils, CompilationHarness<ResourceLocation> fullscreen) {
this.pipeline = pipeline; this.pipeline = pipeline;
this.culling = culling; this.culling = culling;
this.utils = utils; this.utils = utils;
this.fullscreen = fullscreen;
} }
private static List<String> getExtensions(GlslVersion glslVersion) { private static List<String> getExtensions(GlslVersion glslVersion) {
@ -88,8 +92,9 @@ public class IndirectPrograms extends AtomicReferenceCounted {
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, vertexComponents, fragmentComponents, EXTENSIONS); var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, vertexComponents, fragmentComponents, EXTENSIONS);
var cullingCompiler = createCullingCompiler(sources); var cullingCompiler = createCullingCompiler(sources);
var utilCompiler = createUtilCompiler(sources); var utilCompiler = createUtilCompiler(sources);
var fullscreenCompiler = createFullscreenCompiler(sources);
IndirectPrograms newInstance = new IndirectPrograms(pipelineCompiler, cullingCompiler, utilCompiler); IndirectPrograms newInstance = new IndirectPrograms(pipelineCompiler, cullingCompiler, utilCompiler, fullscreenCompiler);
setInstance(newInstance); setInstance(newInstance);
} }
@ -125,6 +130,17 @@ public class IndirectPrograms extends AtomicReferenceCounted {
.harness("utilities", sources); .harness("utilities", sources);
} }
private static CompilationHarness<ResourceLocation> createFullscreenCompiler(ShaderSources sources) {
return UTIL.program()
.link(UTIL.shader(GlCompat.MAX_GLSL_VERSION, ShaderType.VERTEX)
.nameMapper($ -> "fullscreen/fullscreen")
.withResource(FULLSCREEN))
.link(UTIL.shader(GlCompat.MAX_GLSL_VERSION, ShaderType.FRAGMENT)
.nameMapper(rl -> "fullscreen/" + ResourceUtil.toDebugFileNameNoExtension(rl))
.withResource(s -> s))
.harness("fullscreen", sources);
}
static void setInstance(@Nullable IndirectPrograms newInstance) { static void setInstance(@Nullable IndirectPrograms newInstance) {
if (instance != null) { if (instance != null) {
instance.release(); instance.release();
@ -148,8 +164,8 @@ public class IndirectPrograms extends AtomicReferenceCounted {
setInstance(null); setInstance(null);
} }
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, Material material) { public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, Material material, boolean oit) {
return pipeline.get(instanceType, contextShader, material); return pipeline.get(instanceType, contextShader, material, oit);
} }
public GlProgram getCullingProgram(InstanceType<?> instanceType) { public GlProgram getCullingProgram(InstanceType<?> instanceType) {
@ -172,10 +188,15 @@ public class IndirectPrograms extends AtomicReferenceCounted {
return utils.get(DOWNSAMPLE_SECOND); return utils.get(DOWNSAMPLE_SECOND);
} }
public GlProgram getOitCompositeProgram() {
return fullscreen.get(OIT_COMPOSITE);
}
@Override @Override
protected void _delete() { protected void _delete() {
pipeline.delete(); pipeline.delete();
culling.delete(); culling.delete();
utils.delete(); utils.delete();
fullscreen.delete();
} }
} }

View file

@ -70,7 +70,7 @@ public class InstancingPrograms extends AtomicReferenceCounted {
} }
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material) { public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material) {
return pipeline.get(instanceType, contextShader, material); return pipeline.get(instanceType, contextShader, material, false);
} }
@Override @Override

View file

@ -50,7 +50,7 @@ public final class PipelineCompiler {
ALL.add(this); ALL.add(this);
} }
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material) { public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material, boolean oit) {
var light = material.light(); var light = material.light();
var cutout = material.cutout(); var cutout = material.cutout();
var shaders = material.shaders(); var shaders = material.shaders();
@ -66,7 +66,7 @@ public final class PipelineCompiler {
MaterialShaderIndices.cutoutSources() MaterialShaderIndices.cutoutSources()
.index(cutout.source()); .index(cutout.source());
return harness.get(new PipelineProgramKey(instanceType, contextShader, light, shaders, cutout != CutoutShaders.OFF, FrameUniforms.debugOn())); return harness.get(new PipelineProgramKey(instanceType, contextShader, light, shaders, cutout != CutoutShaders.OFF, FrameUniforms.debugOn(), oit));
} }
public void delete() { public void delete() {
@ -128,7 +128,8 @@ public final class PipelineCompiler {
.source()); .source());
var debug = key.debugEnabled() ? "_debug" : ""; var debug = key.debugEnabled() ? "_debug" : "";
var cutout = key.useCutout() ? "_cutout" : ""; var cutout = key.useCutout() ? "_cutout" : "";
return "pipeline/" + pipeline.compilerMarker() + "/frag/" + material + "/" + light + "_" + context + cutout + debug; var oit = key.oit() ? "_oit" : "";
return "pipeline/" + pipeline.compilerMarker() + "/frag/" + material + "/" + light + "_" + context + cutout + debug + oit;
}) })
.requireExtensions(extensions) .requireExtensions(extensions)
.enableExtension("GL_ARB_conservative_depth") .enableExtension("GL_ARB_conservative_depth")
@ -146,6 +147,11 @@ public final class PipelineCompiler {
comp.define("_FLW_USE_DISCARD"); comp.define("_FLW_USE_DISCARD");
} }
}) })
.onCompile((key, comp) -> {
if (key.oit()) {
comp.define("_FLW_OIT");
}
})
.withResource(API_IMPL_FRAG) .withResource(API_IMPL_FRAG)
.withResource(key -> key.materialShaders() .withResource(key -> key.materialShaders()
.fragmentSource()) .fragmentSource())
@ -217,6 +223,7 @@ public final class PipelineCompiler {
* @param light The light shader to use. * @param light The light shader to use.
*/ */
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader, LightShader light, public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader, LightShader light,
MaterialShaders materialShaders, boolean useCutout, boolean debugEnabled) { MaterialShaders materialShaders, boolean useCutout, boolean debugEnabled,
boolean oit) {
} }
} }

View file

@ -45,6 +45,17 @@ public final class MaterialRenderState {
setupWriteMask(material.writeMask()); setupWriteMask(material.writeMask());
} }
public static void setupOit(Material material) {
setupTexture(material);
setupBackfaceCulling(material.backfaceCulling());
setupPolygonOffset(material.polygonOffset());
setupDepthTest(material.depthTest());
WriteMask mask = material.writeMask();
boolean writeColor = mask.color();
RenderSystem.colorMask(writeColor, writeColor, writeColor, writeColor);
}
private static void setupTexture(Material material) { private static void setupTexture(Material material) {
Samplers.DIFFUSE.makeActive(); Samplers.DIFFUSE.makeActive();
AbstractTexture texture = Minecraft.getInstance() AbstractTexture texture = Minecraft.getInstance()

View file

@ -13,6 +13,7 @@ import java.util.List;
import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.material.Transparency;
import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms; import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
@ -36,6 +37,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final List<IndirectInstancer<I>> instancers = new ArrayList<>(); private final List<IndirectInstancer<I>> instancers = new ArrayList<>();
private final List<IndirectDraw> indirectDraws = new ArrayList<>(); private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final List<MultiDraw> multiDraws = new ArrayList<>(); private final List<MultiDraw> multiDraws = new ArrayList<>();
private final List<MultiDraw> transparentDraws = new ArrayList<>();
private final IndirectPrograms programs; private final IndirectPrograms programs;
private final GlProgram cullProgram; private final GlProgram cullProgram;
@ -130,6 +132,7 @@ public class IndirectCullingGroup<I extends Instance> {
private void sortDraws() { private void sortDraws() {
multiDraws.clear(); multiDraws.clear();
transparentDraws.clear();
// sort by visual type, then material // sort by visual type, then material
indirectDraws.sort(DRAW_COMPARATOR); indirectDraws.sort(DRAW_COMPARATOR);
@ -138,7 +141,9 @@ public class IndirectCullingGroup<I extends Instance> {
// if the next draw call has a different VisualType or Material, start a new MultiDraw // if the next draw call has a different VisualType or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || incompatibleDraws(draw1, indirectDraws.get(i + 1))) { if (i == indirectDraws.size() - 1 || incompatibleDraws(draw1, indirectDraws.get(i + 1))) {
multiDraws.add(new MultiDraw(draw1.material(), draw1.isEmbedded(), start, i + 1)); var dst = draw1.material()
.transparency() == Transparency.TRANSLUCENT ? transparentDraws : multiDraws;
dst.add(new MultiDraw(draw1.material(), draw1.isEmbedded(), start, i + 1));
start = i + 1; start = i + 1;
} }
} }
@ -171,7 +176,7 @@ public class IndirectCullingGroup<I extends Instance> {
needsDrawSort = true; needsDrawSort = true;
} }
public void submit() { public void submitSolid() {
if (nothingToDo()) { if (nothingToDo()) {
return; return;
} }
@ -183,7 +188,7 @@ public class IndirectCullingGroup<I extends Instance> {
GlProgram lastProgram = null; GlProgram lastProgram = null;
for (var multiDraw : multiDraws) { for (var multiDraw : multiDraws) {
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material); var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material, false);
if (drawProgram != lastProgram) { if (drawProgram != lastProgram) {
lastProgram = drawProgram; lastProgram = drawProgram;
@ -197,8 +202,34 @@ public class IndirectCullingGroup<I extends Instance> {
} }
} }
public void submitTransparent() {
if (nothingToDo()) {
return;
}
buffers.bindForDraw();
drawBarrier();
GlProgram lastProgram = null;
for (var multiDraw : transparentDraws) {
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material, true);
if (drawProgram != lastProgram) {
lastProgram = drawProgram;
// Don't need to do this unless the program changes.
drawProgram.bind();
}
MaterialRenderState.setupOit(multiDraw.material);
multiDraw.submit(drawProgram);
}
}
public void bindForCrumbling(Material material) { public void bindForCrumbling(Material material) {
var program = programs.getIndirectProgram(instanceType, ContextShader.CRUMBLING, material); var program = programs.getIndirectProgram(instanceType, ContextShader.CRUMBLING, material, false);
program.bind(); program.bind();

View file

@ -48,6 +48,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final DepthPyramid depthPyramid; private final DepthPyramid depthPyramid;
private final WboitFrameBuffer wboitFrameBuffer;
public IndirectDrawManager(IndirectPrograms programs) { public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs; this.programs = programs;
programs.acquire(); programs.acquire();
@ -62,6 +64,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
matrixBuffer = new MatrixBuffer(); matrixBuffer = new MatrixBuffer();
depthPyramid = new DepthPyramid(programs); depthPyramid = new DepthPyramid(programs);
wboitFrameBuffer = new WboitFrameBuffer(programs);
} }
@Override @Override
@ -138,9 +142,17 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
Uniforms.bindAll(); Uniforms.bindAll();
for (var group : cullingGroups.values()) { for (var group : cullingGroups.values()) {
group.submit(); group.submitSolid();
} }
wboitFrameBuffer.setup();
for (var group : cullingGroups.values()) {
group.submitTransparent();
}
wboitFrameBuffer.composite();
MaterialRenderState.reset(); MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay(); TextureBinder.resetLightAndOverlay();
} }

View file

@ -0,0 +1,117 @@
package dev.engine_room.flywheel.backend.engine.indirect;
import org.lwjgl.opengl.ARBDrawBuffersBlend;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL46;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.gl.GlTextureUnit;
import net.minecraft.client.Minecraft;
public class WboitFrameBuffer {
public final int fbo;
private final IndirectPrograms programs;
private final int vao;
public int accum;
public int reveal;
private int lastWidth = -1;
private int lastHeight = -1;
public WboitFrameBuffer(IndirectPrograms programs) {
this.programs = programs;
fbo = GL46.glCreateFramebuffers();
vao = GL46.glCreateVertexArrays();
}
public void setup() {
var mainRenderTarget = Minecraft.getInstance()
.getMainRenderTarget();
createTextures(mainRenderTarget.width, mainRenderTarget.height);
// No depth writes, but we'll still use the depth test
GlStateManager._depthMask(false);
GlStateManager._enableBlend();
ARBDrawBuffersBlend.glBlendFunciARB(0, GL46.GL_ONE, GL46.GL_ONE); // accumulation blend target
ARBDrawBuffersBlend.glBlendFunciARB(1, GL46.GL_ZERO, GL46.GL_ONE_MINUS_SRC_COLOR); // revealage blend target
GlStateManager._blendEquation(GL46.GL_FUNC_ADD);
GL46.glNamedFramebufferTexture(fbo, GL46.GL_DEPTH_ATTACHMENT, mainRenderTarget.getDepthTextureId(), 0);
GlStateManager._glBindFramebuffer(GL46.GL_FRAMEBUFFER, fbo);
GL46.glClearBufferfv(GL46.GL_COLOR, 0, new float[]{0, 0, 0, 0});
GL46.glClearBufferfv(GL46.GL_COLOR, 1, new float[]{1, 1, 1, 1});
}
public void composite() {
var mainRenderTarget = Minecraft.getInstance()
.getMainRenderTarget();
mainRenderTarget.bindWrite(false);
var oitCompositeProgram = programs.getOitCompositeProgram();
GlStateManager._depthMask(false);
GlStateManager._depthFunc(GL46.GL_ALWAYS);
GlStateManager._enableBlend();
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
oitCompositeProgram.bind();
GlTextureUnit.T0.makeActive();
GlStateManager._bindTexture(accum);
GlTextureUnit.T1.makeActive();
GlStateManager._bindTexture(reveal);
// Empty VAO, the actual full screen triangle is generated in the vertex shader
GlStateManager._glBindVertexArray(vao);
GL46.glDrawArrays(GL46.GL_TRIANGLES, 0, 3);
}
public void delete() {
GL46.glDeleteTextures(accum);
GL46.glDeleteTextures(reveal);
GL46.glDeleteFramebuffers(fbo);
GL46.glDeleteVertexArrays(vao);
}
private void createTextures(int width, int height) {
if (lastWidth == width && lastHeight == height) {
return;
}
lastWidth = width;
lastHeight = height;
GL46.glDeleteTextures(accum);
GL46.glDeleteTextures(reveal);
accum = GL46.glCreateTextures(GL46.GL_TEXTURE_2D);
reveal = GL46.glCreateTextures(GL46.GL_TEXTURE_2D);
GL46.glNamedFramebufferDrawBuffers(fbo, new int[]{GL46.GL_COLOR_ATTACHMENT0, GL46.GL_COLOR_ATTACHMENT1});
GL46.glNamedFramebufferTexture(fbo, GL46.GL_COLOR_ATTACHMENT0, accum, 0);
GL46.glNamedFramebufferTexture(fbo, GL46.GL_COLOR_ATTACHMENT1, reveal, 0);
GL46.glTextureStorage2D(accum, 1, GL32.GL_RGBA32F, width, height);
GL46.glTextureStorage2D(reveal, 1, GL32.GL_R8, width, height);
for (int tex : new int[]{accum, reveal}) {
GL46.glTextureParameteri(tex, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
GL46.glTextureParameteri(tex, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
GL46.glTextureParameteri(tex, GL32.GL_TEXTURE_COMPARE_MODE, GL32.GL_NONE);
GL46.glTextureParameteri(tex, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE);
GL46.glTextureParameteri(tex, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE);
}
}
}

View file

@ -17,8 +17,20 @@ in vec2 _flw_crumblingTexCoord;
flat in uvec2 _flw_ids; flat in uvec2 _flw_ids;
#endif #endif
#ifdef _FLW_OIT
// your first render target which is used to accumulate pre-multiplied color values
layout (location = 0) out vec4 accum;
// your second render target which is used to store pixel revealage
layout (location = 1) out float reveal;
#else
out vec4 _flw_outputColor; out vec4 _flw_outputColor;
#endif
float _flw_diffuseFactor() { float _flw_diffuseFactor() {
if (flw_material.cardinalLightingMode == 2u) { if (flw_material.cardinalLightingMode == 2u) {
return diffuseFromLightDirections(flw_vertexNormal); return diffuseFromLightDirections(flw_vertexNormal);
@ -33,6 +45,11 @@ float _flw_diffuseFactor() {
} }
} }
float linearize_depth(float d, float zNear, float zFar) {
float z_n = 2.0 * d - 1.0;
return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
}
void _flw_main() { void _flw_main() {
flw_sampleColor = texture(flw_diffuseTex, flw_vertexTexCoord); flw_sampleColor = texture(flw_diffuseTex, flw_vertexTexCoord);
flw_fragColor = flw_vertexColor * flw_sampleColor; flw_fragColor = flw_vertexColor * flw_sampleColor;
@ -99,5 +116,31 @@ void _flw_main() {
} }
#endif #endif
_flw_outputColor = flw_fogFilter(color); color = flw_fogFilter(color);
color.a = 0.9;
#ifdef _FLW_OIT
float depth = linearize_depth(gl_FragCoord.z, _flw_cullData.znear, _flw_cullData.zfar);
// insert your favorite weighting function here. the color-based factor
// avoids color pollution from the edges of wispy clouds. the z-based
// factor gives precedence to nearer surfaces
//float weight = clamp(pow(min(1.0, color.a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - gl_FragCoord.z * 0.9, 3.0), 1e-2, 3e3);
float weight = max(min(1.0, max(max(color.r, color.g), color.b) * color.a), color.a) *
clamp(0.03 / (1e-5 + pow(depth / 200, 4.0)), 1e-2, 3e3);
// blend func: GL_ONE, GL_ONE
// switch to pre-multiplied alpha and weight
accum = vec4(color.rgb * color.a, color.a) * weight;
// blend func: GL_ZERO, GL_ONE_MINUS_SRC_ALPHA
reveal = color.a;
#else
_flw_outputColor = color;
#endif
} }

View file

@ -0,0 +1,4 @@
void main() {
vec2 vertices[3] = vec2[3](vec2(-1, -1), vec2(3, -1), vec2(-1, 3));
gl_Position = vec4(vertices[gl_VertexID], 0, 1);
}

View file

@ -0,0 +1,48 @@
// shader outputs
layout (location = 0) out vec4 frag;
// color accumulation buffer
layout (binding = 0) uniform sampler2D accum;
// revealage threshold buffer
layout (binding = 1) uniform sampler2D reveal;
// epsilon number
const float EPSILON = 0.00001f;
// calculate floating point numbers equality accurately
bool isApproximatelyEqual(float a, float b) {
return abs(a - b) <= (abs(a) < abs(b) ? abs(b) : abs(a)) * EPSILON;
}
// get the max value between three values
float max3(vec3 v) {
return max(max(v.x, v.y), v.z);
}
void main() {
// fragment coordination
ivec2 coords = ivec2(gl_FragCoord.xy);
// fragment revealage
float revealage = texelFetch(reveal, coords, 0).r;
// save the blending and color texture fetch cost if there is not a transparent fragment
if (isApproximatelyEqual(revealage, 1.0f)) {
discard;
}
// fragment color
vec4 accumulation = texelFetch(accum, coords, 0);
// suppress overflow
if (isinf(max3(abs(accumulation.rgb)))) {
accumulation.rgb = vec3(accumulation.a);
}
// prevent floating point precision bug
vec3 average_color = accumulation.rgb / max(accumulation.a, EPSILON);
// blend pixels
frag = vec4(average_color, 1.0f - revealage);
}