Skip to content

Commit

Permalink
feat: Enable extreme render distances using LOD chunks (#4452)
Browse files Browse the repository at this point in the history
* Add unscaled LOD chunks
* Make further-away LOD chunks larger
* Add scalable world generation for LOD chunks
* Improve LOD chunk loading logic
* Make the loaded region more symmetrical.
  • Loading branch information
4Denthusiast authored Feb 2, 2021
1 parent 6bf03ee commit 8a1532d
Show file tree
Hide file tree
Showing 44 changed files with 1,011 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void setup() {
borders.put(ElevationFacet.class, new Border3D(0, 0, 0));

region = new RegionImpl(new BlockRegion(0, 0, 0).expand(4, 4, 4),
facetProviderChains, borders);
facetProviderChains, borders, 1);
}

@Test
Expand Down
12 changes: 12 additions & 0 deletions engine/src/main/java/org/terasology/config/RenderingConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class RenderingConfig extends AbstractSubscribable {
public static final String RESOLUTION = "Resolution";
public static final String ANIMATED_MENU = "AnimatedMenu";
public static final String VIEW_DISTANCE = "viewDistance";
public static final String CHUNK_LODS = "chunkLods";
public static final String FLICKERING_LIGHT = "FlickeringLight";
public static final String ANIMATE_GRASS = "AnimateGrass";
public static final String ANIMATE_WATER = "AnimateWater";
Expand Down Expand Up @@ -87,6 +88,7 @@ public class RenderingConfig extends AbstractSubscribable {
private Resolution resolution;
private boolean animatedMenu;
private ViewDistance viewDistance;
private float chunkLods;
private boolean flickeringLight;
private boolean animateGrass;
private boolean animateWater;
Expand Down Expand Up @@ -271,6 +273,16 @@ public void setViewDistance(ViewDistance viewDistance) {
propertyChangeSupport.firePropertyChange(VIEW_DISTANCE, oldDistance, viewDistance);
}

public float getChunkLods() {
return chunkLods;
}

public void setChunkLods(float chunkLods) {
float oldLods = this.chunkLods;
this.chunkLods = chunkLods;
propertyChangeSupport.firePropertyChange(CHUNK_LODS, oldLods, chunkLods);
}

public boolean isFlickeringLight() {
return flickeringLight;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public boolean pregenerateChunks() {
}

@Override
public void setViewDistance(ViewDistance viewDistance) {
public void setViewDistance(ViewDistance viewDistance, int chunkLods) {
// TODO Auto-generated method stub

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void render() {
glPushMatrix();
Vector3f cameraPosition = CoreRegistry.get(LocalPlayer.class).getViewPosition(new Vector3f());
Vector3f center = aabb.center(new Vector3f());
glTranslated(center.x - cameraPosition.x, -cameraPosition.y, center.z - cameraPosition.z);
glTranslated(center.x - cameraPosition.x, center.y - cameraPosition.y, center.z - cameraPosition.z);

renderLocally();

Expand Down Expand Up @@ -126,13 +126,8 @@ public void renderLocally() {
if (displayListWire == -1) {
generateDisplayListWire();
}
Vector3f center = aabb.center(new Vector3f());
glPushMatrix();
glTranslated(0f, center.y, 0f);

glCallList(displayListWire);

glPopMatrix();
}

public void renderSolidLocally() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.terasology.math.TeraMath;
import org.terasology.rendering.nui.layers.mainMenu.videoSettings.CameraSetting;
import org.terasology.world.WorldProvider;
import org.terasology.world.chunks.Chunks;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
Expand Down Expand Up @@ -48,6 +49,9 @@ public PerspectiveCamera(WorldProvider worldProvider, RenderingConfig renderingC
this.cameraSettings = renderingConfig.getCameraSettings();

displayDevice.subscribe(DISPLAY_RESOLUTION_CHANGE, this);
renderingConfig.subscribe(RenderingConfig.VIEW_DISTANCE, this);
renderingConfig.subscribe(RenderingConfig.CHUNK_LODS, this);
updateFarClippingDistance();
}

@Override
Expand Down Expand Up @@ -181,10 +185,23 @@ public void setBobbingVerticalOffsetFactor(float f) {
bobbingVerticalOffsetFactor = f;
}

private void updateFarClippingDistance() {
float distance = renderingConfig.getViewDistance().getChunkDistance().x() * Chunks.SIZE_X * (1 << (int) renderingConfig.getChunkLods());
zFar = Math.max(distance, 500) * 2;
// distance is an estimate of how far away the farthest chunks are, and the minimum bound is to ensure that the sky is visible.
}

public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
if (propertyChangeEvent.getPropertyName().equals(DISPLAY_RESOLUTION_CHANGE)) {
cachedFov = -1; // Invalidate the cache, so that matrices get regenerated.
updateMatrices();
switch (propertyChangeEvent.getPropertyName()) {
case DISPLAY_RESOLUTION_CHANGE:
cachedFov = -1; // Invalidate the cache, so that matrices get regenerated.
updateMatrices();
return;
case RenderingConfig.VIEW_DISTANCE:
case RenderingConfig.CHUNK_LODS:
updateFarClippingDistance();
return;
default:
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public abstract class SubmersibleCamera extends Camera {

/* Used for Underwater Checks */
private WorldProvider worldProvider;
private RenderingConfig renderingConfig;
RenderingConfig renderingConfig;

public SubmersibleCamera(WorldProvider worldProvider, RenderingConfig renderingConfig) {
this.worldProvider = worldProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ public String getString(Integer value) {
fovSlider.bindValue(BindHelper.bindBeanProperty("fieldOfView", config.getRendering(), Float.TYPE));
}

final UISlider chunkLodSlider = find("chunkLods", UISlider.class);
if (chunkLodSlider != null) {
chunkLodSlider.setIncrement(1);
chunkLodSlider.setPrecision(0);
chunkLodSlider.setMinimum(0);
chunkLodSlider.setRange(10);
chunkLodSlider.bindValue(BindHelper.bindBeanProperty("chunkLods", config.getRendering(), Float.TYPE));
}

final UISlider frameLimitSlider = find("frameLimit", UISlider.class);
if (frameLimitSlider != null) {
frameLimitSlider.setIncrement(5.0f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,14 @@ private boolean isSideVisibleForBlockTypes(Block blockToCheck, Block currentBloc
//TODO: This only fixes the "water under block" issue of the top side not being rendered. (see bug #3889)
//Note: originally tried .isLiquid() instead of isWater for both checks, but IntelliJ was warning that
// !blockToCheck.isWater() is always true, may need further investigation
if (currentBlock.isWater() && (side == Side.TOP) && !blockToCheck.isWater()){
if (currentBlock.isWater() && (side == Side.TOP) && !blockToCheck.isWater()) {
return true;
}

if (blockToCheck.getURI().toString().equals("engine:unloaded")) {
return false;
}

return currentBlock.isWaving() != blockToCheck.isWaving() || blockToCheck.getMeshGenerator() == null
|| !blockToCheck.isFullSide(side.reverse()) || (!currentBlock.isTranslucent() && blockToCheck.isTranslucent());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.google.common.base.Stopwatch;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.list.TFloatList;
import org.lwjgl.BufferUtils;
import org.terasology.engine.subsystem.lwjgl.GLBufferPool;
import org.terasology.math.Direction;
Expand All @@ -27,6 +28,7 @@
import org.terasology.world.ChunkView;
import org.terasology.world.block.Block;
import org.terasology.world.chunks.ChunkConstants;
import org.terasology.world.chunks.Chunks;

import java.util.concurrent.TimeUnit;

Expand All @@ -44,15 +46,20 @@ public ChunkTessellator(GLBufferPool bufferPool) {
this.bufferPool = bufferPool;
}

public ChunkMesh generateMesh(ChunkView chunkView, int meshHeight, int verticalOffset) {
public ChunkMesh generateMesh(ChunkView chunkView) {
return generateMesh(chunkView, 1, 0);
}

public ChunkMesh generateMesh(ChunkView chunkView, float scale, int border) {
PerformanceMonitor.startActivity("GenerateMesh");
ChunkMesh mesh = new ChunkMesh(bufferPool);

final Stopwatch watch = Stopwatch.createStarted();

for (int x = 0; x < ChunkConstants.SIZE_X; x++) {
for (int z = 0; z < ChunkConstants.SIZE_Z; z++) {
for (int y = verticalOffset; y < verticalOffset + meshHeight; y++) {
// The mesh extends into the borders in the horizontal directions, but not vertically upwards, in order to cover gaps between LOD chunks of different scales, but also avoid multiple overlapping ocean surfaces.
for (int x = 0; x < Chunks.SIZE_X; x++) {
for (int z = 0; z < Chunks.SIZE_Z; z++) {
for (int y = 0; y < Chunks.SIZE_Y - border * 2; y++) {
Block block = chunkView.getBlock(x, y, z);
if (block != null && block.getMeshGenerator() != null) {
block.getMeshGenerator().generateChunkMesh(chunkView, mesh, x, y, z);
Expand All @@ -65,7 +72,7 @@ public ChunkMesh generateMesh(ChunkView chunkView, int meshHeight, int verticalO
mesh.setTimeToGenerateBlockVertices((int) watch.elapsed(TimeUnit.MILLISECONDS));

watch.reset().start();
generateOptimizedBuffers(chunkView, mesh);
generateOptimizedBuffers(chunkView, mesh, scale, border);
watch.stop();
mesh.setTimeToGenerateOptimizedBuffers((int) watch.elapsed(TimeUnit.MILLISECONDS));
statVertexArrayUpdateCount++;
Expand All @@ -74,7 +81,7 @@ public ChunkMesh generateMesh(ChunkView chunkView, int meshHeight, int verticalO
return mesh;
}

private void generateOptimizedBuffers(ChunkView chunkView, ChunkMesh mesh) {
private void generateOptimizedBuffers(ChunkView chunkView, ChunkMesh mesh, float scale, float border) {
PerformanceMonitor.startActivity("OptimizeBuffers");

for (ChunkMesh.RenderType type : ChunkMesh.RenderType.values()) {
Expand All @@ -97,9 +104,10 @@ private void generateOptimizedBuffers(ChunkView chunkView, ChunkMesh mesh) {
elements.vertices.get(i * 3 + 2));

/* POSITION */
elements.finalVertices.put(Float.floatToIntBits(vertexPos.x));
elements.finalVertices.put(Float.floatToIntBits(vertexPos.y));
elements.finalVertices.put(Float.floatToIntBits(vertexPos.z));
float totalScale = scale * Chunks.SIZE_X / (Chunks.SIZE_X - 2 * border);
elements.finalVertices.put(Float.floatToIntBits((vertexPos.x - border) * totalScale));
elements.finalVertices.put(Float.floatToIntBits((vertexPos.y - 2 * border) * totalScale));
elements.finalVertices.put(Float.floatToIntBits((vertexPos.z - border) * totalScale));

/* UV0 - TEX DATA 0.xy */
elements.finalVertices.put(Float.floatToIntBits(elements.tex.get(i * 2)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public void run() {
*/
c.setDirty(false);
if (chunkView.isValidView()) {
newMesh = tessellator.generateMesh(chunkView, ChunkConstants.SIZE_Y, 0);
newMesh = tessellator.generateMesh(chunkView);

c.setPendingMesh(newMesh);
ChunkMonitor.fireChunkTessellated(c.getPosition(new org.joml.Vector3i()), newMesh);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,15 @@ public class RenderQueuesHelper {
this.chunksAlphaReject = chunksAlphaReject;
this.chunksAlphaBlend = chunksAlphaBlend;
}

/**
* Remove any remaining data from all queues, to avoid a memory leak in the case that the nodes using that data aren't present.
*/
public void clear() {
chunksOpaque.clear();
chunksOpaqueShadow.clear();
chunksOpaqueReflection.clear();
chunksAlphaReject.clear();
chunksAlphaBlend.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public interface RenderableWorld {

boolean updateChunksInProximity(BlockRegion renderableRegion);

boolean updateChunksInProximity(ViewDistance viewDistance);
boolean updateChunksInProximity(ViewDistance viewDistance, int chunkLods);

void generateVBOs();

Expand Down
Loading

0 comments on commit 8a1532d

Please sign in to comment.