Jump to content

Josh

Staff
  • Posts

    23,339
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    I've begun a Lua script for a Treeview widget. This is arguably one of the most complex widgets to implement, but after having done this in another language I have a design that I think is pretty simple. The script sorts all tree view nodes into a linear list-like table so that they can be drawn quickly. Like other widgets, the script will calculate where the starting point in the list is to display just the visible nodes. This is very important because it prevents the program from slowing down when the number of nodes gets really high and goes beyond the bounds of the scrollable area.
     

    Script.itemheight = 18 Script.itemindent = 20 function Script:Start() self.color = {} self.color.background = Vec4(0.2,0.2,0.2,1) self.color.foreground = Vec4(0.7,0.7,0.7,1) self.color.border = Vec4(0,0,0,1) end function Script:Draw() local gui = self.widget:GetGUI() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) local scale = self.widget:GetGUI():GetScale() local style = self.widget:GetStyle() --Update table of visible nodes if self.visiblenodes==nil then self:UpdateNodes(self.widget) end --Draw background gui:SetColor(self.color.background.r,self.color.background.g,self.color.background.b,self.color.background.a) gui:DrawRect(pos.x,pos.y,sz.width,sz.height) --Draw nodes local n for n=1,#self.visiblenodes do gui:SetColor(self.color.foreground.r,self.color.foreground.g,self.color.foreground.b,self.color.foreground.a) gui:DrawText(self.visiblenodes[n].widget:GetText(),pos.x + 2*scale + self.visiblenodes[n].indent,pos.y + 2*scale + self.itemheight*(n-1),100,20) end --Draw border gui:SetColor(self.color.border.r,self.color.border.g,self.color.border.b,self.color.border.a) gui:DrawRect(pos.x,pos.y,sz.width,sz.height,1) end --Place nodes into a linear list function Script:UpdateNodes(node,indent) if indent==nil then indent=0 end if self.visiblenodes==nil then self.visiblenodes = {} end local n local count = node:CountChildren() local subnode local gui = self.widget:GetGUI() if node:Collapsed()==false then for n=0,count-1 do subnode = node:GetChild(n) self.visiblenodes[#self.visiblenodes+1] = {} self.visiblenodes[#self.visiblenodes].widget = subnode self.visiblenodes[#self.visiblenodes].indent = indent self:UpdateNodes(subnode,indent+self.itemindent) end end end
     
    The linear list also allows you to instantly calculate the node under a mouse coordinate, so you can figure out which node was clicked without iterating through hundreds of nodes. It's very similar to the design of the listview widget, but with the added complexity of a hierarchy that can be expanded and collapsed.
     

  2. Josh
    Most of the prizes are now shipped, and I am just cleaning up a few pieces of missing information for shipping. In order to ship posters, I need a full name, which I have requested from onaid, MDGunn, Evayr, and Graham. I also am missing a shirt size for Garlic Waffle and MDGunn. I have not received any shipping info from MartyJ, or else I missed it.
     
    I found some mailing tubes from ULine to send posters in, at about $0.75 each. If you are receiving a sticker it will be included in the tube. Other prizes are sent in a separate envelope or package. USPS offered the best shipping rate, with the most expensive ones being about $13 compared to $100 each for UPS shipping.
     

     
    Whew! Shipping has been a pretty big ordeal this time with 20 entrants to handle, plus posters for each entrant. In order to make the next tournament easier, I have added a new information section to your Leadwerks account. You can now add your full name, shipping address, and shirt size by editing your profile. This information will remain private on your account, visible only by yourself and me. Next time around we are going to use this instead of trying to message back and forth with each participant, and I think it will make life simpler for everyone.
     
    The posters were a big hit and I think they were the main reason there were so many participants this time. The idea of working on a one-time event and getting some memorabilia for your effort is a really cool concept and gives the tournaments a new sense of liveliness. I think we will keep doing that.
     

     
    The
    of the resulting games was fun to make, and I think people liked watching it, but it only got 800 views. Without some additional method of promoting this content I don't think it's worth producing. I think a way to spread that kind of content needs to be found before more of it can be made, so that it can be used to promote your games. 
    The timing of the summer tournament was kind of bad because it started too late in the summer. In the future we should kick this off along with the Steam summer sale. There was only a 30 day window between the end of the summer tournament and the beginning of Halloween. In between that, the shipping issues and Steam Dev Days we were not able to hold a Halloween tournament, although you can participate in the community Script Challenge. I love seeing
    and the winter games tournament is tentatively named "Dead of Winter". I'm going to find a new poster artist and get the artwork started soon. 
    Leadwerks Game Launcher now has 90 games on Steam Workshop! The next tournament will definitely push us over the edge to 100 games, a goal that seemed very far away when we released the game launcher. Having 100 games on Steam will be a great achievement for the community, and a fantastic way to start 2017!
  3. Josh
    The beta branch now contains an update that adds C++11 support for GCC on Linux. To use this you must enable C++11 support in the compiler settings in Code::Blocks. Select the Settings > Compiler and Debugger... menu item and then check the box indicated below.
     

     
    All new projects created from the Leadwerks templates will work correctly out-of-the-box.
     
    Your existing projects need a couple of new libraries added to them. The easiest way to do this is to open the CBP project file in a text editor. Find two chunks of text that look like this:

    <Linker> <Add library="$(LeadwerksPath)/Library/Linux/Debug/Leadwerks.a" /> <Add library="dl" /> <Add library="openal" /> <Add library="GL" /> <Add library="GLU" /> <Add library="$(LeadwerksPath)/Library/Linux/libluajit.a" /> <Add library="../../libsteam_api.so" /> <Add library="X11" /> <Add library="Xext" /> <Add library="pthread" /> </Linker>
     
    Add these two libraries to the list. Remember, this will occur twice in the file.

    <Add library="Xrender" /> <Add library="Xft" />
     
    Save the project file and you're ready to use C++11 features with Leadwerks on Linux.
  4. Josh
    The Workshop Store interface has been updated. These changes will go out to the in-editor store interface soon.
     

     
    Clicking on the "Buy" button now opens the item directly in the Steam client, so you no longer have to log into the Steam website. No credit card is needed if you already have one on file in your Steam account.
     

  5. Josh
    Keeping your social media accounts active with screenshots, videos, and updates is important, but as an indie developer you probably don't have time to post on there every day. In this blog I will show you how to easily automate your social media accounts so that a constant stream of new content is going out to your fans.
     
    First, you will need to create a free account with dlvr.it. Connect your social media accounts to it. Facebook, Twitter, and Google+ are the important ones.
     
    Now you need some RSS feeds to act as inputs. There is an RSS feed for your game's announcements here:
    http://steamcommunity.com/games/YOUR_APP_ID/rss/
     
    Set this RSS feed as an input in your dlvr.it account and it will make posts automatically to your social media account.
     
    We can use the Steam web API to create an RSS feed of your game's screenshots, videos, and Workshop items. The code below will do exactly that. Just input your game's app ID and a web API key you generate in the Steamworks interface:

    /*----------------------------------------------------*/ /* This code is free to use. Do not redistribute it. */
    /* Leadwerks Software 2016 */
    /*----------------------------------------------------*/
     
    $appID = 'xxxxxxx';
    $webAPIKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
    $recordCount=20;
    $fileType = '-1';
    $queryType = 1;
     
    function LoadXML($path)
    {
    $content = utf8_encode(file_get_contents($path));
    $content = str_replace(chr(11),'',$content);
    if (trim($content=='')) return NULL;
    return simplexml_load_string($content);
    }
     
    $steamcontent = @LoadXML('http://api.steampowered.com/IPublishedFileService/QueryFiles/v0001?key='.$webAPIKey.'&format=xml&query_type='.$queryType.'&page=1&numperpage='.$recordCount.'&appid='.$appID.'&filetype='.$fileType.'&return_vote_data=1&return_short_description=1');
     
    function clean($string)
    {
    return preg_replace('/[^A-Za-z0-9\- ().,!]/', '', $string); // Removes special chars.
    }
     
    function truncate($string, $length)
    {
    if (strlen($string)>$length)
    {
    return substr($string,0,$length-3)."...";
    }
    else
    {
    return substr($string,0,$length);
    }
    }
     
    echo('<?xml version="1.0" encoding="UTF-8" ?>'."\n");
    echo('<rss version="2.0">'."\n");
    echo('<channel>'."\n");
    echo("<title>Leadwerks Community Activity</title>"."\n");
    echo('<link>http://www.leadwerks.com</link>'."\n");
    echo("<description>Activity feed from Leadwerks Community Hub on Steam.</description>"."\n");
     
    for ($i=0; $i<min($recordCount,$steamcontent->total); $i=$i+1)
    {
    /* Only show screenshots above a certain score */
    if (($steamcontent->publishedfiledetails->message[$i]->vote_data->score>0.4 || $steamcontent->publishedfiledetails->message[$i]->file_type!=5) && $steamcontent->publishedfiledetails->message[$i]->result==1)
    {
    $file_type = $steamcontent->publishedfiledetails->message[$i]->file_type;
    if ($file_type!=0 && $file_type!=5 && $file_type!=4) continue;
    echo("<item>"."\n");
     
    $title = clean($steamcontent->publishedfiledetails->message[$i]->title);
    if ($title=="") $title = truncate(clean($steamcontent->publishedfiledetails->message[$i]->short_description),35);
    if ($title=="Screenshot") $title = "";
     
    /* Get author name */
    $userid = $steamcontent->publishedfiledetails->message[$i]->creator;
    $userdata = simplexml_load_file('http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?format=xml&key='.$webAPIKey.'&steamids='.$userid);
    $username = $userdata->players->player[0]->personaname;
     
    if ($steamcontent->publishedfiledetails->message[$i]->file_type==0)
    {
    echo("\t<title>Workshop item by ".$username.": ".$title."</title>\n");
    }
    else if ($steamcontent->publishedfiledetails->message[$i]->file_type==5)
    {
    echo("\t<title>Screenshot by ".$username.": ".$title."</title>\n");
    }
    else if ($steamcontent->publishedfiledetails->message[$i]->file_type==4)
    {
    echo("\t<title>Video by ".$username.": ".$title."</title>\n");
    }
    else
    {
    continue;
    }
     
    if ($steamcontent->publishedfiledetails->message[$i]->file_type==4)
    {
    echo("\t<link>http://www.youtube.com/watch?v=".$steamcontent->publishedfiledetails->message[$i]->youtubevideoid."</link>\n");
    }
    else
    {
    echo("\t<link>http://steamcommunity.com/sharedfiles/filedetails/?id=".$steamcontent->publishedfiledetails->message[$i]->publishedfileid."</link>\n");
    }
     
    echo("\t<description>\n\t\t<![CDATA[");
     
    $description = clean($steamcontent->publishedfiledetails->message[$i]->short_description);
    $description = $description.' #gamedev #indiedev';
     
    if ($steamcontent->publishedfiledetails->message[$i]->file_type==4)
    {
    if (!empty($description)) echo('<p />'.$description);
    echo("<br />http://www.youtube.com/watch?v=".$steamcontent->publishedfiledetails->message[$i]->youtubevideoid);
    }
    else
    {
    echo("<p /><a href='http://steamcommunity.com/sharedfiles/filedetails/?id=".$steamcontent->publishedfiledetails->message[$i]->publishedfileid."'><img width='600px;' src='".$steamcontent->publishedfiledetails->message[$i]->preview_url."' /></a>");
    if (!empty($description)) echo('<br />'.$description);
    }
     
    echo("]]>\n\t</description>\n");
     
    if ($steamcontent->publishedfiledetails->message[$i]->file_type!=4)
    {
    echo("<enclosure url='".$steamcontent->publishedfiledetails->message[$i]->preview_url."' length='".$steamcontent->publishedfiledetails->message[$i]->file_size."' type='image/jpeg' />");
    }
     
    echo("<guid>".$steamcontent->publishedfiledetails->message[$i]->publishedfileid."</guid>");
    $displaytime = $steamcontent->publishedfiledetails->message[$i]->time_created;
    $displaytime = date('D, d M Y, h:m:s', intval($displaytime));
     
    echo("\t<pubDate>".$displaytime."</pubDate>\n");
     
    echo("\t<category>gamedev</category>\n");
    echo("\t<category>indiedev</category>\n");
    echo("\t<category>Leadwerks</category>\n");
     
    echo("</item>\n");
    }
    }
     
    echo("</channel>\n");
    echo("</rss>\n");
     
    There are a lot of settings in dlvr.it you can experiment with, but this is enough to get you running. Once this is set up you can keep your social media accounts active without having to log in and post items manually several times a day.
  6. Josh
    A small update is out, on all branches, that fixes the vehicle wheels not being positioned correctly:
    http://www.leadwerks.com/werkspace/topic/15405-problembug-with-vehicles-cars-wheels-dont-move-since-update-4-2
  7. Josh
    I've updated the editor and Lua executables on Windows with the following fixes:
    http://www.leadwerks.com/werkspace/topic/15399-rc4-editor-hints-get-built-in-probe-reflections/
    http://www.leadwerks.com/werkspace/topic/15646-prefabs-child-entity-scripts-start-function-not-called/
    http://www.leadwerks.com/werkspace/topic/15644-latest-43-beta-causes-crawler-to-rotate-at-wrong-direction/
    http://www.leadwerks.com/werkspace/topic/15593-releasecleanup-vehicle-error/
     
    The game launcher beta branch for Windows has also been updated. If all goes well, 4.3 will be released next week.
  8. Josh
    Since the GLTF file format can pack textures into a single file with the model, I needed to implement asset loading directly from a stream:
    auto stream = ReadFile("image.png"); auto tex = LoadTexture(stream); This was interesting because I needed to add a check for each supported image type so the loader can determine the file type from the contents instead of the file path extension. Most file formats include a string or "magic number" at the beginning of the file format to indicate what type of file it is:
    //BMP check pos = stream->GetPos(); if (stream->GetSize() - pos >= 2) { if (stream->ReadString(2) == "BM") isbmp = true; } stream->Seek(pos); The TGA file format is weird though because it does not have one of these. It just launches straight into a header of information, already assuming the file is a TGA file. So what you have to do is read some of the values and see if they are reasonable. With a little help from the BlitzMax source code, I was able to do this:
    //TGA check pos = stream->GetPos(); tgahdr hdr; if (stream->GetSize() - pos >= sizeof(hdr)) { const int TGA_NULL = 0; const int TGA_MAP = 1; const int TGA_RGB = 2; const int TGA_MONO = 3; const int TGA_RLEMAP = 9; const int TGA_RLERGB = 10; const int TGA_RLEMONO = 11; const int TGA_COMPMAP = 32; const int TGA_COMPMAP4 = 33; stream->Read(&hdr, sizeof(hdr)); if (hdr.colourmaptype == 0) { if (hdr.imgtype == TGA_MAP or hdr.imgtype == TGA_RGB or hdr.imgtype == TGA_RLERGB) { if (hdr.psize == 15 or hdr.psize == 16 or hdr.psize == 24 or hdr.psize == 32) { if (hdr.width > 0 and hdr.width <= 163284 * 2) { if (hdr.height > 0 and hdr.height <= 163284 * 2) istga = true; } } } } } stream->Seek(pos); In fact the whole idea of having a list of loaders that read the file contents to determine if they are able to load the file is an idea I pulled from the design of BlitzMax. It is strange that so many good tech products have fallen away yet we are growing.
  9. Josh
    I'm now able to load materials from GLTF files. These can use external textures or they can use textures packed into a GLTF binary file. Because we have a standardized material specification, this means you can download GLTF files from SketchFab or Turbosquid, and your model materials will automatically be loaded, all the time. There's no more generating materials or messing around trying to figure out which texture is the normal or specular map. An extension exists for DDS texture support, fortunately.
    Here are the preliminary results.
     
  10. Josh
    A big bug fix update just came out for 4.5. Unless there are any major problems then I am going to go back to Europe for a while. I will be focusing on multiplayer features, specifically the P2P, lobby, and voice chat features in Steamworks, as well as converting lots of models to make them ready-to-use with Leadwerks Game Engine.
    I am just going to bring my little Gigabyte Brix mini PC. If I need to I can have my big monster machines shipped to me overseas, but I'll start with this one and see how it goes.
  11. Josh
    The beta branch has been updated with a new build of version 4.5 beta.  VR support is implemented and documented here:
    https://www.leadwerks.com/learn?page=API-Reference_Object_VR
    Linux C++ projects will currently not build in the beta (working on it now).
    See this thread on updating your C++ projects:
    https://www.leadwerks.com/community/topic/16838-upgrading-44-c-projects-to-45/
     
  12. Josh
    Leadwerks Engine 4.5 will add ZIP files to the list of file formats you can import into the editor.  This isn't really anything special except that it works in a very specific way.  Source art files will be extracted first, followed by final game-ready formats (mdl and tex), followed by .meta files, which contain thumbnails and conversion settings for each file.  The resulting behavior is that you can import a zip file of game assets, with source art files, and the files will be copied in a way so that no reconversion of the files is triggered, because the time stamps are sequenced in the correct order.
    It's the little things. 
  13. Josh
    I'm building the VR project template for Leadwerks 4.5.  Although you can enable VR in any project, this template is specifically designed to provide some of your most common room-scale VR features:
    Teleportation movement, which prevents motion sickness. Picking up and throwing objects. (It's actually really fun!) To start with I am creating the art assets for the teleport effect. This is basically what I want:

    Your controller shoots a beam which ends in an indicator when it hits an upwards-facing slope. Typically this beam will be somewhat arced.  Why the curve? This allows you to climb up to areas above you:

    As always, I am starting with the game assets. I don't believe in using programmer art because it hurts your understanding of what you are trying to create, it's uninspiring, and you will end up writing your code twice once you get the final artwork and realize all the mistakes you made.
    I started with textures. I know I want a circular indicator on the floor, a misty spinning effect rising off it, and a beam. I'm going to make all my textures grayscale so that I can control the color with the entity color value and dynamically change it in the game.  Here are my textures I created in about ten minutes in Paint Shop Pro:



    The first texture above is clamped along the X and Y axes and the second one is clamp along the Y axis.  I am using uncompressed textures for all of these because they have a lot of soft gradients.
    I created my materials with the following settings, again leaving everything white:

    In 3ds Max I created my indicator model. It's just a plane with a cylinder on top, with the end caps removed:

    When I import it into Leadwerks and apply my materials, the model looks like this:

    I'll show you why I am using uncompressed textures. You can see in this shot the edge of the ring has some ugly artifacts when texture compression is used:

    Here's a closeup. Not something I want to see in VR:

    Now I am going to create an instance of the model in the editor and adjust the color. I want a bright blue glowy color. I am setting the color to RGB 128,255,255 and cranking the intensity way up to 2.0. This effectively sets the entity color to 256,512,512. This color is multiplied by the texture color at each pixel and then clamped to 0-255 (the maximum color range of the monitor). That means that the brightest spots on the material will reach a full 255,255,255 white color and look really intense, while darker parts will be tinted blue:

    Notice the object isn't just a flat color, but has a range of color from blue to white. To get this effect I had to increase the intensity over 1.0 to create colors brighter than RGB 255,255,255, and I had to have some red in the color. If I had set the color to RGB 0,255,255 the red channel would never increase and I would have a flat color like this. Not so good:

    If I had set the color to RGB 128,255,255 but left the intensity at 1.0 I would also have a solid color:

    Finally I added a script to the model and saved it as a prefab. The script just rotates the model around slowly on its Y axis, which I think will look pretty good. I'm going to perform the rotation in the Draw() function so it doesn't get called if the object is hidden or offscreen, and I don't think anyone will notice if the rotation doesn't update when they look away:
    function Script:Draw() self.entity:Turn(0, 0.1 * Time:GetSpeed(), 0) end That's it for now. The next step will be to create my teleportation mechanic in VR.
  14. Josh

    Articles
    Autodesk 3ds Max now supports export of glTF models, as well as a new glTF material type. The process of setting up and exporting glTF models is pretty straightforward, but there are a couple of little details I wanted to point out to help prevent you from getting stuck. For this article, I will be working with the moss rocks 1 model pack from Polyhaven.
    Getting geometry into 3ds Max is simple enough. I imported the model as an FBX file.

    To set up the material, I opened the compact material editor and set the first slot to be a glTF material.

    Press the button for the base color map, and very importantly choose the General > Bitmap map type. Do not choose OSL > Bitmap Lookup or your textures won't export at all.

    Select your base color texture, then do the same thing with the normal and roughness maps, if you have them. 3ds Max treats metal / roughness as two separate textures, although you might be able to use the same texture both if it grabs the data from the green (roughness) and blue (metal) channels. This is something I don't know yet.

    Select the File > Export menu item to bring up the glTF export dialog. Uncheck the "Export glTF binary" option because we don't want to pack our model and textures into a single file: I don't know what the baked / original material option does because I don't see any difference when I use it.

    At this point you should have a glTF file that is visible in any glTF model viewer.

    Now something slightly weird max does is it generates some new textures for some of the maps. This is probably because it is combining different channels to produce final images. In this case, none of our textures need to be combined, so it is just a small annoyance. A .log file will be saved as well, but these can be safely deleted.

    You can leave the images as-is, or you can open up the glTF file in a text editor and manually change the image file names back to the original files:
    "images": [ { "uri": "rock_moss_set_01_nor_gl_4k.jpg" }, { "uri": "M_01___Defaultbasecolortexture.jpeg" }, { "uri": "M_01___Defaultmetallicroughnesstex.jpeg" } ], Finally, we're going to add LODs using the Mesh Editor > ProOptimizer modifier. I like these settings, but the most important thing is to make sure "Keep textures" is checked. You can press the F3 key at any time to toggle wireframe view and get a better view of what the optimizer does to your mesh.

    Export the file with the same name as the full-resolution model, and add "_lod1" to the end of the file name (before the extension). Then repeat this process saving lod2 and lod3 using 25% and 12.5% for the vertex reduction value in the ProOptimizer modifier.
    Here is my example model you can inspect:
    mossrock1a.zip
    Now it is very easy to get 3D models from 3ds max to your game engine.
  15. Josh
    A new build is available on the beta branch on Steam. This fixes a couple of issues.
    Oculus view projection is fixed. Added VR.AButton, VR.BButton, VR.GripAxis for compatibility with Oculus Touch controllers:
    https://www.leadwerks.com/community/topic/17052-vive-and-oculus-controls/ Fixed terrain collision bug:
    https://www.leadwerks.com/community/topic/16985-character-controller-falls-through-terrain/
  16. Josh
    In designing the new engine, I have found that there are three distinct types of optimization.
    Streamlining
    This is refinement. You make small changes and try to gain a small amount of performance. Typically, this is done as a last step before releasing code. The process can be ongoing, but suffers from diminishing returns after a while. When you eliminate unnecessary math based on guaranteed assumptions you are streamlining code. For example, a 4x4 matrix multiplication can skip the calculations to fill the right-most column if the matrices are guaranteed to be orthogonal (non-sheared).
    Quality Degradation
    This is when you downgrade the quality of your results within a certain tolerable level where it won't be noticed much. An example of this is using a low-resolution copy of a model when it is far away from the camera. Quality degradation can be pretty arbitrary, and can mask your true performance, so it's best to keep an option to disable this.
    Architectural
    By designing algorithms in a way that makes maximum use of hardware and produces the most optimum results, we can greatly increase performance. Architectural optimization produces groundbreaking changes that can be ten or 100 times faster than the old architecture. An example of this is GPU hardware, which produces a massive performance increase over software rendering. We're seeing a lot of these types of improvements in Leadwerks Game Engine 5 because the entire system is being designed to make maximum use of modern graphics hardware.
     
  17. Josh
    At this point I have successfully created a sparse octree class and can insert voxelized meshes into it. An octree is a way of subdividing space into eight blocks at each level of the tree:

    A sparse octree doesn't create the subnodes until they are used. For voxel data, this can save a lot of memory.
    It was difficult to get the rounding and all the math completely perfect (and it has to be completely perfect!) but now I have a nice voxel tree that can follow the camera around and is aligned correctly to the world axis and units. The code that inserts a voxel is pretty interesting: A voxel tree is created with a number of levels, and the size of the structure is equal to pow(2,levels). For example, an octree with 8 levels creates a 3D grid of 256x256x256 voxels. Individual voxels are then inserted to the top-level tree node, which recursively calls the SetSolid() function until the last level is reached. A voxel is marked as "solid" simply by having a voxel node at the last level (0). (GetChild() has the effect of finding the specified child and creating it if it doesn't exist yet.)
    A bitwise flag is used to test which subnode should be called at this level. I didn't really work out the math, I just intuitively went with this solution and it worked as I expected:
    void VoxelTree::SetSolid(const int x, const int y, const int z, const bool solid) { int flag = pow(2, level); if (x < 0 or y < 0 or z < 0) return; if (x >= flag * 2 or y >= flag * 2 or z >= flag * 2) return; flag = pow(2, level - 1); int cx = 0; int cy = 0; int cz = 0; if ((flag & x) != 0) cx = 1; if ((flag & y) != 0) cy = 1; if ((flag & z) != 0) cz = 1; if (solid) { if (level > 0) { GetChild(cx, cy, cz)->SetSolid(x & ~flag, y & ~flag, z & ~flag, true); } } else { if (level > 0) { if (kids[cx][cy][cz] != nullptr) { kids[cx][cy][cz]->SetSolid(x & ~flag, y & ~flag, z & ~flag, false); } } else { //Remove self auto parent = this->parent.lock(); Assert(parent->kids[position.x][position.y][position.y] == Self()); parent->kids[position.x][position.y][position.y] = nullptr; } } } The voxel tree is built by adding all scene entities into the tree. From there it was easy to implement a simple raycast to see if anything was above each voxel, and color it black if another voxel is hit:

    And here is the same program using a higher resolution voxel tree. You can see it's not much of a stretch to implement ambient occlusion from here:

    At a voxel size of 0.01 meters (the first picture) the voxelization step took 19 milliseconds, so it looks like we're doing good on speed. I suspect the rest of this project will be more art than science. Stay tuned!
  18. Josh
    I was having trouble with cone tracing and decided to first try a basic GI algorithm based on a pattern of raycasts. Here is the result:

    You can see this is pretty noisy, even with 25 raycasts per voxel. Cone tracing uses an average sample, which eliminates the noise problem, but it does introduce more inaccuracy into the lighting.
    Next I wanted to try a more complex scene and get an estimate of performance. You may recognize the voxelized scene below as the "Sponza" scene frequently used in radiosity testing:

    Direct lighting takes 368 milliseconds to calculate, with voxel size of 0.25 meters. If I cut the voxel grid down to a 64x64x64 grid then lighting takes just 75 milliseconds.
    These speeds are good enough for soft GI that gradually adjusts as lighting changes, but I am not sure if this will be sufficient for our purposes. I'd like to do real-time screen-independent reflections.
    I thought about it, and I thought about it some more, and then when I was done with that I kept thinking about it. Here's the design I came up with:

    The final output is a 3D texture containing light data for all six possible directions.  (So a 256x256x256 grid of voxels would actually be 1536x256x256 RGB, equal to 288 megabytes.) The lit voxel array would also be six times as big. When a pixel is rendered, three texture lookups are performed on the 3D texture and multiplied by the normal of the pixel. If the voxel is empty, there is no GI information for that volume, so maybe a higher mipmap level could be used (if mipmaps are generated in the last step). The important thing is we only store the full-resolution voxel grid once.
    The downsampled voxel grid use an alpha channel for coverage. For example, a pixel with 0.75 alpha would have six out of eight solid child voxels.
    I do think voxelization is best performed on the CPU due to flexibility and the ability to cache static objects.
    Direct lighting, in this case, would be calculated from shadowmaps. So I have to implement the clustered forward renderer before going forward with this.
  19. Josh
    I was able to partially implement clustered forward rendering. At this time, I have not divided the camera frustum up into cells and I am just handing a single point light to the fragment shader, but instead of a naive implementation that would just upload the values in a shader uniform, I am going through the route of sending light IDs in a buffer. I first tried texture buffers because they have a large maximum size and I already have a GPUMemBlock class that makes them easy to work with. Because the GPU likes things to be aligned to 16 bytes, I am treating the buffer as an array of ivec4s, which makes the code a little trickier, thus we have a loop within a loop with some conditional breaks:
    vec4 CalculateLighting(in vec3 position, in vec3 normal) { vec4 lighting = vec4(0.0f); int n,i,lightindex,countlights[3]; vec4 lightcolor; ivec4 lightindices; mat4 lightmatrix; vec2 lightrange; vec3 lightdir; float l,falloff; //Get light list offset int lightlistpos = 0;//texelFetch(texture12, sampleCoord, 0).x; //Point Lights countlights[0] = texelFetch(texture11, lightlistpos).x; for (n = 0; n <= countlights[0] / 4; ++n) { lightindices = texelFetch(texture11, lightlistpos + n); for (i = 0; i < 4; ++i) { if (n == 0 && i == 0) continue; //skip first slot since that contains the light count if (n * 4 + i > countlights[0]) break; //break if we go out of bounds of the light list lightindex = lightindices[1]; lightmatrix[3] = texelFetch(texture15, lightindex * 4 + 3); vec3 lightdir = position - lightmatrix[3].xyz; float l = length(lightdir); falloff = max(0.0f,-dot(normal,lightdir/l)); if (falloff <= 0.0f) continue; lightrange = texelFetch(texture15, lightindex * 4 + 4).xy; falloff *= max(0.0f, 1.0f - l / lightrange.y); if (falloff <= 0.0f) continue; lightmatrix[0] = texelFetch(texture15, lightindex * 4); lightmatrix[1] = texelFetch(texture15, lightindex * 4 + 1); lightmatrix[2] = texelFetch(texture15, lightindex * 4 + 2); lightcolor = vec4(lightmatrix[0].w,lightmatrix[1].w,lightmatrix[2].w,1.0f); lighting += lightcolor * falloff; } } return lighting; } I am testing with Intel graphics in order to get a better idea of where the bottlenecks are. My GEForce 1080 just chews through this without blinking an eye, so the slower hardware is actually helpful in tuning performance. I was dismayed at first when I saw my framerate drop from 700 to 200+. I created a simple scene in Leadwerks 4 with one point light and no shadows, and the performance was quite a bit worse on this hardware, so it looks like I am actually doing well. Here are the numbers:
    Turbo (uniform buffer): 220 FPS Turbo (texture buffer): 290 FPS Leadwerks 4: 90 FPS Of course a discrete card will run much better. The depth pre-pass has a very slight beneficial effect in this scene, and as more lights and geometry are added, I expect the performance savings will become much greater.
    Post-processing effects like bloom require a texture with the scene rendered to it, so this system will still need to render to a single color texture when these effects are in use. The low quality settings, however, will render straight to the back buffer and thus provide a much better fallback for low-end hardware.

    Here we can see the same shader working with lots of lights. To get good performance out of this, the camera frustum needs to be divided up into cells with a list of relevant lights for each cell.

    There are two more benefits to this approach. Context multisample antialiasing can be used when rendering straight to the back buffer. Of course, we can do the same with deferred rendering and multisample textures now, so that is not that big of a deal.

    What IS a big deal is the fact that transparency with shadows will work 100%, no problems. All the weird tricks and hacks we have tried to use to achieve this all go away. (The below image is one such hack that uses dithering combined with MSAA to provide 50% transparency...sort of.)

    Everything else aside, our first tests reveal more than a 3X increase in performance over the lighting approach that Leadwerks 4 uses. Things look fantastic!
  20. Josh
    After a couple days of work I got point light shadows working in the new clustered forward renderer. This time around I wanted to see if I could get a more natural look for shadow edges, as well as reduve or eliminate shadow acne. Shadow acne is an effect that occurs when the resolution of the shadow map is too low, and incorrect depth comparisons start being made with the lit pixels: By default, any shadow mapping alogirthm will look like this, because not every pixel onscreen has an exact match in the shadow map when the depth comparison is made:

    We can add an offset to the shadow depth value to eliminate this artifact:
    \
    However, this can push the shadow back too far, and it's hard to come up with values that cover all cases. This is especially problematic with point lights that are placed very close to a wall. This is why the editor allows you to adjust the light range of each light, on an individual basis.
    I came across a techniqe called variance shadow mapping. I've seen this paper years ago, but never took the time to implement it because it just wasn't a big priority. This works by writing the depth and depth squared values into a GL_RG texture (I use 32-bit floating points). The resulting image is then blurred and the variance of the values can be calculated from the average squared depth stored in the green channel.


    Then we use Chebyshev's inequality to get an average shadow value:

    So it turns out, statistics is actually good for something useful after all. Here are the results:

    The shadow edges are actually soft, without any graininess or pixelation. There is a black border on the edge of the cubemap faces, but I think this is caused by my calculated cubemap face not matching the one the hardware uses to perform the texture lookup, so I think it can be fixed.
    As an added bonus, this eliminates the need for a shadow offset. Shadow acne is completely gone, even in the scene below with a light that is extremely close to the floor.

    The banding you are seeing is added in the JPEG compression and it not visible in the original render.
    Finally, because the texture filtering is so smooth, shadowmaps look much higher resolution than with PCF filtering. By increasing the light range, I can light the entire scene, and it looks great just using a 1024x1024 cube shadow map.

    VSMs are also quite fast because they only require a single texture lookup in the final pass. So we get better image quality, and probably slightly faster speed. Taking extra time to pay attention to small details like this is going to make your games look great soon!
  21. Josh
    You can now view detailed sales records of your game assets in Leadwerks Marketplace. First, log into your Leadwerks account and navigate to the Leadwerks Marketplace main page. In the bottom-right, below the categories, a link to your paid files will appear.

    Here you can see a list of all your paid items:

    When you click on an item, you can see a list of people who have purchased it, along with sales dates.

    If you wish to give a free license to any member for any reason, you can do so by clicking the "Generate Purchase" button. A window will pop up where you can type in the member's name and add the item to their account for free.

    These tools give you more control over your game assets and better information on sales.
  22. Josh
    Here are the results of the Summer Games Tournament. Make sure you update your mailing address, because posters are being sent out immediately!
    Invade
    The arcade classic "Space Invaders" has been re-imagined with modern graphics and cute 3D aliens!
    Constanta
    Constant is an abstract game about capturing cubes. Make sure you read the instructions!
    Death Rooms
    Procedurally generated levels and a lot of interesting rooms make this FPS worth trying. Watch out for traps!
     
  23. Josh
    Lighting is nearly complete. and it is ridiculously fast! There are still some visual glitches to work out, mostly with lights intersecting the camera near plane, but it's nearly perfect. I turned the voxel tree back on to see what the speed was, and to check if it was still working, and I saw this image of the level partially voxelized. The direct lighting shader I am using in the rest of the scene will be used to calculate lighting for each voxel on the GPU, and then bounces will be performed to quickly calculate approximate global illumination. This is fun stuff!

×
×
  • Create New...