Composite terrain behind block entities under Fancy translucency

This commit is contained in:
Ruling-0 committed 2026-11-08 12:15:37 -06:00
1 parent 358f495471
commit c340a200a8
7 files changed
+208 -37

No files matched your search

@@ -103,8 +103,12 @@ public class PanoramaRenderer {
}
public static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, Function<ShaderBindingContext, T> factory) {
return loadProgram(name, vertBase, fragBase, ShaderConstants.EMPTY, factory);
}
public static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, ShaderConstants fragConstants, Function<ShaderBindingContext, T> factory) {
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, vertBase + ".vert", ShaderConstants.EMPTY);
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragBase + ".frag", ShaderConstants.EMPTY);
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragBase + ".frag", fragConstants);
try {
final GlProgram<T> program = GlProgram.builder(name).attachShader(vert).attachShader(frag).link(factory);
program.bind();
@@ -13,6 +13,7 @@ import com.gtnewhorizons.angelica.rendering.TextureSamplers;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
import com.gtnewhorizons.angelica.rendering.translucency.TranslucencyLayers;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import com.gtnewhorizons.angelica.rendering.voxelization.SdlShadowVoxelizationSink;
@@ -196,6 +197,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
if (program != null) {
program.bind();
program.getInterface().setupState(pass);
if (pass == AngelicaRenderPassConfiguration.TRANSLUCENT_PASS) TranslucencyLayers.INSTANCE.beginTerrainSplit(program.handle());
}
}
@@ -204,6 +206,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
if (irisProgram != null) {
endIrisPass(pass);
} else {
if (pass == AngelicaRenderPassConfiguration.TRANSLUCENT_PASS) TranslucencyLayers.INSTANCE.endTerrainSplit();
super.end(pass);
}
}
@@ -81,6 +81,7 @@ public class AngelicaRenderPassConfiguration {
.name("translucent")
.useReverseOrder(true)
.useTranslucencySorting(ClientProxy.options().performance.translucencyMode.sorts())
.extraDefine("TRANSLUCENCY_SPLIT", "")
.build();
TRANSLUCENT_MATERIAL = new Material(TRANSLUCENT_PASS, translucentCutoff, true);
@@ -25,6 +25,7 @@ import net.coderbot.iris.rendertarget.IRenderTargetExt;
import net.minecraft.client.Minecraft;
import net.minecraft.client.shader.Framebuffer;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -35,10 +36,18 @@ import java.nio.ByteBuffer;
/// Draws each [TranslucentLayer] into its own target, seeded with the main depth, and composites the drawn layers over
/// the main framebuffer per pixel, farthest first. Layers hold premultiplied color with coverage alpha (GLSM layer
/// blending), and the composite writes the nearest layer depth.
///
/// The terrain layer has a second sheet for fragments behind the block-entity layer, so a tile entity inside a
/// block's own translucent model is covered only by the faces in front of it.
public final class TranslucencyLayers {
private static final int LAYERS = TranslucentLayer.values().length;
private static final int FIRST_LAYER_UNIT = 1;
private static final int TERRAIN_BEHIND_UNIT = FIRST_LAYER_UNIT + 2 * LAYERS;
private static final int SPLIT_DEPTH_UNIT = TERRAIN_BEHIND_UNIT + 1;
private static final int[] SINGLE_BUFFER = { 0 };
private static final int[] SPLIT_BUFFERS = { 0, 1 };
private static final int[] BEHIND_BUFFER = { 1 };
public static final TranslucencyLayers INSTANCE = new TranslucencyLayers(new MinecraftTarget());
@@ -66,8 +75,11 @@ public final class TranslucencyLayers {
private int savedRead;
private boolean alphaMasked;
private GlProgram<Object> program;
private int maskUniform;
private boolean splitting;
private boolean behindDrawn;
private CompositeProgram program;
private CompositeProgram behindProgram;
private int vao;
private StateSet prepareState;
private StateSet compositeState;
@@ -80,12 +92,14 @@ public final class TranslucencyLayers {
public void beginFrame() {
decided = false;
drawnMask = 0;
behindDrawn = false;
}
void beginFrame(boolean active) {
this.active = active;
decided = true;
drawnMask = 0;
behindDrawn = false;
}
public boolean active() {
@@ -143,6 +157,33 @@ public final class TranslucencyLayers {
layers[open.ordinal()].framebuffer.bind();
}
public void beginTerrainSplit(int program) {
splitting = open == TranslucentLayer.TERRAIN && (drawnMask & (1 << TranslucentLayer.BLOCK_ENTITIES.ordinal())) != 0;
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(program, "u_SplitEnabled"), splitting ? 1 : 0);
if (!splitting) return;
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(program, "u_SplitDepth"), SPLIT_DEPTH_UNIT);
final Target terrain = layers[TranslucentLayer.TERRAIN.ordinal()];
if (!behindDrawn) {
behindDrawn = true;
terrain.attachBehind(width, height);
terrain.framebuffer.drawBuffers(BEHIND_BUFFER);
clear(terrain);
}
final int unit = GLSMTextures.getActiveTextureUnit();
bindTexture(SPLIT_DEPTH_UNIT, layers[TranslucentLayer.BLOCK_ENTITIES.ordinal()].depth);
GLSMTextures.glActiveTexture(GL13.GL_TEXTURE0 + unit);
terrain.framebuffer.drawBuffers(SPLIT_BUFFERS);
}
public void endTerrainSplit() {
if (!splitting) return;
splitting = false;
layers[TranslucentLayer.TERRAIN.ordinal()].framebuffer.drawBuffers(SINGLE_BUFFER);
final int unit = GLSMTextures.getActiveTextureUnit();
bindTexture(SPLIT_DEPTH_UNIT, 0);
GLSMTextures.glActiveTexture(GL13.GL_TEXTURE0 + unit);
}
public void composite() {
if (drawnMask == 0) return;
BatchStateGuard.beforeChange();
@@ -172,8 +213,10 @@ public final class TranslucencyLayers {
bindTexture(FIRST_LAYER_UNIT + i, layers[i].color);
bindTexture(FIRST_LAYER_UNIT + LAYERS + i, layers[i].depth);
}
GLSMPrograms.glUseProgram(program.handle());
GLSMPrograms.glUniform1i(maskUniform, drawnMask);
if (behindDrawn) bindTexture(TERRAIN_BEHIND_UNIT, layers[TranslucentLayer.TERRAIN.ordinal()].behind);
final CompositeProgram composite = behindDrawn ? behindProgram : program;
GLSMPrograms.glUseProgram(composite.program.handle());
GLSMPrograms.glUniform1i(composite.maskUniform, drawnMask);
GLSMVertexInput.glBindVertexArray(vao);
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
} finally {
@@ -183,34 +226,34 @@ public final class TranslucencyLayers {
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, read);
GLSMAttribStack.popStateTo(depth);
drawnMask = 0;
behindDrawn = false;
}
}
public void destroy() {
destroyTargets();
if (program != null) {
program.delete();
program.program.delete();
behindProgram.program.delete();
GLSMVertexInput.glDeleteVertexArrays(vao);
program = null;
behindProgram = null;
}
}
private void initProgram() {
program = PanoramaRenderer.loadProgram("angelica:translucency_composite", "angelica:panorama_blur", "angelica:translucency_composite", ctx -> ctx);
final int handle = program.handle();
GLSMPrograms.glUseProgram(handle);
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_Depth"), 0);
for (int i = 0; i < LAYERS; i++) {
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_Layer" + i), FIRST_LAYER_UNIT + i);
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_LayerDepth" + i), FIRST_LAYER_UNIT + LAYERS + i);
}
maskUniform = GLSMPrograms.glGetUniformLocation(handle, "u_LayerMask");
GLSMPrograms.glUseProgram(0);
program = new CompositeProgram("angelica:translucency_composite", ShaderConstants.EMPTY);
behindProgram = new CompositeProgram("angelica:translucency_composite_behind", ShaderConstants.builder().add("TERRAIN_BEHIND").build());
vao = GLSMVertexInput.glGenVertexArrays();
compositeState = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
}
private void prepare(Target target) {
clear(target);
blit(main.framebuffer(), target.framebuffer.getId(), depthFormat.isCombinedStencil() ? GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT : GL11.GL_DEPTH_BUFFER_BIT);
}
private void clear(Target target) {
if (prepareState == null) prepareState = StateSet.forMask(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_SCISSOR_BIT);
final int depth = GLSMAttribStack.pushState(prepareState);
try {
@@ -219,7 +262,6 @@ public final class TranslucencyLayers {
target.framebuffer.bind();
GLSMBlend.glClearColor(0f, 0f, 0f, 0f);
GLSMFramebuffer.glClear(GL11.GL_COLOR_BUFFER_BIT);
blit(main.framebuffer(), target.framebuffer.getId(), depthFormat.isCombinedStencil() ? GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT : GL11.GL_DEPTH_BUFFER_BIT);
} finally {
GLSMAttribStack.popStateTo(depth);
}
@@ -264,12 +306,33 @@ public final class TranslucencyLayers {
if (depthSnapshot != null) depthSnapshot.destroy();
depthSnapshot = null;
drawnMask = 0;
behindDrawn = false;
}
private static final class CompositeProgram {
final GlProgram<Object> program;
final int maskUniform;
CompositeProgram(String name, ShaderConstants constants) {
program = PanoramaRenderer.loadProgram(name, "angelica:panorama_blur", "angelica:translucency_composite", constants, ctx -> ctx);
final int handle = program.handle();
GLSMPrograms.glUseProgram(handle);
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_Depth"), 0);
for (int i = 0; i < LAYERS; i++) {
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_Layer" + i), FIRST_LAYER_UNIT + i);
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_LayerDepth" + i), FIRST_LAYER_UNIT + LAYERS + i);
}
GLSMPrograms.glUniform1i(GLSMPrograms.glGetUniformLocation(handle, "u_TerrainBehind"), TERRAIN_BEHIND_UNIT);
maskUniform = GLSMPrograms.glGetUniformLocation(handle, "u_LayerMask");
GLSMPrograms.glUseProgram(0);
}
}
private static final class Target {
final int color;
final int depth;
final GlFramebuffer framebuffer;
int behind;
Target(int width, int height, DepthBufferFormat depthFormat, boolean withColor) {
depth = texture(width, height, depthFormat.getGlInternalFormat(), depthFormat.getGlType(), depthFormat.getGlFormat());
@@ -278,7 +341,7 @@ public final class TranslucencyLayers {
if (withColor) {
color = texture(width, height, GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
framebuffer.addColorAttachment(0, color);
framebuffer.drawBuffers(new int[] { 0 });
framebuffer.drawBuffers(SINGLE_BUFFER);
framebuffer.readBuffer(0);
} else {
color = 0;
@@ -287,6 +350,12 @@ public final class TranslucencyLayers {
}
}
void attachBehind(int width, int height) {
if (behind != 0) return;
behind = texture(width, height, GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
framebuffer.addColorAttachment(1, behind);
}
private static int texture(int width, int height, int internalFormat, int format, int type) {
final int previous = GLSMQueries.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
final int texture = GLSMTextures.glGenTextures();
@@ -303,6 +372,7 @@ public final class TranslucencyLayers {
void destroy() {
framebuffer.destroy();
if (color != 0) GLSMTextures.glDeleteTextures(color);
if (behind != 0) GLSMTextures.glDeleteTextures(behind);
GLSMTextures.glDeleteTextures(depth);
}
}
@@ -54,6 +54,13 @@ out vec4 fragColor; // The output fragment for the color framebuffer
#define fragColor gl_FragColor
#endif
#if !defined(LEGACY) && defined(TRANSLUCENCY_SPLIT)
// Fragments behind the block-entity layer's depth go to a second sheet (TranslucencyLayers).
uniform bool u_SplitEnabled;
uniform sampler2D u_SplitDepth;
layout(location = 1) out vec4 fragBehind;
#endif
vec2 snapToTexelCentre(vec2 uv, vec2 texelSize, vec2 texelsPerPixel) {
vec2 centreCoords = uv / texelSize - 0.5;
vec2 lowerCentre = floor(centreCoords);
@@ -237,4 +244,12 @@ void main() {
#else // No fog
fragColor = diffuseColor;
#endif
#if !defined(LEGACY) && defined(TRANSLUCENCY_SPLIT)
fragBehind = vec4(0.0);
if (u_SplitEnabled && gl_FragCoord.z > texelFetch(u_SplitDepth, ivec2(gl_FragCoord.xy), 0).r) {
fragBehind = fragColor;
fragColor = vec4(0.0);
}
#endif
}
@@ -1,6 +1,7 @@
#version 330 core
// A sample is skipped only when all zero, so additive light with no coverage still counts.
// A sample is skipped only when all zero, so additive light with no coverage still counts. The TERRAIN_BEHIND sheet
// sorts at the block-entity depth where the front sheet has terrain, inserted first so a tie keeps it behind.
in vec2 v_TexCoord;
@@ -16,19 +17,21 @@ uniform sampler2D u_LayerDepth2;
uniform sampler2D u_LayerDepth3;
uniform sampler2D u_LayerDepth4;
uniform int u_LayerMask;
#ifdef TERRAIN_BEHIND
uniform sampler2D u_TerrainBehind;
#define SHEETS 6
#else
#define SHEETS 5
#endif
out vec4 fragColor;
vec4 colors[5];
float depths[5];
vec4 colors[SHEETS];
float depths[SHEETS];
int count = 0;
float mainDepth;
void insert(int bit, sampler2D colorTexture, sampler2D depthTexture) {
if ((u_LayerMask & bit) == 0) return;
vec4 color = texture(colorTexture, v_TexCoord);
if (color == vec4(0.0)) return;
float depth = texture(depthTexture, v_TexCoord).r;
void insertSheet(vec4 color, float depth) {
if (depth > mainDepth) return;
int j = count++;
colors[j] = color;
@@ -44,9 +47,31 @@ void insert(int bit, sampler2D colorTexture, sampler2D depthTexture) {
}
}
void insert(int bit, sampler2D colorTexture, sampler2D depthTexture) {
if ((u_LayerMask & bit) == 0) return;
vec4 color = texture(colorTexture, v_TexCoord);
if (color == vec4(0.0)) return;
insertSheet(color, texture(depthTexture, v_TexCoord).r);
}
#ifdef TERRAIN_BEHIND
void insertTerrain() {
vec4 behind = texture(u_TerrainBehind, v_TexCoord);
vec4 front = texture(u_Layer0, v_TexCoord);
if (behind != vec4(0.0)) {
insertSheet(behind, front == vec4(0.0) ? texture(u_LayerDepth0, v_TexCoord).r : texture(u_LayerDepth1, v_TexCoord).r);
}
if (front != vec4(0.0)) insertSheet(front, texture(u_LayerDepth0, v_TexCoord).r);
}
#endif
void main() {
mainDepth = texture(u_Depth, v_TexCoord).r;
#ifdef TERRAIN_BEHIND
insertTerrain();
#else
insert(1, u_Layer0, u_LayerDepth0);
#endif
insert(2, u_Layer1, u_LayerDepth1);
insert(4, u_Layer2, u_LayerDepth2);
insert(8, u_Layer3, u_LayerDepth3);
@@ -42,11 +42,25 @@ class TranslucencyCompositeGLTest {
out vec4 color;
void main() { color = u_Color; }
""";
private static final String SPLIT_FS = """
#version 330 core
uniform vec4 u_Color;
uniform bool u_SplitEnabled;
uniform sampler2D u_SplitDepth;
layout(location = 0) out vec4 front;
layout(location = 1) out vec4 behind;
void main() {
bool back = u_SplitEnabled && gl_FragCoord.z > texelFetch(u_SplitDepth, ivec2(gl_FragCoord.xy), 0).r;
front = back ? vec4(0.0) : u_Color;
behind = back ? u_Color : vec4(0.0);
}
""";
private int colorTex;
private int depthTex;
private GlFramebuffer main;
private int quadProgram;
private int splitProgram;
private int vao;
private TranslucencyLayers layers;
@@ -62,6 +76,7 @@ class TranslucencyCompositeGLTest {
main.bind();
GLSMRasterizer.glViewport(0, 0, WIDTH, 1);
quadProgram = GlTestSupport.program(QUAD_VS, QUAD_FS);
splitProgram = GlTestSupport.program(QUAD_VS, SPLIT_FS);
vao = GLSMVertexInput.glGenVertexArrays();
layers = new TranslucencyLayers(new TranslucencyLayers.MainTarget() {
@Override public int framebuffer() { return main.getId(); }
@@ -87,6 +102,7 @@ class TranslucencyCompositeGLTest {
GLSMTextures.glDeleteTextures(depthTex);
GLSMPrograms.glUseProgram(0);
GLSMPrograms.glDeleteProgram(quadProgram);
GLSMPrograms.glDeleteProgram(splitProgram);
GLSMVertexInput.glDeleteVertexArrays(vao);
GLSMBlend.disableBlend();
GLSMDepthStencilAlpha.disableDepthTest();
@@ -105,20 +121,36 @@ class TranslucencyCompositeGLTest {
return tex;
}
private void drawInto(TranslucentLayer layer, int column, float depth, float r, float g, float b, float a) {
drawInto(layer, column, depth, r, g, b, a, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
private void drawInto(TranslucentLayer layer, int column, float depth, float r, float g, float b, float a, int srcRgb, int dstRgb) {
layers.begin(layer);
GLSMBlend.enableBlend();
GLSMBlend.glBlendFunc(srcRgb, dstRgb);
draw(column, depth, r, g, b, a);
GLSMBlend.disableBlend();
drawBlended(quadProgram, column, depth, r, g, b, a, srcRgb, dstRgb);
layers.end();
}
private void drawBlended(int program, int column, float depth, float r, float g, float b, float a, int srcRgb, int dstRgb) {
GLSMBlend.enableBlend();
GLSMBlend.glBlendFunc(srcRgb, dstRgb);
draw(program, column, depth, r, g, b, a);
GLSMBlend.disableBlend();
}
private void drawTerrain(int column, float depth, float r, float g, float b, float a) {
drawBlended(splitProgram, column, depth, r, g, b, a, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
private void draw(int column, float depth, float r, float g, float b, float a) {
draw(quadProgram, column, depth, r, g, b, a);
}
private void draw(int program, int column, float depth, float r, float g, float b, float a) {
GLSMRasterizer.glViewport(column, 0, 1, 1);
GLSMPrograms.glUseProgram(quadProgram);
GLSMPrograms.glUniform1f(GLSMPrograms.glGetUniformLocation(quadProgram, "u_Depth"), depth);
GLSMPrograms.glUniform4f(GLSMPrograms.glGetUniformLocation(quadProgram, "u_Color"), r, g, b, a);
GLSMPrograms.glUseProgram(program);
GLSMPrograms.glUniform1f(GLSMPrograms.glGetUniformLocation(program, "u_Depth"), depth);
GLSMPrograms.glUniform4f(GLSMPrograms.glGetUniformLocation(program, "u_Color"), r, g, b, a);
GLSMVertexInput.glBindVertexArray(vao);
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GLSMVertexInput.glBindVertexArray(0);
@@ -153,11 +185,11 @@ class TranslucencyCompositeGLTest {
@Test
void compositesLayersByDepthAndWritesNearestDepth() {
layers.beginFrame(true);
drawInto(TranslucentLayer.TERRAIN, 0, 0.3f, 0f, 1f, 0f, 0.5f, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
drawInto(TranslucentLayer.PARTICLES, 0, 0.5f, 0f, 0f, 1f, 0.5f, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
drawInto(TranslucentLayer.TERRAIN, 1, 0.95f, 0f, 0f, 1f, 0.5f, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
drawInto(TranslucentLayer.TERRAIN, 0, 0.3f, 0f, 1f, 0f, 0.5f);
drawInto(TranslucentLayer.PARTICLES, 0, 0.5f, 0f, 0f, 1f, 0.5f);
drawInto(TranslucentLayer.TERRAIN, 1, 0.95f, 0f, 0f, 1f, 0.5f);
drawInto(TranslucentLayer.CLOUDS, 2, 0.5f, 0f, 0.4f, 0f, 0f, GL11.GL_ONE, GL11.GL_ONE);
drawInto(TranslucentLayer.TERRAIN, 3, 0.5f, 0f, 1f, 0f, 0.5f, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
drawInto(TranslucentLayer.TERRAIN, 3, 0.5f, 0f, 1f, 0f, 0.5f);
draw(3, 0.2f, 0f, 0f, 1f, 1f);
layers.composite();
@@ -174,4 +206,25 @@ class TranslucencyCompositeGLTest {
final ByteBuffer again = readColor();
for (int i = 0; i < WIDTH * 4; i++) assertEquals(color.get(i), again.get(i), "a composite with no new layer draws changes nothing");
}
@Test
void terrainBehindTheBlockEntityLayerCompositesBehindIt() {
layers.beginFrame(true);
drawInto(TranslucentLayer.BLOCK_ENTITIES, 0, 0.5f, 0f, 0f, 1f, 0.5f);
drawInto(TranslucentLayer.BLOCK_ENTITIES, 1, 0.5f, 0f, 0f, 1f, 0.5f);
drawInto(TranslucentLayer.PARTICLES, 1, 0.55f, 0f, 1f, 0f, 0.5f);
layers.begin(TranslucentLayer.TERRAIN);
GLSMPrograms.glUseProgram(splitProgram);
layers.beginTerrainSplit(splitProgram);
drawTerrain(0, 0.6f, 0f, 1f, 0f, 0.5f);
drawTerrain(0, 0.3f, 1f, 1f, 1f, 0.5f);
drawTerrain(1, 0.6f, 1f, 1f, 1f, 0.5f);
layers.endTerrainSplit();
layers.end();
layers.composite();
final ByteBuffer color = readColor();
assertColumn(color, 0, 159, 159, 191);
assertColumn(color, 1, 64, 96, 159);
}
}