Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.20]: Implement OIT Rendering. #9607

Draft
wants to merge 1 commit into
base: 1.20.x
Choose a base branch
from

Conversation

marchermans
Copy link
Contributor

@marchermans marchermans commented Jun 28, 2023

Order Independent Transparent Rendering

OIT Rendering, for short OIT, is a rendering mechanic which allows rendering several transparent objects without sorting their order first.

Existing mechanic

If we take a look at the existing rendering mechanics within Minecraft, then Mojang uses two major systems to handle layering transparent surfaces:

  1. Make sure to render the furthest away vertices first during transparent and semi-transparent surface rendering.
  2. Render transparent geometry after solid ones.
    This works virtually all of the time. But not in all:
    For example:
Expand to see examples of particles

image

As you can see, the smoke particles are semi-transparent, but the effect cloud particles in the background are not visible.

Expand to see examples of shulker bullet halos

image

image

As you can see, the entity renderer for the shulker and the clouds in the background are not rendered properly.

Expand to see examples of ice block interactions

image

Visible here is the main example of the rendering engine breaking transparency since the water is not visible behind the ice.

Impact

These are corner cases in vanilla, and the priority of fixing them is lower. However, these issues happen regularly in a modded environment when rendering additional components into the world. Examples of cases where this happens are custom particles, schematic previews, fake worlds, etc.

New mechanic

This new feature implements weighted order independent rendering based on the paper published by: McGuire and Bavoil1. The idea here is to capture all rendering calls which involve transparent subsections that we are interested in and draw them to a separate system.
This system emulates the usual ALPHA <-> 1-ALPHA rendering, but more on the restrictions of this new system later.

Implementation:

This new system uses additional hooks placed into the rendering pipelines for the level, particles, and CPU Bound computed buffers to check whether the RenderType or ParticleRenderType will render transparent components. If so, it will capture the call and all related contextual information needed to retrigger and store it. Once the world has been rendered, it binds a new custom render target used to perform the OIT Rendering of the captured calls. Using a new system to adapt shaders, the system will calculate the transparent processing that normally happens when you perform an ALPHA <-> 1-ALPHA blended rendering on the GPU, but this time in the shader.

Afterward, the resulting color and background coverage, known as the reveal, is used for blitting the transparent rendered output to the main buffer.

Results

Expand to see examples of particles

image

As you can see, the smoke particles are semi-transparent and the effect cloud particles behind them are now visible.

Expand to see examples of shulker bullet halos

image

image

As you can see, the backgrounds behind the shulker halo are rendered properly and are visible.

Expand to see examples of ice block interactions

image

Visible here is the main example of the rendering engine working properly, the water is visible behind the ice.

New Hooks and Events

The original PoC for this PR implemented the hooks raw into the OIT subsystem. However, I always envisioned that this system runs on top of several hooks for the community.
These hooks or events are the following:

  • RegisterGlslPreprocessorsEvent: Event fired when the preprocessors for an event are determined. Allows modders to register custom preprocessors that modify shaders. Used by Forges OIT system to wrap the shader execution and optionally handle the OIT render pass. Additional adaptations were made to
  • RenderTargetEvent.Create and RenderTargetEvent.Resize: Allows for managing custom RenderTarget-instances and implementations, which shaders can use. Forges OIT system uses a custom RenderTarget implementation with different FrameBuffers and attached textures to handle the Transparency calculation.
  • ShaderEvent.OnAttachmentInitialization fired when the shader instances are attached to GPU programs. Allows for the configuration of custom attributes when shaders are adapted.
  • ParticleRenderType#requiresTransparencySorting to indicate to external systems is this particle render type uses transparency components and requires sorting. Setting this to true will allow the render type to be transformed by OIT.
  • Custom render types for chunk Geometry: Although currently not exposed, the system now supports custom render types which are not the default transparency render type for chunk geometry sorting and OIT processing.

Current problems:

  • The weighting function: The current function was adapted from the recommended weighting function in the paper. However, it still needs improvement. Some colors get a bit muted (see ice and honey blocks). Over the next few weeks, we can still modify these weights and improve the accuracy to match vanilla better.
  • Not all forms of transparency are handled. I am looking at you: Weird additive transparency Mojang uses with the creepers...

Status:

The PR enables OIT rendering by default for now.
This is to gather feedback on the feature.
Currently, the shaders use an ARB extension for OpenGL, which is supposedly supported on all platforms Mojang runs on. Including Mac OSX; however, the actual impact still needs to be seen.

Performance impact:

By default, this has no negative impact.
However, depending on your environment, you can expect 10 to 15% FPS improvements (the more transparency-related rendering is needed for a scene, the bigger the impact, so oceans/beaches have a much larger impact than desserts). Besides this, the frame times get more stable, given that now no sorting needs to happen based on your position.

Footnotes

  1. Weighted Blended Order-Independent Transparency

@autoforge autoforge bot added Triage This request requires the active attention of the Triage Team. Requires labelling or reviews. 1.20 Feature This request implements a new feature. New Event This request implements a new event. labels Jun 28, 2023
@autoforge autoforge bot requested a review from a team June 28, 2023 10:02
@marchermans marchermans added the Rendering This request deals with rendering. These must be treated with care, and tend to progress slowly. label Jun 28, 2023
@SizableShrimp
Copy link
Member

Does this fix sorting w/ lightning + water/whatever? What is preventing this from working for Creepers/can that be easily fixed? And how much will this break mods doing custom stuff (not sure exactly what that may be)?

@marchermans
Copy link
Contributor Author

marchermans commented Jun 28, 2023

Does this fix sorting w/ lightning + water/whatever? What is preventing this from working for Creepers/can that be easily fixed? And how much will this break mods doing custom stuff (not sure exactly what that may be)?

Yes, as far as I can see, it fixes the combination with Lightning + Water.
Mojang uses "additive transparency" for the creeper, but in reality, it is color multiplication without any transparency involved. So it is not needed. But it might be depending on what modders do. So please let me know whether I need to find a solution for this or not.
With regards to breaking modder's stuff.
I am not exactly sure. If you have a really custom rendering setup with custom render targets and custom massive shaders, it will break it. But 99% should just work. It should even take care of the custom setups people have for handling their particles which are transparent. I think netto it is an improvement and it should not effect many people.

+ private final String importPatternPrefix;
+
+ public GlslPreprocessor()
+ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on previous line

+ }
+
+ public GlslPreprocessor(String importPatternPrefix)
+ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on previous line

+ }
+
+ public List<String> process(String p_166462_, com.mojang.blaze3d.shaders.Program.Type type)
+ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on previous line and this method should potentially be added to the .exc file? I believe that only affects parameters so I don't believe (?) it should cause issues with it overriding the method in IForgeGlslPreprocessor. Also might as well include the override annotation

+
+ public GlslPreprocessor(String importPatternPrefix)
+ {
+ this.importPattern = Pattern.compile("(#(?:/\\*(?:[^*]|\\*+[^*/])*\\*+/|\\h)*%s(?:/\\*(?:[^*]|\\*+[^*/])*\\*+/|\\h)*(?:\"(.*)\"|<(.*)>))".formatted(importPatternPrefix));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth a comment above this line along the lines of: Forge: Copy of REGEX_MOJ_IMPORT with support for custom prefixes just so that if mojang changes their regex at some point it is easier to know what part of this has to be updated.


- public static Program m_166604_(Program.Type p_166605_, String p_166606_, InputStream p_166607_, String p_166608_, GlslPreprocessor p_166609_) throws IOException {
+ public static Program m_166604_(Program.Type p_166605_, String p_166606_, InputStream p_166607_, String p_166608_, GlslPreprocessor p_166609_) throws IOException
+ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on previous line

/**
* Defines a render call that can be queued up, based on a particle render type.
*/
private interface IParticleRenderCall extends IRenderCall {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on a new line

* @param modelViewMatrix The model view matrix to use.
* @param projectionMatrix The projection matrix to use.
*/
private record RenderedBufferRenderCall(RenderType renderType, BufferBuilder.RenderedBuffer renderedBuffer, Matrix4f modelViewMatrix, Matrix4f projectionMatrix) implements IRenderTypeBasedRenderCall {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on a new line

* @param cameraZ The camera Z position.
* @param projectionMatrix The projection matrix to use.
*/
private record ChunkGeometryRenderCall(RenderType renderType, PoseStack currentPoseStack, double cameraX, double cameraY, double cameraZ, Matrix4f projectionMatrix) implements IRenderTypeBasedRenderCall {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on a new line

@Override
public void drawDirect()
{
renderType.doRender(renderedBuffer, (shader) -> {}, (shader) -> {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how both of these are NO-OP ShaderInstance consumers and ChunkGeometryRenderCall and ParticleRenderCall also both have NO-OP ShaderInstance consumers we should just create and store a single private static final Consumer<ShaderInstance> NO_OP_CONSUMER = shader -> {}; in the OITLevelRenderer and then reference that in all these places so that we only allocate a single object for all these consumers

* @param camera The camera to use.
* @param partialTickTime The partial tick time to use.
*/
private record ParticleRenderCall(ParticleRenderType particleRenderType, Iterable<Particle> toRender, @Nullable Frustum clippingHelper, Camera camera, float partialTickTime) implements IParticleRenderCall {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracket should be on a new line

@@ -25,7 +25,7 @@ net/minecraft/client/renderer/block/model/ItemTransform.<init>(Lorg/joml/Vector3
net/minecraft/client/renderer/block/model/ItemTransforms.<init>(Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lcom/google/common/collect/ImmutableMap;)V=|p_111798_,p_111799_,p_111800_,p_111801_,p_111802_,p_111803_,p_111804_,p_111805_,moddedTransforms
net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$ChunkCompileTask.<init>(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DZ)V=|p_194422_,pos,p_194423_,p_194424_
net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask.<init>(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DLnet/minecraft/client/renderer/chunk/RenderChunkRegion;Z)V=|p_194426_,pos,p_194427_,p_194428_,p_194429_
net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$ResortTransparencyTask.<init>(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;)V=|p_112888_,pos,p_112889_,p_112890_
net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$ResortTransparencyTask.<init>(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;)V=|p_112888_,renderType,pos,p_112889_,p_112890_
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signature is missing the added RenderType parameter

@XFactHD
Copy link
Contributor

XFactHD commented Jun 28, 2023

I've played around with this and found a few issues with it:

  • Distance fog doesn't fade out the world and instead fades the geometry to white, leading to the "rough edges" of the chunks at the border of the render distance still being visible
    2023-06-28_17 20 59
  • Inner faces of the player model render through the outer faces in 3rd person
    2023-06-28_17 21 22
  • Blending of multiple translucent blocks looks wrong (inverted order maybe?)
    2023-06-28_17 25 31
  • Water appears to sometimes vanish behind translucent blocks
    2023-06-28_17 25 47
  • Resource reloading makes all non-translucent chunk geometry, all block and item models in screens and text go black (flat textures like screen backgrounds and button backgrounds still work)
    2023-06-28_17 33 54
  • Parts of the first-person hand model are invisible after joining a world, moving a few meters usually fixes it
    2023-06-28_17 39 00
  • Fabulous mode breaks all translucent chunk geometry rendering (it shouldn't be possible to have Fabulous mode and OIT enabled at the same time)
    2023-06-28_17 41 24
  • Drawing additional geometry in a RenderLevelStageEvent bypasses all patches that capture render calls in most, if not all stages, which leads to translucent geometry rendered there not being handled appropriately. In my case (particle stage with particle render target) this leads to the item-in-hand vanishing when the custom stuff renders

This was tested on commit 3d35fd1 and all points except the last one were reproduced in a plain Forgedev environment

@jellysquid3
Copy link

As perhaps a useful prior work, we also attempted to implement the WBOIT algorithm in CaffeineMC/sodium-fabric#519, but ran into a lot of issues with the approach. The algorithm doesn't handled layered translucency very well, and fails badly once you start mixing different colors (see some of the linked screenshots in the thread.)

My understanding is that the algorithm (as we implemented it) was correct and that our visual artifacts were within expectations of the original paper. We did a lot of tweaking with the weighting formula but didn't manage to produce results that were any better.

If you do want to try and go ahead with this approach, you may find better results with Moment-Based Order Independent Translucency (MBOIT). The performance isn't nearly as "good"-- though probably still much better than trying to sort the geometry on the CPU-- and it appears to handle these kinds of layered effects much better. It also shares many similarities with the original WBOIT paper, so your work here would prove a very useful platform for implementing the MBOIT paper.

@LexManos LexManos marked this pull request as draft July 14, 2023 17:18
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@autoforge autoforge bot added the Needs Rebase This PR requires a rebase to implement upstream changes (usually to fix merge conflicts). label Jul 19, 2023
@autoforge
Copy link

autoforge bot commented Jul 19, 2023

@marchermans, this pull request has conflicts, please resolve them for this PR to move forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.20 Feature This request implements a new feature. Needs Rebase This PR requires a rebase to implement upstream changes (usually to fix merge conflicts). New Event This request implements a new event. Rendering This request deals with rendering. These must be treated with care, and tend to progress slowly. Triage This request requires the active attention of the Triage Team. Requires labelling or reviews.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants