-
Notifications
You must be signed in to change notification settings - Fork 115
Implementation Details
The see-through portals and seamless teleportation are actually very hard to implement. This mod is a technical feat. Here I will introduce the implementation details of this mod:
There are two ways of rendering portal within the architecture of rasterization:
- Use stencil buffer to limit rendering area and render portals from outer to inner
- Render portal content to another frame buffer first and then draw to the main frame buffer. Render inner portal first.
This mod mainly adopts the first method. (It also has the "compatibility" rendering mode which uses the second method but does not support portal-in-portal rendering)
The portal rendering pseudocode:
renderWorld() {
prepare
render solid things
renderPortals()
render transparent things
}
renderPortals() {
if exceeds recursion limit, exit
for each portal nearby {
if portal is in frustum {
render the portal shape with occlusion query
with stencil func: equal to the portal layer
with stencil operation: increment by 1
predict the portal visibility (avoid waiting for occlusion query result)
if the portal is deemed visible {
outerStencil = current portal layer
increment portal layer by 1
with stencil func: equal to the portal layer, do:
clear depth
renderWorld() with new position, new dimension, new camera transformation
(will do recursion)
set depth value by portal shape
reset stencil value to outerStencil
decrement portal layer by 1
}
}
}
}
The outmost part of the framebuffer has stencil value 0. Rendering the portal area will increase stencil value by 1. So the stencil value corresponds to the portal layer.
That's only the main skeleton of portal rendering. Other details includes:
- Frustum culling optimization
- Visibility prediction optimization
- Enable
GL_DEPTH_CLAMP
for portal shape rendering. Or if the camera is close to the portal it will do wrong clipping. - Front clipping by transforming the shader code.
- Because the portal rendering is recursive, all rendering related context should be switched upon rendering, stored on the stack, and then recover after the portal rendering finishes.
- The face culling should be inversed when rendering odd number layers of mirrors (for example, 1, 3, 5 layers of mirrors).
When rendering portals, it will do advanced frustum culling to improve performance.
For example, when rendering this scene
The sections behind the portal will be culled. (outer frustum culling)
The sections out of portal view will be culled (inner frustum culling)
Without advanced frustum culling:
When rendering portal content, everything in front of the portal plane will be clipped. The gold block in this image is in nether behind the portal.
If not correctly clipped, the portal will be rendered wrongly:
This mod do clipping by transforming the vertex shader and set gl_ClipDistance
.
There is another culling method using oblique projection. http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
This mod does not use oblique projection for clipping because oblique projection cannot do correct clipping in all cases. If the angle between the culling plane normal with view vector is not bigger than 90 degrees, it will not work at all.
And oblique projection make it harder to be compatible with Iris.
After applying one mirror transformation, all counter-clockwise triangles will become clockwise. So the face culling should be inverted. Applying two mirror transformations cancels that. The face culling will be inverted when rendering an odd number of layers of mirrors.
As mentioned before, there are 2 ways to do portal rendering in rasterization. This mod uses the first method by default and also provides compatibility render mode that use the second method.
The first method is to limit the rendering area using stencil buffer. The second method firstly render the portal content into a framebuffer and then draw that framebuffer into the main framebuffer. The reason that this mod does not adopt the second method by default is that the second method does not allow accurate portal visibility detection by occlusion query.
This mod allows deep-nested portal. For example, this mirror room scene:
This mirror room scene involves many portal-in-portal rendering. With the stencil buffer approach we can use occlusion query to accurately determine whether one portal is visible. But with the second method, it's hard to determine whether a portal-in-portal is visible so it will do a lot more unnecessary rendering.
The 2 methods above are all under the architecture of rasterization. With ray tracing, portal rendering is much simpler. If the ray hits a portal, just emit a new transformed ray. No clipping is required. And it will be much more efficent in rendering the mirror room scene.
If an entity is intersecting with a portal, to render this entity correctly, it will render the entity twice. The entity will be rendered both outside of the portal and inside portal with plane culling. Minecraft uses deferred entity rendering which firstly collects all triangles and then render all of them. This cannot handle the entity rendering that has a special culling plane. So it will first render all collected triangles and then use a separate draw call to render the culled entity.
For example, this sheep is partly in portal and partly outside of portal
Vanilla Minecraft only allows loading one dimension in client at once. This mod eliminates that limitation and changed many code that's built upon one-client-world assumption. To maintain minimal intrusive and maximum generality, this mod switches the client world context when invoking code associated with remote dimensions.
Vanilla Minecraft only allows chunks nearby the player to be loaded. This mod's portal allows loading very far chunks, so this limitation must be eliminated. Vanilla use a fixed size array to store chunk references and chunk rendering info references. This mod changed them into maps.
To do world information synchonization, the server must track the chunk visibility and entity visibility for every player. This mod rewrites chunk tracking algorithm. ImmPtl's chunk tracking algorithm updates periodcally and searches for directly and indrectly visible portals. To mitigate the lag spikes, this mod's chunk tracking algorithm make it gradually loads chunks from near to far. Entity tracking mechanics also gets changed.
The server needs to send packets to client to synchronize world informations (blocks, entiteis, etc.). All of these packets does not contain dimension information, so the client cannot discriminate these packets between dimensions.
To solve that, ImmPtl changed the networking protocol in a minimal intrusive way. It wraps world information packets into a "redirected packet" that contain dimension information, and the client will handle these packets with context switched.
To make the teleportation seamless, this mod do client side teleportation before every frame's rendering (not during ticking). Teleportation happens when the camera crosses the portal (not after the player entity crossing the portal).
Teleportation is iterative. Normally the player at most teleports one time in a frame. But when the player crosses the world wrapping corner the teleportation may happen twice.
In most cases the server accept client position information. This mod make that position information contain dimension information. And the server must validate the client position to avoid cheating. In some cases, the client accepts position information from the server, for example when a teleportation command executes. The position synchronization is mutual, so it become a little bit complex.
Although nether portals seems be aligned with blocks, this mod does not limit the portals to be aligned with blocks. Portals are entities, not blocks.
This mod's portal can be tilted, can be in any shape, can be arbitrarily big (global portals), can be anywhere, can point to any dimension, can point to anywhere, and can have any rotation transformation, scale transformation.
One portal entity corresponds to a one-way one-faced portal. A normal bi-way, bi-faced nether portal contains 4 portal entities.
This mod also provide global portal mechanic that allows very big portals. The global portals are stored in per-dimension world data. Dimension stack portals and world wrapping portals are all global portals. Global portals are always loaded, so they can be very big.
ImmPtl has cross-portal collision. In this scene, the creeper is in overworld (because its eye position is in overworld) but it's standing on a glass block in nether.
When an entity is halfway in the portal then its collision will be specially treated.
It will cut the entity's collision into two pieces, one outside portal and one inside portal. It firstly does collision test for the outer part and then switch the entity to the portal destination position and do collision test for the inner part.
ImmPtl allows you to hear the sound through portal. To calculate the sound volume, it will find the sound "trajectory". It firstly finds the nearest point on the portal from the sound source, and then teleport through the portal on that point and then go into the player.
This mod supports cross-portal block breaking and placement. It can even place blocks across a portal.
For example, in this dimension stack scene, placing a block upon the top nether will actually place the block to the bottom of overworld.
If the player cross a portal with rotation transformation, its camera rotation may become tilted which is not a valid camera transformation. To make the teleportation seamless, this mod will do a smooth camera rotation transition. It involves some quaternion math. If gravity changer mod is present, it will take gravity rotations into consideration.