Search Unity

After playing minecraft...

Discussion in 'General Discussion' started by jc_lvngstn, Oct 8, 2010.

Thread Status:
Not open for further replies.
  1. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    @db_futbol18

    Yes, lightning is a problem with infinite y. You can never tell if there is a roof above your head as you would have to load infinite chunks above you, so the minecraft algorithm does not work.

    My algorithm is a little different:
    If I am examining a block below surface, then no block gets full sunlight. This means that if I digg straight down from the surface, light dims the same way horizontal digging would. By surface I mean above a hightmap, so caves are treated as diggs. This looks good, and intuitive enough.

    If I am examining an empty block above surface, then either it was part of a pre carved island, or gets full sunlight. If part of a precarved island, it gets indirect light from the nearby sunlight. This looks good as it makes the bottom of the floating island dark. You are right that floating island don't cast shadows on the ground unless they are close enough to ground that their carved out blocks touch ground.

    So with this algorithm, light never travels more than 16 blocks away, and I don't have to worry about infinite blocks above.

    When I add user modifiable blocks, as soon as a user changes a sunlight block, it will lose full sunlight. An option is to let the user place sunlight blocks and air blocks as two different blocks, though I suspect it is more complicated than it is worth.

    If anyone has a better idea, I am all ears. The core restriction is that I chunk can see its neighbors, but not the neighbor of his neighbors, so the absolute maximum I can see is 32 blocks away from the current chunk.
     
  2. SpiderPork

    SpiderPork

    Joined:
    Jan 29, 2011
    Posts:
    27
    Judging by the dynamic flashlight and bump maps, did you mix a vertex color and a pixel color shader? So, vertex colors are overridden by pixel colors?
     
  3. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    @SpiderPork (cool alias)

    I wrote a custom shader that receives several texture atlases: one for the texture, another for the normal map, another for the specularity. The shader receives from each vertex how much ambient light it receives. The shader itself calculates the light from the flashlight as simply coming from the camera in a cone. For both the vertex light and the flash light, it uses blinn-phong at the fragment shader and adds the results. So the magic happens in just 1 pass, and I can have a huge mesh of different blocks rendered by just one unity material. No built in unity shader can come close to what I am doing. I also optimized the shader heavily for rectilinear geometry. I get an average of 200 FPS on my crappy built in intel card.

    This was one of the hardest thing I have done in this game, especially since I had to learn what a shader was.

    I'll make a video showing how gold looks up close.
     
  4. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    In this video I show the shader at work.



    At around second 10, pay close attention to the reflexion on gold and how it follows the player, reacting to the position of the flashlight.
    At around second 18, you can clearly see the depth in the rocks, done by normal mapping. You can see gold as bumping over the rocks through the video, also done by normal mapping.

    Framerate drops when recording to 30-70 fps. If I turn off recording, It goes from 230 to 450 fps as reported by the stats window. This is on an Integrated: Intel® HD Graphics 3000.

    At the beginning, you can see a floating island merged with ground up close, you can see some shadow under the floating island, and you can see grass and dirt on top of the island.

    The only caviat I see with this approach, is that you will not be able to see other people's flashlights.
     
    Last edited: Oct 16, 2011
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    What are your world size, mesh size, and CPU?

    I use 512x512x128 world, 16x16x16 meshes and Core Quad Q6600 2.4GHz, and the bottleneck is the CPU.

    $island.JPG
    $cpu.png

    Even if I reduce GPU frequency by half, fps does not go down.
     
    Last edited: Oct 17, 2011
  6. jorge-castro

    jorge-castro

    Joined:
    Jul 26, 2008
    Posts:
    142
    Exist different kind of optimization that can be used but the more important is the optimization done "not in realtime".



    For example this (it is a representation in 2d).
    Z is a invisible material (or translucent), it is threated as a non-material.
    X is a visible block, in this rendered!.
    Y is a block but it is not visible, then it is not rendered.
    Y (dark gray) it is a block too far to be rendered (if exist a max distance).
    X (blue) is a visible material that can be optimized (i.e. not rendered) via raycasting.

    Y (light gray) and X (brown) is not calculated per scene but only when some block is added or deleted (and this computation is only done locally).

    This scene (without raycasting/distance optimization).
    68 visible blocks (2 translucent blocks) (40%)
    104 invisible blocks (60%)
     
    Last edited: Oct 16, 2011
  7. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    My world is infinite in all 3 dimensions. But I assume you are asking how much of it is loaded at a time.
    I load a minimum of 6x6x6 chunks where each chunk is 32x32x32. so it is a minimum of 192 blocks on each axis. I say minimum because I only drop the chunks if they get farther than 10x10x10
    I have an i5 2.50GHz which is a dual core cpu.
    one of the cores goes to 100% for 3 or 4 seconds when starting the game while calculating the world, and then drops to 4-7% while playing.
     
  8. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Ok, 32x32x32 meshes are much more cpu-friendly. Cpu is still the bottleneck but no longer that narrow.

    $island2.JPG
    $cpu2.png

    Those blue Xes are a pain when there is a large underground caves network, but I guess to render them all would be much cheaper than to calculate which ones are visible and which ones are not.
     
    Last edited: Oct 16, 2011
  9. Clave

    Clave

    Joined:
    Apr 6, 2010
    Posts:
    24
    Usually blocks in caves are adjacent to 0 lighting. since there's no way to see them you could choose to not render them. . i havent thought much of it might have some disadvantage or not worthy at all.

    Anyway, how do you think moving objects like animals in Minecraft are lit? checking their surrounding light values and apply to the mesh every frame? hmmmm :confused:
     
  10. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    When the player is in the cave everything around him is unlit but the cave still has to be rendered.
     
  11. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    That is called occlusion culling. It is very expensive to do at real time. With infinite world you cannot do it ahead of time. It ends up being cheaper to just render them all and let the video card cull them by using the depth buffer.

    There is another optimization that I am doing that made a huge difference. In your example, if you look at 10R, 10S, 10T and 10U, you can draw the surface with just 2 triangles total, instead of 2 triangles for each block which totals 8, by drawing a big rectangle 4 blocks long. This can dramatically reduce the amount of triangles especially for valleys and water. It can make a significant improvement on hills and caves depending on your generation algorithm.

    Yet another optimization, is to find a way to avoid examining rows 1-5, or even 5g-5U. This also saved huge cpu.
     
  12. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I modified my mesh building function the way that it throw away every second face. The meshes became twice as smaller, as if they were "optimized". Then I measured fps and compared the result with the fps that normal meshes produce. There's no difference at all, 200±some fps in both cases.

    Conclusion: it doesn't matter how complex the meshes are, the thing that matters is how many meshes are on the screen.
     
  13. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    @alexzzzz

    Yes. That is true up to a point. The biggest improvement you can have is to reduce is the number of draw calls.

    However, each mesh has a limit of how big it can be. You can have up to 64K vertices per mesh. By doing this optimization, I was able to reduce dramatically the amount of vertices per mesh, allowing me to have meshes of 32x32x32 instead of smaller without constantly hitting that limit, which reduces the amount of meshes I have.

    The real trick, is to have this optimization without causing performance penalty on the CPU. This is not trivial at all. But as I managed to do it, I can tell you it is possible. Generating the meshes for each chunk with these optimizations takes for me an average of 5 ms. It is actually slower if I disable the optimization. Think of processing runs of blocks instead of individual blocks. Code becomes much more complex, but much faster.

    If you have a fixed height of 128, it does not make such big difference, because since the surface is mostly horizontal, having taller chunks does not increase triangle count significantly. Even though 16x16x128 has the same amount of blocks as 32x32x32, you end up with bigger meshes at 32x32x32 at the surface and lots of empty chunks above and below surface.

    Also, in my computer, I found that past 4k triangles per mesh, fps does start to decrease. My guess is that this depends on the video card. Below that, as in your case it makes no difference.

    There is additional savings in memory, as the meshes are simpler.
     
  14. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    32x32x32 in 5ms !
     
  15. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    :) that is just creating the mesh. That does not include generating the chunk, or running the light.
     
  16. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I thought my mesh creating function was fast enough but it's 6..8 times slower than yours. If I manage to make it that fast, I definitely will use 32x32x32 meshes.
     
  17. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I like the id of 32x32x32 meshes, instead of ?*?*128 meshes (or 256). Sure helps with the whole 65k vertices issue.
    I also agree with the conclusion that more meshes = bad, regardless of complexity. I guess once Unity shoves them in the pipe, modern gpus chew them up pretty quick.

    If I remember correctly, mesh gen wasn't a big time concern on my project...it was creating the collision meshes that was painfully slow. I may reload it tonight and check it, to see what the actual numbers were.
    Something I'd be interested in is a good approach to changing the mesh, without having to regen the whole thing. Just removing the collision mesh would free up so much memory, I could spend some on a lookup scheme to directly modify the mesh for a single block. Any thoughts on this?
    We really need a community wiki project, going into detail on various approaches on lighting, terrain, and other systems. Alexzzzz's examples and illustrations are excellent, hate to see them buried alive in this thread :)
     
  18. logty

    logty

    Joined:
    Apr 5, 2011
    Posts:
    23
    I have been having a bit of a problem with the minecraft project created by jc, and I need a bit of help. I cannot figure out how to get block creation and destruction to work better, because in his package it puts chunks that need to be updated in a queue and it takes a second to destroy or create blocks, how can I fix this so that it instantly creates or destroys blocks the frame they are supposed to be destroyed?
    Thanks, Logty
     
  19. logty

    logty

    Joined:
    Apr 5, 2011
    Posts:
    23
    I have been having a bit of a problem with the minecraft project created by jc, and I need a bit of help. I cannot figure out how to get block creation and destruction to work better, because in his package it puts chunks that need to be updated in a queue and it takes a second to destroy or create blocks, how can I fix this so that it instantly creates or destroys blocks the frame they are supposed to be destroyed?
    Thanks, Logty
     
  20. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I haven't considered moving away from the queue approach, but I'm curious if others have found a better way which would also benefit logty and others.

    In my project, chunks that need processing and such are handled by being dropped in queues which are then handled by other threads. How are the rest of you handling this?
    I'm wondering if using smaller chunk sizes, and not creating the collision meshes would speed things up so much that threading wouldn't even be necessary.
     
  21. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    @jc_lvngstn

    How about using a priority queue? where the priority is given by the distance between the player and the chunk. That way, when you destroy a block, the chunk goes straight to the head of the queue.

    Here is an implementation:
    http://www.codeproject.com/KB/recipes/priorityqueue.aspx
     
  22. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Thanks, I need to go back and look at my code. I -believe- when it comes to chunk generation, chunks are sorted by distance so that the closest ones are generated first.
    But I don't recall what I do for block removal. I would have thought I would handle those immediately...

    But what I'm really curious about is:

    1. Are you queuing up chunks when generating terrain and handling those with other threads?
    2. When the player causes a terrain change, are you using the same background queue system to handle relighting chunks around the player, regenerating the meshes, etc, or do you just call those functions immediately from the current thread?
    3. Are you using Unity's collision meshes for the terrains, or just calculating collisions based on blocks?

    I think that letting Unity create collision meshes for my terrain is a big part of the slow down, and also using chunks that are too big (32x32x128). This means that each time someone digs...it has a big lag spike because the chunks and collision mesh regeneration is just too slow. I like others ideas of having smaller chunk sizes and ditching Unity's collision system...less of a hiccup for digging and such.

    Any thoughts on this?
    I was thinking...it's a shame we don't all just work for this one company, that has a project leader who has tasked everyone on this thread with working together on one project: A working framework in Unity that people can build off of. Right now, I see a lot of individual effort, each person one working on the same thing but ultimately separate. I know that for me, the major thing that I wanted was a coding style that was easy for others to follow, and was easy to maintain. I'm not sure what drives everyone else to work on a separate project, but I can't help but think that if everyone banded together and could agree on a fundamental solution and approach, this would have already been -done- and we could move on to add our own unique features. From what I have seen in this thread, many are interested and have some incredible talent.

    A minecraft style project has some pretty consistent features: Infinite terrain, multiplayer, caves, water, basic ai, digging and dropping, inventory, maybe some basic crafting. I feel joining together, we could have gotten those things out of the way already.

    Just some thoughts :)
     
    Last edited: Oct 18, 2011
  23. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    That is my plan. I have not done it yet because it was easier to optimize stuff in 1 thread.

    That is my plan too. I would have just 1 thread doing all the terrain and changes. I don't want to have multiple threads doing terrain as it would seriously complicate lightning, and I don't want to be rendering one chunk while the neighbor chunk is being modified, as that would screw up the border blocks. So I want just 1 thread doing all changes, and sending the meshes arrays via another queue to the unity main thread.


    Are you able to use multiple threads for working on the chunks? How do you handle having 2 threads working on neighbor chunks especially with lightning and mesh generation?

    I started out creating collision meshes for everything visible. The problem is that as you found out, it is very expensive. What I am doing now, is to have a trigger collider to detect when the user moves, and every time he moves more than 1 block, I create box colliders for each one of the blocks that surround the player within a couple of blocks radius. This cut down about 20% of the total time for creating chunks. Creating the collision meshes was for some reason more expensive than creating the visible meshes, even though the collision meshes were structurally simpler.

    I would love to collaborate on having custom colliders, AFAIK it is not possible to do so, but maybe I am not familiar enough with unity. I have concerns that if we have custom colliders, then asset store models may not work correctly. I would like to keep the ability of picking characters from the asset store and have them work immediatelly.
     
  24. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I have two queues, one for background tasks like terrain generation, lighting calculation and mesh building, another for tasks that can be performed only in the main thread like creating game objects.

    Also I have several executors. A main thread executor is just a method that is called every frame. It takes tasks from the main thread queue, executes them and measures the total time it takes. When the time exceeds a certain limit, the executor stops even if there are other tasks in the queue. I use 10ms limit and this helps me to keep the game smooth.

    A background executor works in a separate thread. It wakes up every 10ms to check if there is something in the background queue, and if there are tasks, it performs them until the queue is empty. I use two background executers for my quad-core cpu, and one if there are less cores.

    One more thing I use is a queue sorter that is performed in separate thread. It wakes up every 2 seconds and sorts the background queue according to chunk’s visibility and distance to the player (there's a video I uploaded a while ago).

    All the queues and executors are parts of an update manager, the single class that’s in charge of all chunk updates. All new tasks are added via its EnqueueTask(task, priority) method. “Low” priority tasks are added to the end of the queue, “high” priority tasks are inserted in the beginning of the queue, and “immediate” priority tasks are performed immediately without being put in any queue. When the player moves, new chunks are processed with low priority. If the player digs or places a new block, the changes get high priority status.
     
    Last edited: Mar 1, 2013
  25. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I never noticed any problems with threading having multiple threads work on chunks, maybe because I completed each phase (terrain gen, lighting, etc) for all chunks, before moving onto the next phase.

    What I would do is create the list of chunks to generate ONLY terrain data for. The thread processor I created evenly split the list of chunks among X number of threads, and told each thread to generate terrain data for its pool of chunks.Generating terrain data was just using the noise functions mostly, and when doing that one chunk could care less about the state of its neighbor.
    Lighting was pretty much the same. I never noticed lighting artifacts. Same with mesh generation. After you have already generated the terrain data...mesh gen doesn't care about the other chunk's mesh data...it just needs to read its block data, already generated by the terrain pass.

    About colliders:
    I’m with you on this, I thought about just creating the collision mesh from blocks within a certain radius of the player (or any movable object for that matter!) How to get this work with multiplayer, in an infinite world, is what I’m not sure how to implement.
     
  26. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Alexzzzz, I forget...how are you handling collisions?
     
  27. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I use built-in mesh colliders but with special optimized meshes.

    $collider-mesh.jpg
    Visible mesh: 1120 vertices, 560 triangles.
    Collider mesh: 84 vertices, 42 triangles.
     
  28. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Ah, yes. I remember now, definitely an awesome solution. I should reread back over the previous pages...but how quick is your optimized mesh generation? Fast enough to handle digging and such?
     
  29. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    For 16x16x16 meshes
    visible: 5..6 ms
    collider: 1 ms

    For 32x32x32 meshes
    visible: 30..40 ms
    collider: 15..20 ms (don't know why, anyway I'm in a process of rewriting the mesh building code).
     
  30. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    About terrain, lighting and mesh generating stages:

    1. Each map chunk (Nx128xN) has its state: Empty, HasTerrain, HasLighting, HasMesh and some auxiliary intermediate states. When the state changes, the chunk fires StateChanged event.

    2. Each map chunk is subscribed to its neighbors’ StateChanged event, so it is aware of all the changes that happen around it.

    3. The StateChanged event handler analyzes the chunk’s state and all the neighbors’ states and initiates state transition if all preconditions are met. For example, if a chunk has terrain and all the neighbors have terrain too, then it’s time to calculate lighting. If a chunk has lighting and all the neighbors have lighting, then it’s time to build meshes.

    So, I made it the way that I don’t need to start the tasks manually. All chunks know in what state they are, in what state they should be, and initiate transitions as soon as possible without any additional control from me, and they do it in such an order that guarantees that there will be no artifacts. The only thing I have to do is to create a map chunk, set its current state to Empty, set its target state to HasMesh, and forget about it; the chunk will do its best to evolutionate to that target state by its own.
     
    Last edited: Feb 14, 2012
    toqueteos likes this.
  31. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Very interesting model, with the event driven state changes. Once you get them wired up. I like it, definitely something to chew on.

    I was using excel to create different terrain layouts (I used your screenshot as an example), and I think I'm going to give the optimized collision mesh gen a go. I may also play around with optimizing the terrain mesh also. Exciting stuff!

    I tried different approaches to creating an optimized mesh by hand with excel, and came to the conclusion that no matter how you do your scans to identify rectangles (left to right, right to left, whatever), generally you get the same number of rectangles from the block faces. I don't see any magical, single approach to always getting the least number of rectangles.
     
    Last edited: Oct 18, 2011
  32. logty

    logty

    Joined:
    Apr 5, 2011
    Posts:
    23
    I believe the queue method is great, but not for updating chunks where blocks are being created or destroyed, I think that those chunks should just be instantly updated.
     
  33. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Ha-ha, I was trying to use Excel too, but it turned out it wasn't the best tool to draw grids. Powerpoint is a bit more convenient, but also is the wrong tool.
     
  34. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    What happens if light from one chunks spills to another, that is if you have a torch near the border of a chunk, how do send the light from that chunk to the neighbor?
     
  35. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    Alex, how do you handle ambient occlusion? I tried implementing this this, but I am running into a problem:

    Say I am going to draw the block at the center of this picture, and on the upper left corner above it, there is rock.

    Code (csharp):
    1. ---------
    2. |xx|  |  |
    3. |xx|  |  |
    4. ----------
    5. |  |  |  |
    6. |  |  |  |
    7. ----------
    8. |  |  |  |
    9. |  |  |  |
    10. ----------
    When I am drawing the center surface, I will use 2 triangles, I can split that surface like "/" or like "\", for example:

    Code (csharp):
    1. ---------
    2. |xx|  |  |
    3. |xx|  |  |
    4. ----------
    5. |  | /|  |
    6. |  |/ |  |
    7. ----------
    8. |  |  |  |
    9. |  |  |  |
    10. ----------
    Code (csharp):
    1. ---------
    2. |xx|  |  |
    3. |xx|  |  |
    4. ----------
    5. |  |\ |  |
    6. |  | \|  |
    7. ----------
    8. |  |  |  |
    9. |  |  |  |
    10. ----------
    The problem is that they look completely different. In the first example, the upper left triangle has shadow casted from the corner block. But in the second, the shadow casts over the whole surface.

    Both examples happen simultaneously depending if I am looking east or west.

    take a look at the screenshots to see how nasty that looks. This is looking from east:

    Look at the shadow casted by corner block of sand.

    This is looking from west:

    Look at the shadow casted by the corner block of sand.

    Your screenshots look a hell of a lot better than that.
     
  36. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I think they look the same:

    $ao2.jpg

    $ao1.jpg

    There're two solutions:
    1) rotate faces,
    2) use 4 triangles for corner faces.

    I guess one vertex more, one less, there's no big deal.
     
    Last edited: Oct 19, 2011
  37. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    The lighting function worked on blocks, in "world" or sector coordinates. So if you set a torch in the middle of the map, it would be at 128,128 (the center top block). When it actually went to set the light amount on a block, it called something like SetLight(blockX, blockY, amount).
    The SetLight function calculated which chunk had the correct block, and set it.

    So, the lighting function was somewhat oblivious to chunks.
    Maybe not super efficient...but really, setting artificial light really doesn't need to be lightening fast (haha). The chunk regen far outweighs light calc, from what I recall.

    [edit] Just some clarification...recalculating the chunk block within the chunk is likely not very efficient, but it was easier in my mind to code first, then optimize later once it was working. The whole giant-circular-array scheme would probably be much faster.
    But from what I recall, setting the light values on blocks around an artificial source (a torch) was pretty darn quick! I guess if you had a game where massive amounts of lights were being created, yeah optimizing it would be a higher priority.
     
    Last edited: Oct 19, 2011
  38. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    For those of you interested in optimizing the meshes, here is the same mesh before and after optimization:

    Before:

    For this one mesh, there are 5517 vertices, and 2756 triangles.
    For rendering 72 meshes it takes 0.23 seconds.
    If looking at some complex mountain, FPS drops to 70

    After:

    Now there are 1056 vertices, and 834 triangles.
    For rendering the same 72 meshes it takes 0.22 seconds.
    If looking at same complex mountain, FPS does not go below 450.
    Total number of meshes are exactly the same.

    For drawing this, I need a specialized shader, since now a triangle can span several blocks and I want repeating patterns within that one triangle.
     
  39. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    That's beautiful. I didn't think about the specialized shader requirement...I guess I just assumed the existing vertex shader would handle repeating the patterns. Doh!

    You said 72 meshes...what are the dimensions of your world, in blocks?
     
  40. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    72 meshes are just what got rendered because of the player moving. At the very beginning I load 6 x 6 x 6 chunks = 216. each chunk is 32 x 32 x 32.

    So I keep a minimum of 192 x 192 x 192 blocks in memory at all times. Though I don't drop chunk unless they get far from the player so normally I keep more than that as the player moves.
     
  41. Clave

    Clave

    Joined:
    Apr 6, 2010
    Posts:
    24
    PaulP are you using VertexColor for the shadows? if yes, how are you applying the shadows on your Optimized Mesh?
     
  42. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I was looking at this ambient occlusion shadows...the optimized mesh recognized them as different rectangles.
    That must be one heck of an optimizing algorithm. I'm still working out the non-ambient shadow one.
    Not to mention...it seems like it would also have to take into account the shades of light for the blocks. Meep!
     
  43. GibTreaty

    GibTreaty

    Joined:
    Aug 25, 2010
    Posts:
    792
    Here's how I'm doing my ambient light.

    Code (csharp):
    1.  
    2. public static void ChangeLight(Block block, int faceIndex) {
    3.         if(!block) {
    4.             bool[] neighbors = new bool[26];
    5.  
    6.             for(int i = 0; i < neighbors.Length; i++) {
    7.                 neighbors[i] = !block.FindNeighbor(MathHelper.direction[i]); //If neighbor is NOT an air block //My blocks automatically check if block == Element.Air although I should change it to block != Element.Air
    8.  
    9.             }
    10.  
    11.             for(int i = 0; i < 4; i++) {
    12.                 Color color = Color.white;
    13.  
    14.                 switch(faceIndex) {
    15.                     case (int)Direction.Left: color = GetAmbientLightLeft(i, neighbors); break;
    16.                     case (int)Direction.Right: color = GetAmbientLightRight(i, neighbors); break;
    17.                     case (int)Direction.Top: color = GetAmbientLightTop(i, neighbors); break;
    18.                     case (int)Direction.Bottom: color = GetAmbientLightBottom(i, neighbors); break;
    19.                     case (int)Direction.Front: color = GetAmbientLightFront(i, neighbors); break;
    20.                     case (int)Direction.Back: color = GetAmbientLightBack(i, neighbors); break;
    21.                 }
    22.  
    23.                 block.chunk.lights[block.arrayPosition.x, block.arrayPosition.y, block.arrayPosition.z][faceIndex][i] = color;
    24.             }
    25.         }
    26.     }
    27.  
    28.     static Color GetAmbientLightLeft(int index, bool[] neighbors) {
    29.         switch(index) {
    30.             case 0:
    31.                 if(
    32.                     neighbors[(int)Direction.LeftFront] ||
    33.                     neighbors[(int)Direction.LeftTop] ||
    34.                     neighbors[(int)Direction.LeftTopFront]
    35.                     ) { return ambient; } break;
    36.             case 1:
    37.                 if(
    38.                     neighbors[(int)Direction.LeftBack] ||
    39.                     neighbors[(int)Direction.LeftTop] ||
    40.                     neighbors[(int)Direction.LeftTopBack]
    41.                     ) { return ambient; } break;
    42.             case 2:
    43.                 if(
    44.                     neighbors[(int)Direction.LeftFront] ||
    45.                     neighbors[(int)Direction.LeftBottom] ||
    46.                     neighbors[(int)Direction.LeftBottomFront]
    47.                     ) { return ambient; } break;
    48.             case 3:
    49.                 if(
    50.                     neighbors[(int)Direction.LeftBack] ||
    51.                     neighbors[(int)Direction.LeftBottom] ||
    52.                     neighbors[(int)Direction.LeftBottomBack]
    53.                     ) { return ambient; } break;
    54.         }
    55.  
    56.         return nonAmbient;
    57.     }
    58.     static Color GetAmbientLightRight(int index, bool[] neighbors) {
    59.         switch(index) {
    60.             case 0:
    61.                 if(
    62.                     neighbors[(int)Direction.RightBack] ||
    63.                     neighbors[(int)Direction.RightTop] ||
    64.                     neighbors[(int)Direction.RightTopBack]
    65.                     ) { return ambient; } break;
    66.             case 1:
    67.                 if(
    68.                     neighbors[(int)Direction.RightFront] ||
    69.                     neighbors[(int)Direction.RightTop] ||
    70.                     neighbors[(int)Direction.RightTopFront]
    71.                     ) { return ambient; } break;
    72.             case 2:
    73.                 if(
    74.                     neighbors[(int)Direction.RightBack] ||
    75.                     neighbors[(int)Direction.RightBottom] ||
    76.                     neighbors[(int)Direction.RightBottomBack]
    77.                     ) { return ambient; } break;
    78.  
    79.             case 3:
    80.                 if(
    81.                     neighbors[(int)Direction.RightFront] ||
    82.                     neighbors[(int)Direction.RightBottom] ||
    83.                     neighbors[(int)Direction.RightBottomFront]
    84.                     ) { return ambient; } break;
    85.  
    86.         }
    87.  
    88.         return nonAmbient;
    89.     }
    90.     static Color GetAmbientLightTop(int index, bool[] neighbors) {
    91.         switch(index) {
    92.             case 0:
    93.                 if(
    94.                     neighbors[(int)Direction.LeftTop] ||
    95.                     neighbors[(int)Direction.TopFront] ||
    96.                     neighbors[(int)Direction.LeftTopFront]
    97.                     ) { return ambient; } break;
    98.             case 1:
    99.                 if(
    100.                     neighbors[(int)Direction.RightTop] ||
    101.                     neighbors[(int)Direction.TopFront] ||
    102.                     neighbors[(int)Direction.RightTopFront]
    103.                     ) { return ambient; } break;
    104.             case 2:
    105.                 if(
    106.                     neighbors[(int)Direction.LeftTop] ||
    107.                     neighbors[(int)Direction.TopBack] ||
    108.                     neighbors[(int)Direction.LeftTopBack]
    109.                     ) { return ambient; } break;
    110.             case 3:
    111.                 if(
    112.                     neighbors[(int)Direction.RightTop] ||
    113.                     neighbors[(int)Direction.TopBack] ||
    114.                     neighbors[(int)Direction.RightTopBack]
    115.                     ) { return ambient; } break;
    116.         }
    117.  
    118.         return nonAmbient;
    119.     }
    120.     static Color GetAmbientLightBottom(int index, bool[] neighbors) {
    121.         switch(index) {
    122.             case 0:
    123.                 if(
    124.                     neighbors[(int)Direction.LeftBottom] ||
    125.                     neighbors[(int)Direction.BottomBack] ||
    126.                     neighbors[(int)Direction.LeftBottomBack]
    127.                     ) { return ambient; } break;
    128.             case 1:
    129.                 if(
    130.                     neighbors[(int)Direction.RightBottom] ||
    131.                     neighbors[(int)Direction.BottomBack] ||
    132.                     neighbors[(int)Direction.RightBottomBack]
    133.                     ) { return ambient; } break;
    134.             case 2:
    135.                 if(
    136.                     neighbors[(int)Direction.LeftBottom] ||
    137.                     neighbors[(int)Direction.BottomFront] ||
    138.                     neighbors[(int)Direction.LeftBottomFront]
    139.                     ) { return ambient; } break;
    140.             case 3:
    141.                 if(
    142.                     neighbors[(int)Direction.RightBottom] ||
    143.                     neighbors[(int)Direction.BottomFront] ||
    144.                     neighbors[(int)Direction.RightBottomFront]
    145.                     ) { return ambient; } break;
    146.         }
    147.  
    148.         return nonAmbient;
    149.     }
    150.     static Color GetAmbientLightFront(int index, bool[] neighbors) {
    151.         switch(index) {
    152.             case 0:
    153.                 if(
    154.                     neighbors[(int)Direction.RightFront] ||
    155.                     neighbors[(int)Direction.TopFront] ||
    156.                     neighbors[(int)Direction.RightTopFront]
    157.                     ) { return ambient; } break;
    158.             case 1:
    159.                 if(
    160.                     neighbors[(int)Direction.LeftFront] ||
    161.                     neighbors[(int)Direction.TopFront] ||
    162.                     neighbors[(int)Direction.LeftTopFront]
    163.                     ) { return ambient; } break;
    164.             case 2:
    165.                 if(
    166.                     neighbors[(int)Direction.RightFront] ||
    167.                     neighbors[(int)Direction.BottomFront] ||
    168.                     neighbors[(int)Direction.RightBottomFront]
    169.                     ) { return ambient; } break;
    170.             case 3:
    171.                 if(
    172.                     neighbors[(int)Direction.LeftFront] ||
    173.                     neighbors[(int)Direction.BottomFront] ||
    174.                     neighbors[(int)Direction.LeftBottomFront]
    175.                     ) { return ambient; } break;
    176.         }
    177.  
    178.         return nonAmbient;
    179.     }
    180.     static Color GetAmbientLightBack(int index, bool[] neighbors) {
    181.         switch(index) {
    182.             case 0:
    183.                 if(
    184.                     neighbors[(int)Direction.LeftBack] ||
    185.                     neighbors[(int)Direction.TopBack] ||
    186.                     neighbors[(int)Direction.LeftTopBack]
    187.                     ) { return ambient; } break;
    188.             case 1:
    189.                 if(
    190.                     neighbors[(int)Direction.RightBack] ||
    191.                     neighbors[(int)Direction.TopBack] ||
    192.                     neighbors[(int)Direction.RightTopBack]
    193.                     ) { return ambient; } break;
    194.             case 2:
    195.                 if(
    196.                     neighbors[(int)Direction.LeftBack] ||
    197.                     neighbors[(int)Direction.BottomBack] ||
    198.                     neighbors[(int)Direction.LeftBottomBack]
    199.                     ) { return ambient; } break;
    200.             case 3:
    201.                 if(
    202.                     neighbors[(int)Direction.RightBack] ||
    203.                     neighbors[(int)Direction.BottomBack] ||
    204.                     neighbors[(int)Direction.RightBottomBack]
    205.                     ) { return ambient; } break;
    206.         }
    207.  
    208.         return nonAmbient;
    209.     }
    210.  
    The functions return either an ambient or nonAmbient color...
    Code (csharp):
    1.  
    2. public static Color ambient = Color.black;
    3. public static Color nonAmbient = Color.white;
    4.  
    After I choose what color I want a block to be, I use Color.Lerp to blend the ambient color in with the block's color.

    Code (csharp):
    1.  
    2. for(int f = 0; f < Block.faces; f++) {
    3. //a = block
    4. //decide blockColor here...
    5.  
    6. for(int i = 0; i < Block.vertices; i++) {
    7.                         Color color = lights[a.arrayPosition.x, a.arrayPosition.y, a.arrayPosition.z][f][i];
    8.  
    9.                         lights[a.arrayPosition.x, a.arrayPosition.y, a.arrayPosition.z][f][i] = Color.Lerp(blockColor, color, .4f);
    10.                     }
    11. }
    12.  
    And the light array is inside each chunk as...
    Code (csharp):
    1.  
    2. public ColorI[,,][][] lights = new ColorI[0, 0, 0][][];
    3.  
    which is accessed like...
    Code (csharp):
    1.  
    2. for(int f = 0; f < Block.faces; f++) {//Block.faces = 6, the amount of sides on a block
    3.             for(int v = 0; v < Block.vertices; v++) {//Block.vertices = 4, the amount of vertices per face
    4.                 lights[x, y, z][f][v] = new Color(r,g,b);
    5.             }
    6.         }
    7.  
    ColorI is my own color class that uses integers (actually bytes but I'm going to change that) instead of floats.
     
    Last edited: Oct 19, 2011
  44. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    You are correct. I can not merge two blocks if their light is different. That is why ambient occlusion shadows break apart as separate rectangles. Also, as you enter caves, blocks with different lights can not be merged, so cave entrances are kind of a bad case scenario, but still a lot of merging happens because whole rows of blocks keep the same lights.

    The optimizing algorithm is my 4th generation, after which my brain hurt. The thing to recognize is that you are not looking for the optimal way to tessellate your mesh. You are looking for a good enough way that can be performed in O(x*y*z) , meaning you examine each block only once per face. If you pay careful attention to my example, It is possible to tessellate even better: for example, on the second grass level, the 3 big rectangles could be done in 2. But doing so would potentially mean reexamining the generated rectangles which would slow things down, at not much gain.

    The savings are especially good on water as typically in the ocean a whole chunk can be done with just 2 triangles.
     
    Last edited: Oct 19, 2011
  45. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    at each vertex, I calculate how much light it should have by averaging the light of the 4 blocks that are on top, including the one that are solid. I am doing the exact same thing as this guy.

    For the optimized mesh, the trick is not to merge blocks if their light is different. It does not look quite as pretty as I wanted it to be as you can see in my screenshots a few posts back, but it looks good enough for me.
     
  46. logty

    logty

    Joined:
    Apr 5, 2011
    Posts:
    23
    This mesh optimization is a great idea, but how would you handle multiple different blocks? Would they have to break up the meshes even more?, ie like having a dirt block in the middle of all of that grass.
     
  47. logty

    logty

    Joined:
    Apr 5, 2011
    Posts:
    23
    I know this question seems really stupid, but I cannot get block creation/destruction to work and I was wondering if anyone could tell me why. When I destroy the block that I have created it gives me the item in my inventory but it does not destroy that block until I update the chunk by destroying a block that I did not create. Any ideas?
     
  48. Duskling

    Duskling

    Joined:
    Mar 15, 2011
    Posts:
    1,196
    Anyone here have any tips of how I would start a voxel engine or at least start learning more about how to create a simple one? Thanks.
     
  49. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    You don't have to break the mesh. In my example, you see grass and sand and they are both part of the same mesh and material. If you were to add a dirt block in the middle of all the grass, it would simply mean a few additional triangles to the same mesh.

    What I do is pass a texture atlas to the shader with the texture for all the different blocks. The vertices for each triangle have the uv coordinates in this atlas for the texture that apply to that triangle.
     
  50. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I wish I had time to go back and document the whole process from the beginning, from start to finish...or that this effort could become a community focused project, with all approaches discussed, documented, etc. Personally, I've abandoned the "blocky world" approach, it's just not the end result I want. And even if I had not, my project has some flaws that caused problems like what you are running into, so while it does ok, I don't know that I would hold it up as a good learning tool.
     
Thread Status:
Not open for further replies.