I am currently working on a voxel game like minecraft, and I’d like to implement a level of detail system that allows me to render chunks distant to the player with less resolution in order to improve performance. While I understand the basic concepts of LOD and octrees, I have no idea how to actually implement them for this use case. I’ve looked through github and dev forums to no avail. My main questions are: how would you determine what block type should be placed in each position of a lower resolution chunk, and how would you know which chunks need to have their resolution updated when the player moves? I’d appreciate any related resources too
#How to implement LOD (Level Of Detail)
1 messages · Page 1 of 1 (latest)
I think you can use things like radius from the characters position to determine the LOD
Like the greater radii has a lower LOD since it's further out but the lower radii have higher LOD because they're closer
I think doing it from the characters hrp position would be enough to justify the LOD radii
Now for implementation, just do it when the player's position updates. However, depending on how many instances are loaded, you may want to prioritize by doing closer instances first then further instances later
you can use stuff like remotes depending on how ur stuff is set up
I'm working on something similar and plan to add LOD so if I did get this wrong someone please do correct me
just use whats best for your datastructure
for example, i used octrees
and i just store color data for every node, not just leaf nodes, so i can just stop going further down the octree
other option would be, if youre using noise, just reduce the sample points
if storing every block (like a 3d texture)
you could calculate the average, or just use one corner
whatever fits best for your use case
I’m not very good with octrees so bear with me, but then let’s say I want to generate a LOD0 chunk… considering each voxel in that chunk represents 8 voxels in a LOD1 chunk, would I first generate the noise data for each of those LOD1 voxel positions and then based on those, get the block type with the highest frequency and that’d be the corresponding LOD0 block type?
** You are now Level 1! **
I don’t think that’d be very good because then you’d still technically need to store or atleast sample through the “lower levels” of the tree even for huge LODs that represent like thousands of blocks with only one
you could do something like that
or just one sample point per voxel as you should look more in to performance in roblox
depends how noisy your noise is 
small details like a one block sized flower could get 4/8 blocks big on a lower LOD
i think most people kinda try to avoid LODs a bit and more focus on other optimizations
like GreedyMeshing / Culling / better chunk loading
those can make a big difference without really visual impact
So then you’d only sample the center point for example? I think that’d be good for performance but it could be very faulty, like pretend you have 8 LOD1 chunks completely filled with blocks, but exactly at the center point between these 8 chunks there’s no block… the LOD0 representation would then be no block
yea thats kinda the issue
on the other hand, LODs are normally so far away that you shoudnt see that much of those small mistakes it does
i didnt do it in roblox 

That’s the first thing I came up with too, but with more chunks, you’d have to constantly check more and more each frame which could get very expensive, so I think an octree system would be a good solution to this. BUT HOW THE HELL DO YOU IMPLEMENT OCTREES AGHHH I HATE OCTREES!! I HATE OCTREES!! I HATE OCTREES!!
also are you utilizing streaming enabled? (I presume so just wondering)
thats also the part i have issues with, my chunk loading is completely broken 
No, I made my own chunk system but it’s extremely laggy so I’m rethinking that too
streaming enabled is good for stuff like that that takes up absurd amounts of ram
afaik
yea but thats more like for pre generated stuff
not voxel terrain 
So about octrees, I imagine you’d first get the largest parent node the player is in representing the lowest resolution (like a LOD4 representing 16^3 16^3 chunks with just 1 voxel per chunk) and then subdivide it, find which of the child nodes the player is in, subdivide that, then repeat until you get to LOD0 (16^3 chunk)
But actually you’d need to subdivide the surrounding LOD1s too? Because if you do it the way I described the LOD1 chunks would be way too close to the player, but then you’d have to subdivide everything else too… I don’t know
** You are now Level 2! **
chat am I cooking
local nids = Enum.NormalId:GetEnumItems()
local Resolutions = {[0] = 16, 8, 4, 2, 1}
local function CreateNode(X,Y,Z,LOD)
local Resolution = Resolutions[LOD]
local NodePart = Instance.new("Part")
NodePart.Position = vector.create(X+Resolution/2, Y+Resolution/2, Z+Resolution/2)
NodePart.Size = vector.one * Resolution
NodePart.Anchored = true
NodePart.CanCollide = false
NodePart.Transparency = 1
NodePart.Parent = workspace
for _, n in nids do
local Outline = Instance.new("Decal")
Outline.Face = n
Outline.Texture = "rbxassetid://"..84335509
Outline.Parent = NodePart
end
return {X=X,Y=Y,Z=Z,LOD=LOD,Part=NodePart}
end
local function SubdivideNode(Node)
local X, Y, Z = Node.X, Node.Y, Node.Z
local ChildLOD = Node.LOD + 1
local Resolution = Resolutions[ChildLOD]
Node.Children = {
[0] = CreateNode(X, Y, Z, ChildLOD), -- -X, -Y, -Z
CreateNode(X + Resolution, Y, Z, ChildLOD), -- +X, -Y, -Z
CreateNode(X, Y + Resolution, Z, ChildLOD), -- -X, +Y, -Z
CreateNode(X + Resolution, Y + Resolution, Z, ChildLOD), -- +X, +Y, -Z
CreateNode(X, Y, Z + Resolution, ChildLOD), -- -X, -Y, +Z
CreateNode(X + Resolution, Y, Z + Resolution, ChildLOD), -- +X, -Y, +Z
CreateNode(X, Y + Resolution, Z + Resolution, ChildLOD), -- -X, +Y, +Z
CreateNode(X + Resolution, Y + Resolution, Z + Resolution, ChildLOD), -- +X, +Y, +Z
}
Node.Part:Destroy()
Node.Part = nil
end
local function ContainsPosition(Node, Position)
local X, Y, Z = Position.X, Position.Y, Position.Z
local Resolution = Resolutions[Node.LOD]
return X>=Node.X and X<=Node.X+Resolution and Y>=Node.Y and Y<= Node.Y+Resolution and Z>=Node.Z and Z<=Node.Z+Resolution
end
local Node = CreateNode(0, 0, 0, 0)
local PlayerPosition = vector.create(3, 6, 9)
if ContainsPosition(Node, PlayerPosition) then
while Node.LOD ~= 4 do
SubdivideNode(Node)
for _, ChildNode in Node.Children do
if ContainsPosition(ChildNode, PlayerPosition) then
Node = ChildNode
end
end
end
end