Skip to content

Commit

Permalink
Render most opaque particles before translucent content
Browse files Browse the repository at this point in the history
Relates to IrisShaders#137
  • Loading branch information
coderbot16 committed Mar 5, 2021
1 parent 4d51ad7 commit fcc3977
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.coderbot.iris.fantastic;

public enum ParticleRenderingPhase {
EVERYTHING,
OPAQUE,
TRANSLUCENT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.coderbot.iris.fantastic;

public interface PhasedParticleManager {
void setParticleRenderingPhase(ParticleRenderingPhase phase);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package net.coderbot.iris.mixin.fantastic;

import com.google.common.collect.ImmutableList;
import net.coderbot.iris.fantastic.ParticleRenderingPhase;
import net.coderbot.iris.fantastic.PhasedParticleManager;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.client.particle.ParticleTextureSheet;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import java.util.ArrayList;
import java.util.List;

/**
* Extends the ParticleManager class to allow multiple phases of particle rendering.
*
* This is used to enable the rendering of known-opaque particles much earlier than other particles, most notably before
* translucent content. Normally, particles behind translucent blocks are not visible on Fancy graphics, and a user must
* enable the much more intensive Fabulous graphics option. This is not ideal because Fabulous graphics is fundamentally
* incompatible with most shaderpacks.
*
* So what causes this? Essentially, on Fancy graphics, all particles are rendered after translucent terrain. Aside from
* causing problems with particles being invisible, this also causes particles to write to the translucent depth buffer,
* even when they are not translucent. This notably causes problems with particles on Sildur's Enhanced Default when
* underwater.
*
* So, what these mixins do is try to render known-opaque particles right before entities are rendered and right after
* opaque terrain has been rendered. This seems to be an acceptable injection point, and has worked in my testing. It
* fixes issues with particles when underwater, fixes a vanilla bug, and doesn't have any significant performance hit.
* A win-win!
*
* Unfortunately, there are limitations. Some particles rendering in texture sheets where translucency is supported. So,
* even if an individual particle from that sheet is not translucent, it will still be treated as translucent, and thus
* will not be affected by this patch. Without making more invasive and sweeping changes, there isn't a great way to get
* around this.
*
* As the saying goes, "Work smarter, not harder."
*/
@Mixin(ParticleManager.class)
public class MixinParticleManager implements PhasedParticleManager {
@Unique
private ParticleRenderingPhase phase = ParticleRenderingPhase.EVERYTHING;

@Shadow
@Final
private static List<ParticleTextureSheet> PARTICLE_TEXTURE_SHEETS;

private static final List<ParticleTextureSheet> OPAQUE_PARTICLE_TEXTURE_SHEETS;

static {
OPAQUE_PARTICLE_TEXTURE_SHEETS = ImmutableList.of(
ParticleTextureSheet.PARTICLE_SHEET_OPAQUE,
ParticleTextureSheet.PARTICLE_SHEET_LIT,
ParticleTextureSheet.CUSTOM,
ParticleTextureSheet.NO_RENDER
);
}

@Redirect(method = "renderParticles", at = @At(value = "FIELD", target = "Lnet/minecraft/client/particle/ParticleManager;PARTICLE_TEXTURE_SHEETS:Ljava/util/List;"))
private List<ParticleTextureSheet> iris$selectParticlesToRender() {
if (phase == ParticleRenderingPhase.TRANSLUCENT) {
// Create a copy of the list
//
// We re-copy the list every time in case someone has added new particle texture sheets behind our back.
List<ParticleTextureSheet> toRender = new ArrayList<>(PARTICLE_TEXTURE_SHEETS);

// Remove all known opaque particle texture sheets.
toRender.removeAll(OPAQUE_PARTICLE_TEXTURE_SHEETS);

return toRender;
} else if (phase == ParticleRenderingPhase.OPAQUE) {
// Render only opaque particle sheets
return OPAQUE_PARTICLE_TEXTURE_SHEETS;
} else {
// Don't override particle rendering
return PARTICLE_TEXTURE_SHEETS;
}
}

@Override
public void setParticleRenderingPhase(ParticleRenderingPhase phase) {
this.phase = phase;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package net.coderbot.iris.mixin.fantastic;

import net.coderbot.iris.fantastic.ParticleRenderingPhase;
import net.coderbot.iris.fantastic.PhasedParticleManager;
import net.coderbot.iris.layer.GbufferProgram;
import net.coderbot.iris.layer.GbufferPrograms;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* Uses the PhasedParticleManager changes to render opaque particles much earlier than other particles.
*
* See the comments in {@link MixinParticleManager} for more details.
*/
@Mixin(WorldRenderer.class)
public class MixinWorldRenderer {
@Shadow
@Final
private MinecraftClient client;

@Shadow
@Final
private BufferBuilderStorage bufferBuilders;

@Inject(method = "render", at = @At("HEAD"))
private void iris$resetParticleManagerPhase(MatrixStack matrices, float tickDelta, long limitTime,
boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer,
LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f,
CallbackInfo callback) {
((PhasedParticleManager) client.particleManager).setParticleRenderingPhase(ParticleRenderingPhase.EVERYTHING);
}

@Inject(method = "render", at = @At(value = "CONSTANT", args = "stringValue=entities"))
private void iris$renderOpaqueParticles(MatrixStack matrices, float tickDelta, long limitTime,
boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer,
LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f,
CallbackInfo callback) {
VertexConsumerProvider.Immediate immediate = bufferBuilders.getEntityVertexConsumers();

((PhasedParticleManager) client.particleManager).setParticleRenderingPhase(ParticleRenderingPhase.OPAQUE);

GbufferPrograms.push(GbufferProgram.TEXTURED_LIT);
client.particleManager.renderParticles(matrices, immediate, lightmapTextureManager, camera, tickDelta);
GbufferPrograms.pop(GbufferProgram.TEXTURED_LIT);

((PhasedParticleManager) client.particleManager).setParticleRenderingPhase(ParticleRenderingPhase.TRANSLUCENT);
}
}
3 changes: 2 additions & 1 deletion src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

"mixins": [
"mixins.iris.json",
"mixins.iris.yeetphysicsmod.json"
"mixins.iris.yeetphysicsmod.json",
"mixins.iris.fantastic.json"
],

"depends": {
Expand Down
13 changes: 13 additions & 0 deletions src/main/resources/mixins.iris.fantastic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.coderbot.iris.mixin.fantastic",
"compatibilityLevel": "JAVA_8",
"client": [
"MixinParticleManager",
"MixinWorldRenderer"
],
"injectors": {
"defaultRequire": 1
}
}

0 comments on commit fcc3977

Please sign in to comment.