Jump to content


  • Content Count

  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    Textures in Leadwerks don't actually store any pixel data in system memory. Instead the data is sent straight from the hard drive to the GPU and dumped from memory, because there is no reason to have all that data sitting around in RAM. However, I needed to implement texture saving for our terrain system so I implemented a simple "Pixmap" class for handling image data:
    class Pixmap : public SharedObject { VkFormat m_format; iVec2 m_size; shared_ptr<Buffer> m_pixels; int bpp; public: Pixmap(); const VkFormat& format; const iVec2& size; const shared_ptr<Buffer>& pixels; virtual shared_ptr<Pixmap> Copy(); virtual shared_ptr<Pixmap> Convert(const VkFormat format); virtual bool Save(const std::string& filename, const SaveFlags flags = SAVE_DEFAULT); virtual bool Save(shared_ptr<Stream>, const std::string& mimetype = "image/vnd-ms.dds", const SaveFlags flags = SAVE_DEFAULT); friend shared_ptr<Pixmap> CreatePixmap(const int, const int, const VkFormat, shared_ptr<Buffer> data); friend shared_ptr<Pixmap> LoadPixmap(const std::wstring&, const LoadFlags); }; shared_ptr<Pixmap> CreatePixmap(const int width, const int height, const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, shared_ptr<Buffer> data = nullptr); shared_ptr<Pixmap> LoadPixmap(const std::wstring& path, const LoadFlags flags = LOAD_DEFAULT); You can convert a pixmap from one format to another in order to compress raw RGBA pixels into BCn compressed data. The supported conversion formats are very limited and are only being implemented as they are needed. Pixmaps can be saved as DDS files, and the same rules apply. Support for the most common formats is being added.
    As a result, the terrain system can now save out all processed images as DDS files. The modern DDS format supports a lot of pixel formats, so even heightmaps can be saved. All of these files can be easily viewed in Visual Studio itself. It's by far the most reliable DDS viewer, as even the built-in Windows preview function is missing support for DX10 formats. Unfortunately there's really no modern DDS viewer application like the old Windows Texture Viewer.

    Storing terrain data in an easy-to-open standard texture format will make development easier for you. I intend to eliminate all "black box" file formats so all your game data is always easily viewable in a variety of tools, right up until the final publish step.
  2. Josh
    I wanted to see if any of the terrain data can be compressed down, mostly to reduce GPU memory usage. I implemented some fast texture compression algorithms for BC1, BC3, BC4, BC5, and BC7 compression. BC6 and BC7 are not terribly useful in this situation because they involve a complex lookup table, so data from different textures can't be mixed and matched. I found two areas where texture compression could be used, in alpha layers and normal maps. I implemented BC3 compression for terrain alpha and could not see any artifacts. The compression is very fast, always less than one second even with the biggest textures I would care to use (4096 x 4096).
    For normals, BC1 (DXT1 and BC3 (DXT5) produce artifacts: (I accidentally left tessellation turned on high in these shots, which is why the framerate is low):

    BC5 gives a better appearance on this bumpy area and closely matches the original uncompressed normals. BC5 takes 1 byte per pixel, one quarter the size of uncomompressed RGBA. However, it only supports two channels, so we need one texture for normals and another for tangents, leaving us with a total 50% reduced size.

    Here are the results:
    2048 x 2048 Uncompressed Terrain:
    Heightmap = 2048 * 2048 * 2 = 8388608 Normal / tangents map = 16777216 Secret sauce = 67108864 Secret sauce 2 = 16777216 Total = 104 MB 2048 x 2048 Compressed Terrain:
    Heightmap = 2048 * 2048 * 2 = 8388608 Normal map = 4194304 Tangents = 4194304 Secret sauce = 16777216 Secret sauce 2 = 16777216 Total = 48 MB Additionally, for editable terrain an extra 32 MB of data needs to be stored, but this can be dumped once the terrain is made static. There are other things you can do to reduce the file size but it would not change the memory usage, and processing time is very high for "super-compression" techniques. I investigated this thoroughly and found the best compression methods for this situation that are pretty much instantaneous with no noticeable loss of quality, so I am satisfied.
  3. Josh
    A new update is available for beta testers. This adds a new LOD system to the terrain system, fixes the terrain normals, and adds some new features. The terrain example has been updated ans shows how to apply multiple material layers and save the data.

    Terrain in LE4 uses a system of tiles. The tiles are rendered at a different resolution based on distance. This works great for medium sized terrains, but problems arise when we have very large view distances. This is why it is okay to use a 4096x4096 terrain in LE4, but your camera range should only show a portion of the terrain at once. A terrain that size will use 1024 separate tiles, and having them all onscreen at once can cause slowdown just because of the number of objects. That have to be culled and drawn.

    Another approach is to progressively divide the terrain up into quadrants starting from the top and working down to the lowest level. When a box is created that is a certain distance from the camera, the algorithm stops subdividing it and draws a tile. The same resolution tile is drawn over and over, but it is stretched to cover different sized areas.

    This approach is much better suited to cover very large areas. At the furthest distance, the entire terrain will be drawn with just one single 32x32 patch. Here it is in action with a 2048x2048 terrain, the same size as The Zone:
    This example shows how to load a heightmap, add some layers to it, and save the data, or load the data we previously saved:
    --Create terrain local terrain = CreateTerrain(world,2048,32) terrain:SetScale(1,150,1) --Load heightmap terrain:LoadHeightmap("Terrain/2048/2048.r16", VK_FORMAT_R16_UNORM) --Add base layer local mtl = LoadMaterial("Materials/Dirt/dirt01.mat") local layerID = terrain:AddLayer(mtl) --Add rock layer mtl = LoadMaterial("Materials/Rough-rockface1.json") rockLayerID = terrain:AddLayer(mtl) terrain:SetLayerSlopeConstraints(rockLayerID, 35, 90, 25) --Add snow layer mtl = LoadMaterial("Materials/Snow/snow01.mat") snowLayerID = terrain:AddLayer(mtl) terrain:SetLayerHeightConstraints(snowLayerID, 50, 1000, 8) terrain:SetLayerSlopeConstraints(snowLayerID, 0, 35, 10) --Normals if FileType("Terrain/2048/2048_N.raw") == 0 then terrain:UpdateNormals() terrain:SaveNormals("Terrain/2048/2048_N.raw") else terrain:LoadNormals("Terrain/2048/2048_N.raw") end --Layers if FileType("Terrain/2048/2048_L.raw") == 0 or FileType("Terrain/2048/2048_A.raw") == 0 then terrain:SetLayer(rockLayerID, 1) terrain:SetLayer(snowLayerID, 1) terrain:SaveLayers(WriteFile("Terrain/2048/2048_L.raw")) terrain:SaveAlpha(WriteFile("Terrain/2048/2048_A.raw")) else terrain:LoadLayers("Terrain/2048/2048_L.raw") terrain:LoadAlpha("Terrain/2048/2048_A.raw") end The x86 build configurations have also been removed from the game template project. This is available now in the beta tester forum.
  4. Josh
    Documentation in Leadwerks 5 will start in the header files, where functions descriptions are being added directly like this:
    /// <summary> /// Sets the height of one terrain point. /// </summary> /// <param name="x">Horizontal position of the point to modify.</param> /// <param name="y">Vertical position of the point to modify.</param> /// <param name="height">Height to set, in the range -1.0 to +1.0.</param> virtual void SetHeight(const int x, const int y, const float height); This will make function descriptions appear automatically in Visual Studio, to help you write code faster and more easily:

    Visual Studio can also generate an XML file containing all of the project's function descriptions as part of the build process. The generated XML file will serve as the basis for the online documentation and Visual Studio Code extension for Lua. This is how I see it working:

    I am also moving all things private to private members. I found a cool trick that allows me to create read-only members. In the example below, you can access the "position" member to get an entity's local position, but you cannot modify it without using the SetPosition() method. This is important because modifying values often involves updating lots of things in the engine under the hood and syncing data with other threads. This also means that any method Visual Studio displays as you are typing is okay to use, and there won't be any undocumented / use-at-your-own risk types of commands like we had in Leadwerks 4.
    class Entity { private: Vec3 m_position; public: const Vec3& position; }; Entity::Entity() : position(m_position) {} It is even possible to make constructors private so that the programmer has to use the correct CreateTerrain() or whatever command, instead of trying to construct a new instance of the class, with unpredictable results. Interestingly, the constructor itself has to be added as a friend function for this to work.
    class Terrein { private: Terrain(); public: friend shared_ptr<World> CreateTerrain(shared_ptr<World>, int, int, int) }; The only difference is that inside the CreateTerrain function I have to do this:
    auto terrain = shared_ptr<Terrain>(new Terrain); instead of this, because make_shared() doesn't have access to the Terrain constructor. (If it did, you would be able to create a shared pointer to a new terrain, so we don't want that!)
    auto terrain = make_shared<Terrain>(); I have big expectations for Leadwerks 5, so it makes sense to pay a lot of attention to the coding experience you will have while using this. I hope you like it!
  5. Josh
    An often-requested feature for terrain building commands in Leadwerks 5 is being implemented. Here is my script to create a terrain. This creates a 256 x 256 terrain with one terrain point every meter, and a maximum height of +/- 50 meters:
    --Create terrain local terrain = CreateTerrain(world,256,256) terrain:SetScale(256,100,256) Here is what it looks like:

    A single material layer is then added to the terrain.
    --Add a material layer local mtl = LoadMaterial("Materials/Dirt/dirt01.mat") local layerID = terrain:AddLayer(mtl) We don't have to do anything else to make the material appear because by default the entire terrain is set to use the first layer, if a material is available there:

    Next we will raise a few terrain points.
    --Modify terrain height for x=-5,5 do for y=-5,5 do h = (1 - (math.sqrt(x*x + y*y)) / 5) * 20 terrain:SetElevation(127 + x, 127 + y, h) end end And then we will update the normals for that whole section, all at once. Notice that we specify a larger grid for the normals update, because the terrain points next to the ones we modified will have their normals affected by the change in height of the neighboring pixel.
    --Update normals of modified and neighboring points terrain:UpdateNormals(127 - 6, 127 - 6, 13, 13) Now we have a small hill.

    Next let's add another layer and apply it to terrain points that are on the side of the hill we just created:
    --Add another layer mtl = LoadMaterial("Materials/Rough-rockface1.json") rockLayerID = terrain:AddLayer(mtl) --Apply layer to sides of hill for x=-5,5 do for y=-5,5 do slope = terrain:GetSlope(127 + x, 127 + y) alpha = math.min(slope / 15, 1.0) terrain:SetMaterial(rockLayerID, 127 + x, 127 + y, alpha) end end We could improve the appearance by giving it a more gradual change in the rock layer alpha, but it's okay for now.

    This gives you an idea of the basic terrain building API in Leadwerks 5, and it will serve as the foundation for more advanced terrain features. This will be included in the next beta.
  6. Josh
    A new update is available for beta testers.
    The terrain building API is now available and you can begin working with it, This allows you to construct and modify terrains in pure code. Terrain supports up to 256 materials, each with its own albedo, normal, and displacement maps. Collision and raycasting are currently not supported.
    Fast C++ Builds
    Precompiled headers have been integrated into the example project. The Debug build will compile in about 20 seconds the first run, and compile in just 2-3 seconds thereafter. An example class is included which shows how to add files to your game project for optimum compile times. Even if you edit one of your header files, your game will still compile in just a few seconds in debug mode! Integrating precompiled headers into the engine actually brought the size of the static libraries down significantly, so the download is only about 350 MB now.
    Enums Everywhere
    Integer arguments have been replaced with enum values for window styles, entity bounds, and load flags. This is nice because the C++ compiler has some error checking so you don't do something like this:
    LoadTexture("grass.dds", WINDOW_FULLSCREEN); Operators have been added to allow combining enum values as bitwise flags.
    A new LOAD_DUMP_INFO LoadFlags value has been added which will print out information about loaded files (I need this to debug the GLTF loader!).
    Early Spring Cleaning
    Almost all the pre-processor macros have been removed from the Visual Studio project, with just a couple ones left. Overall the headers and project structure have been massively cleaned up.
  7. Josh
    Here are some things I did in the last couple days to fix a computer that was basically unusable.
    It seems that Superfetch was rebranded to "SysMain" in an update and automatically re-enabled. If your computer is grinding away either the CPU or disk usage while doing nothing, this is the culprit. Disable it in Windows services.
    The XBox games bar is suspect. I recommend disabling it now that FRAPS supports Vulkan.
    Some features in Visual Studio are making it unusably slow.
    In Project settings > Link > Debugging, set "Generate Debug Info" to "DEBUG:FASTLINK" (in the debug build only) for faster linking.
    Disable these two settings in the general program Options:
    Enable Diagnostic Tools while debugging Show elapsed time PerfTip while debugging
  8. Josh
    A new beta is uploaded with lots of new features and improvements. Things are really taking shape!
    Animation is now supported and it's really fast. Two examples are included. Package loader plugins now supported, VPK package loader for Source Engine games included with example. Added localization example. Shaders folder very neatly organized, now contains shader family files. Config folder eliminated. Engine headers cleaned up and organized. Lots of third party libraries removed. SVG texture loader removed. Printed console output is now much quieter. Current directory is stripped from load messages. DebugError() command renamed to RuntimeError(). DebugWarning() command renamed to Warning().
  9. Josh
    I created a test of 1000 animated crawler models to see how the performance of Vulkan stacks up against our older OpenGL renderer. Here it is in OpenGL running at 37 FPS, which is pretty respectable considering how many animations are playing (and about 4 million polygons).

    With Vulkan the same test yields a framerate of 66 FPS, 78% faster than the OpenGL version.

    Here is a video of the characters animating. Each skeleton is its own unique animation system, there are no shared skeletons in this demo.
  10. Josh
    It's been nearly a year since I made the decision to port our OpenGL renderer over to Vulkan, and it has been very difficult work, but we are getting back up to speed. I was able to get skinned animation working for the first time in Vulkan yesterday, using a slightly modified version of our original animation shader code.
    The system works exactly the same as in Leadwerks 4, with a few differences:
    Animation features are confined to the Model class only, and are no longer a general Entity feature. Bones are no longer an entity. This reduces a lot of overhead when animation is processed. There will be some type of "AttachToBone" method in the entity class you can use to place a weapon in a character's hand. The Model class has a new Skeleton member. A skeleton is only present in animated models. Skeletons can be shared between models. This allows the engine to process animation once and multiple models can share the skeleton. The models can be instances of the same model, or different models. This is very good for rendering large crowds of enemies. (However, you might not even need to bother with this type of optimization, because 1000 unique skeletons works with no issues, as seen below.) Animation is executed on one or more separate threads which makes it very very fast. Animated characters are now batched instead of drawn one at a time. With the OpenGL prototype renderer we saw very good performance with this technique, so it will be interesting to see how Vulkan compares:
    I have not added support for GLTF animation loading yet, which will give us a wide variety of ready-to-use animated models.
  11. Josh
    An update is available for Leadwerks Game Engine 5 beta. The GUI system is now working with support for the following items:
    Panel Button Tabber Label Hyperlink Text field (editable, single line) Text area (multiline, read-only, allows text selection) The GUI scripts use a system of "blocks". You can add a solid rectangle, a wire rectangle, (both support rounded corners) or a text block.
    The drawing hierarchy is not yet respected, so if you type a long line of text in a text field, for example, the text will overrun the container.
    Some weirdness exists when GUI blocks are resized, can be seen in the tabbed panel tabs.
    Alpha anti-alias is not currently implemented so curved edges will be a bit pixellated. Has to do with pre-multiplied alpha the engine now uses by default.
    It's better to move, resize, show and hide, and change the text of blocks than to clear the widget and recreate them. There was some difficult stuff here with the culling and render tweening system I had to work out.
  12. Josh
    Analytics is a feature I have long thought was a good candidate to be moved into a plugin.
    It is an optional features that only some users care about. It imports some pretty big third-party libraries into the engine. It requires an extra DLL be distributed with your game. It's a totally self-contained features that is easy to separate out from the rest of the engine. I have uploaded my code for Analytics in Leadwerks here as a new empty Visual Studio project:
    https://github.com/Leadwerks/PluginSDK/tree/master/Game Analytics
    This will be a good test case to see how we can implement API plugins. An API plugin is an optional module that adds new commands to the engine. It should work with C++, Lua, and C#.
    How do we do this? I'm not sure what the best way is. Do we want to try to make an object-oriented API like the rest of the engine, or should we just stick to a simpler procedural API? I putting this out now so we can discuss and determine the best way to handle this.
    Here is some info on using sol to expose an API from a DLL:
    If we can make this plugin work then it will serve as an example for how all API plugins should be integrated. Please take a look at tell me how you would like this to work.
  13. Josh
    The addition of our plugin system makes the engine feel different. There is a stronger distinction between core and non-essential functionality. I removed a lot of third-party libraries from the project and am just focusing on graphics, physics, pathfinding, and other features that are core to the functioning of your game. Things like file importers terrain generation, etc. can be stored away in self-contained optional plugins, or can be part of the editor project, and do not need to reside in the base engine.
    I feel like this satisfies both my desire for the core engine to be as focused and streamlined as possible, and for the end user's desire to have infinity features.
    I also feel like I want the engine to be as small and "in the background" as possible. I removed the superfluous print statements like "initializing engine blah blah blah..." so that only the useful info gets printed. Your game's log file is not a place I need to advertise my engine. Additionally, I am trying to simplify the game directory as much as possible with as few files as possible.
    Here is the only printed output when a program is run:
    Running script "Scripts/Start/Autoload/FITextureLoader.lua" Loading plugin "Plugins/FITextureLoader.dll"... Running script "Scripts/Start/LoadPackages.lua" Running script "Scripts/Start/System/ComponentSystem.lua" Running script "Scripts/Start/System/Error.lua" Running script "Scripts/Animation.lua" Loading shader family "Shaders/PBR.json"... Loading model "Models/Crawler/crawler.mdl" Loading material "Models/Crawler/crawler.mat"... Loading texture "Models/Crawler/crawler.tex"... Loading texture "Models/Crawler/crawlerdot3.tex"... Loading texture "Models/Crawler/crawler_spec.tex"... Loading shader family "Shaders/Blinn-Phong.json"... Loading material "Models/Crawler/crawler_eyeball.mat"... Loading texture "Models/Crawler/d_eyeball.tex"... Loading texture "Models/Crawler/crawler_eyeballdot3.tex"... I got rid of the "Config" folder. Shader families should be placed in the "Shaders" directory (although they can be stored anywhere) and languages can be kept in a "Localization" folder, if you use them.
    The shaders folder is now organized with some sanity.

    Materials, languages, and shader familiy JSON files all need to have a root object that indicates the type of asset they are:
    { "material": { "color": [ 1, 1, 1, 1 ], "emission": [ 0, 0, 0 ], "metallic": 0.75, "roughness": 0.5, "baseTexture": { "file": "./wall_01_D.tex" }, "normalTexture": { "file": "./wall_01_N.tex" } } } The "Scripts/Start" directory will now be executed recursively at program start. I hid the required scripts away in a sub-folder called "System" so hopefully no one will delete them.
    I have renamed the "Bank" and "BankStream" classes to "Buffer" and "BufferStream". The old class names were a holdover from the days of BlitzMax.
    Finally, with UPX I got the size of the release EXE and required DLLs down to 3.5 megabytes.
    The whole thing should feel small, efficient, and quick to start. The opposite of bloated.
  14. Josh
    Leadwerks 4 supports compressed and encrypted game files in ZIP format, but there are many different custom package formats different games use. The plugin system in Leadwerks 5 allows us to create importers for these different package formats so we can access content directly from commercial games you have installed on your computer. The example below shows how to load a VTF texture from the game Half-Life 2 directly from the game's install files.
    First we need to load some plugins to deal with these weird file formats.
    --Load VPK package loader plugin local vpkloader = LoadPlugin("Plugins/VPK.dll") --Load VTF texture loader plugin local vtfloader = LoadPlugin("Plugins/VTF.dll") Next we will load a single VPK package directly from the Half-Life 2 install directory. If you have Steam installed somewhere other than the default path you can change the value of the STEAM_PATH variable to load the package from somewhere else.
    --Steam installation path local STEAM_PATH = "C:/Program Files (x86)/Steam" --Load package (Half-Life 2 must be installed) local pkg = LoadPackage(STEAM_PATH.."/steamapps/common/Half-Life 2/hl2/hl2_textures_dir.vpk") Now we can load content directly from the Half-Life 2 game files. Although the file specified below does not actually exist in the game folder, it will be loaded as if it was because of the VPK package we previously loaded.
    --Load texture local tex = LoadTexture("materials/building_template/buildingset040a.vtf") Here is the result when I run the program:

    Now if you wanted to load all the HL2 data files when your game starts, that is easy to do too. If you place this script in the "Scripts/Start" folder all the content will be automatically made available:
    local STEAM_PATH = "C:/Program Files (x86)/Steam/" local HL2_PATH = STEAM_PATH.."steamapps/common/Half-Life 2/hl2/" local dir = LoadDir(HL2_PATH) local k,v LoadedPackages = {} for k,v in ipairs(dir) do local ext = Lower(ExtractExt(v)) if ext == "vpk" then local pkg = LoadPackage(v) if pkg ~= nil then table.insert(LoadedPackages, pkg) end end end What is this good for? You can browse and view all the content in your installed games and use some assets as placeholders. Valve is pretty lenient with their IP so if your game is only published on Steam, you could probably use all the Source games textures and models. You could make a mod that replaces the original game engine but requires the game to be installed to run. It would also not be too hard to integrate these features into the new editor so that it can be used as a modding tool for different games and allow you to create new game levels.
    You can get the source for this and other plugins on Github.
  15. Josh
    I've restructured the plugin SDK for our new engine and created a new repository on Github here:
    The GMF2 format will only be used as an internal data transfer protocol for model loader plugins. Our main supported file format will be GLTF.
    As of now, the plugin system can be used to write texture loaders for different file formats, model loaders, or to modify behavior of particles in the new particle system. The FreeImage texture loader has been moved out of the core engine and into a plugin so you no longer have to include the FreeImage DLL unless you want to use it. The main supported texture format will be DDS, but the FreeImage plugin supports many common image file formats.
  16. Josh
    The Leadwerks 5 beta will soon be updated with particle emitters and an example particle system plugin. Previously, I showed some impressive results with physically interactive particles that collide with and exert forces on the environment. I decided to use the plugin system for controlling particle behavior, as this offers the best performance and can be run on the physics thread. 
    A particle system plugin uses some predefined structures and functions to modify the behavior of particles when they are emitted or as they are updated. This allows for unlimited features to be added to the particle system, because anything you want can be added with a plugin. A system for sending settings to the plugin will be implemented in the future so you can adjust the plugin settings and see the results. The default particle settings and features will probably stay pretty barebones and I will just use the plugin system to add any advanced functionality since it is so flexible.
    void EmitParticle(ParticleModifier* mod, ParticleSystem* particlesystem, Particle* particle) { if (mod->emissionshape == EMISSION_SHAPE_BOX) { particle->position[0] = Random(-mod->area[0], mod->area[0]); particle->position[1] = Random(-mod->area[1], mod->area[1]); particle->position[2] = Random(-mod->area[2], mod->area[2]); } else if (mod->emissionshape == EMISSION_SHAPE_CYLINDER) { particle->position[0] = Random(-mod->area[0], mod->area[0]); particle->position[1] = Random(-mod->area[1], mod->area[1]); particle->position[2] = Random(-mod->area[2], mod->area[2]); auto l = sqrt(particle->position[0] * particle->position[0] + particle->position[1] * particle->position[1] + particle->position[2] * particle->position[2]); if (l > 0.0f) { particle->position[0] /= l; particle->position[1] /= l; particle->position[2] /= l; } } particle->position[0] += particlesystem->matrix[12]; particle->position[1] += particlesystem->matrix[13]; particle->position[2] += particlesystem->matrix[14]; } There are three other new Lua examples included. Coroutines.lua shows how a sequence of actions can be added to an entity before the game starts, and the actions will be executed in order:
    --Create model local model = CreateBox(world) --Add some behaviors to be executed in order model:AddCoroutine(MoveToPoint, Vec3(3,0,0), 2) model:AddCoroutine(MoveToPoint, Vec3(-3,0,0), 2) model:AddCoroutine(MoveToPoint, Vec3(0,0,0), 2) --Main loop while window:Closed() == false do world:Update() world:Render(framebuffer) end This is great for setting up cut scenes or other sequences of events.
    An example showing how to enable tessellation is also included. Tessellation is now a per-camera setting.
    camera:SetTessellation(10) The number you input is the size in pixels of the tessellated primitives. Use zero to disable tessellation. Tessellation is disabled by default on all cameras.
    Finally, an example showing how to use a texture loader plugin is included. All you have to do is load the plugin and after that textures can be loaded in VTF format:
    local vtfloader = LoadPlugin("Plugins/VTF.dll") local tex = LoadTexture("Materials/wall01.vtf")  
  17. Josh
    I made some changes to the design of the particle system. I am less concerned with the exact behavior of particles as they move around and move interested right now in building a system with good performance and deep physics interactions. Although I want particle behavior to be customizable, I don't think scripts are the right tool for the job. C++ plugins are better suited for this for two reasons.
    C++ is much faster, and particles are a system that will make heavy use of that. Lua scripts can't be run on separate threads. In Leadwerks Engine 4 we have basic particle collisions, but I wanted something more interactive in the new system. I move the particle update code into the physics thread. I implemented collision as well as the ability for particles to exert forces on other objects. Here's what happens when some slow-moving smoke particles interact with a scene: The lower platform rotates freely while the upper platform is motorized.
    When the particle velocity is increase they start to behave like a stream of water:
    Best of all, the speed is surprisingly fast. 4000 particles with collision update in just 2 milliseconds. The code scales well across cores so if you have a lot of CPU cores simulations with 100,000 particles are possible.
    Right now particles are processed in the physics thread, and get sent to the rendering thread for display, but right now the main thread actually never sees the individual particles.
    This is fast enough I think particles will default to full physics. Instead of just being a dumb visual effect we are going to have fully interactive fluids and gases. Flamethrowers can fill a room with fire and it will creep around corners to fill a space.
  18. Josh
    For finer control over what 2D elements appear on what camera, I have implemented a system of "Sprite Layers". Here's how it works:
    A sprite layer is created in a world. Sprites are created in a layer. Layers are attached to a camera (in the same world). The reason the sprite layer is linked to the world is because the render tweening operates on a per-world basis, and it works with the sprite system just like the entity system. In fact, the rendering thread uses the same RenderNode class for both.
    I have basic GUI functionality working now. A GUI can be created directly on a window and use the OS drawing commands, or it can be created on a sprite layer and rendered with 3D graphics. The first method is how I plan to make the new editor user interface, while the second is quite flexible. The most common usage will be to create a sprite layer, attach it to the main camera, and add a GUI to appear in-game. However, you can just as easily attach a sprite layer to a camera that has a texture render target, and make the GUI appear in-game on a panel in 3D. Because of these different usages, you must manually insert events like mouse movements into the GUI in order for it to process them:
    while true do local event = GetEvent() if event.id == EVENT_NONE then break end if event.id == EVENT_MOUSE_DOWN or event.id == EVENT_MOUSE_MOVE or event.id == EVENT_MOUSE_UP or event.id == EVENT_KEY_DOWN or event.id == EVENT_KEY_UP then gui:ProcessEvent(event) end end You could also input your own events from the mouse position to create interactive surfaces, like in games like DOOM and Soma. Or you can render the GUI to a texture and interact with it by feeding in input from VR controllers.

    Because the new 2D drawing system uses persistent objects instead of drawing commands the code to display elements has changed quite a lot. Here is my current button script. I implemented a system of abstract GUI "rectangles" the script can create and modify. If the GUI is attached to a sprite layer these get translated into sprites, and if it is attached directly to a window they get translated into system drawing commands. Note that the AddTextRect doesn't even allow you to access the widget text directly because the widget text is stored in a wstring, which supports Unicode characters but is not supported by Lua.
    --Default values widget.pushed=false widget.hovered=false widget.textindent=4 widget.checkboxsize=14 widget.checkboxindent=5 widget.radius=3 widget.textcolor = Vec4(1,1,1,1) widget.bordercolor = Vec4(0,0,0,0) widget.hoverbordercolor = Vec4(51/255,151/255,1) widget.backgroundcolor = Vec4(0.2,0.2,0.2,1) function widget:MouseEnter(x,y) self.hovered = true self:Redraw() end function widget:MouseLeave(x,y) self.hovered = false self:Redraw() end function widget:MouseDown(button,x,y) if button == MOUSE_LEFT then self.pushed=true self:Redraw() end end function widget:MouseUp(button,x,y) if button == MOUSE_LEFT then self.pushed = false if self.hovered then EmitEvent(EVENT_WIDGET_ACTION,self) end self:Redraw() end end function widget:OK() EmitEvent(EVENT_WIDGET_ACTION,self) end function widget:KeyDown(keycode) if keycode == KEY_ENTER then EmitEvent(EVENT_WIDGET_ACTION,self) self:Redraw() end end function widget:Start() --Background self:AddRect(self.position, self.size, self.backgroundcolor, false, self.radius) --Border if self.hovered == true then self:AddRect(self.position, self.size, self.hoverbordercolor, true, self.radius) else self:AddRect(self.position, self.size, self.bordercolor, true, self.radius) end --Text if self.pushed == true then self:AddTextRect(self.position + iVec2(1,1), self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) else self:AddTextRect(self.position, self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) end end function widget:Draw() --Update position and size self.primitives[1].position = self.position self.primitives[1].size = self.size self.primitives[2].position = self.position self.primitives[2].size = self.size self.primitives[3].size = self.size --Update the border color based on the current hover state if self.hovered == true then self.primitives[2].color = self.hoverbordercolor else self.primitives[2].color = self.bordercolor end --Offset the text when button is pressed if self.pushed == true then self.primitives[3].position = self.position + iVec2(1,1) else self.primitives[3].position = self.position end end This is arguably harder to use than the Leadwerks 4 system, but it gives you advanced capabilities and better performance that the previous design did not allow.
  19. Josh
    I wanted to work on something a bit different, and this sure is different. I've got a framework of a new particle system worked out. What's really special about this system is the amount of interactivity the particles will allow.
    Particle-world collisions. Particle-particle collisions (repulsion) Particle-particle cohesion (fluids with surface tension) Instead of just being a visual effect, I want our new particles to be fully interactive with physics so that particles can exert forces on objects. This will allow you to simulate fluids, smoke, and other effects in a realistic manner, not just dumb collision of particles bounding off walls. It should even be possible to simulate hydrophobic and hydrophillic liquids if you mix two together with different cohesion values.
    Basically what I want is something like Nvidia Flow on the CPU and exerting forces on the world. So if you had water falling on a water wheel the wheel would move because of the forces, or a blast of wind could knock objects over without any special force fields or other fake effects.
    I also have a technique worked out that will allow lighting of clouds and other masses of gas, with back-scattering.
    Emitters can be instanced so if you have one really high-quality torch effect, for example, you can instance it and use it as much as you like without any additional computational cost per instance.
    Particle emitters can be controlled with a Lua script or C++ actor. Two new functions are available, UpdateParticle() and EmitParticle(). Here is a script that controls particle behavior over time:
    entity.particleVelocity = Vec3(0,0,1) entity.particleAcceleration = Vec3(0,-1,0) entity.inverseSquareFalloff = true entity.particleRadiusBegin = 0.1 entity.particleRadiusEnd = 0.2 entity.particleColorBegin = Vec4(1,1,1,1) entity.particleColorEnd = Vec4(1,1,1,0) entity.particleMass = 1 entity.particleSpin = 5 function entity:Start() self.particleColorBeginHSL = HSL(self.particleColorBegin.rgb) self.particleColorEndHSL = HSL(self.particleColorEnd.rgb) local emitter = Emitter(self) if emitter == nil then return end local n for n = 1, #emitter.particles do emitter.particles[n].mass = self.particleMass emitter.particles[n].falloff = (n-1) / (#emitter.particles - 1) end end function entity:EmitParticle(index) local emitter = Emitter(self) if emitter == nil then return end emitter.particles[index].position = self:GetPosition(true) emitter.particles[index].velocity = TransformVector(self.particleVelocity,self,nil) emitter.particles[index].radius = self.particleRadiusBegin emitter.particles[index].color = self.particleColorBegin end function entity:UpdateParticle(index) local emitter = Emitter(self) if emitter == nil then return end emitter.particles[index].velocity = emitter.particles[index].velocity + self.particleAcceleration / 60 local falloff = emitter.particles[index].falloff if self.inverseSquareFalloff then falloff = falloff * falloff end emitter.particles[index].color.rgb = RGB(self.particleColorBeginHSL * (1 - falloff) + self.particleColorEndHSL * falloff) emitter.particles[index].color.a = self.particleColorBegin.a * (1 - falloff) + self.particleColorEnd.a * falloff emitter.particles[index].radius = self.particleRadiusBegin * (1 - falloff) + self.particleRadiusEnd * falloff emitter.particles[index].rotation = emitter.particles[index].rotation + self.particleSpin / 60 end A different script could be used to make particles emit from vertices of a model, to make the model appear to be on fire, or other effects. This will allow infinite customization to create any behavior you want.
    Particle physics will be calculated on the physics thread so I expect them to be very fast.
  20. Josh
    I have been working on 2D rendering off and on since October. Why am I putting so much effort into something that was fairly simple in Leadwerks 4? I have been designing a system in anticipation of some features I want to see in the GUI, namely VR support and in-game 3D user interfaces. These are both accomplished with 2D drawing performed on a texture. Our system of sprite layers, cameras, and sprites was necessary in order to provide enough control to accomplish this.
    I now have 2D drawing to a texture working, this time as an official supported feature. In Leadwerks 4, some draw-to-texture features were supported, but it was through undocumented commands due to the complex design of shared resources between OpenGL contexts. Vulkan does not have this problem because everything, including contexts (or rather, the VK equivalent) is bound to an abstract VkInstance object.

    Here is the Lua code that makes this program:
    --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; if display == nil then DebugError("Primary display not found.") end local displayscale = display:GetScale() --Create a window local window = CreateWindow(display, "2D Drawing to Texture", 0, 0, math.min(1280 * displayscale.x, display.size.x), math.min(720 * displayscale.y, display.size.y), WINDOW_TITLEBAR) --Create a rendering framebuffer local framebuffer = CreateFramebuffer(window); --Create a world local world = CreateWorld() --Create second camera local texcam = CreateCamera(world) --Create a camera local camera = CreateCamera(world) camera:Turn(45,-45,0) camera:Move(0,0,-2) camera:SetClearColor(0,0,1,1) --Create a texture buffer local texbuffer = CreateTextureBuffer(512,512,1,true) texcam:SetRenderTarget(texbuffer) --Create scene local box = CreateBox(world) --Create render-to-texture material local material = CreateMaterial() local tex = texbuffer:GetColorBuffer() material:SetTexture(tex, TEXTURE_BASE) box:SetMaterial(material) --Create a light local light = CreateLight(world,LIGHT_DIRECTIONAL) light:SetRotation(55,-55,0) light:SetColor(2,2,2,1) --Create a sprite layer. This can be shared across different cameras for control over which cameras display the 2D elements local layer = CreateSpriteLayer(world) texcam:AddSpriteLayer(layer) texcam:SetPosition(0,1000,0)--put the camera really far away --Load a sprite to display local sprite = LoadSprite(layer, "Materials/Sprites/23.svg", 0, 0.5) sprite:MidHandle(true,true) sprite:SetPosition(texbuffer.size.x * 0.5, texbuffer.size.y * 0.5) --Load font local font = LoadFont("Fonts/arial.ttf", 0) --Text shadow local textshadow = CreateText(layer, font, "Hello!", 36 * displayscale.y, TEXT_LEFT, 1) textshadow:SetColor(0,0,0,1) textshadow:SetPosition(50,30) textshadow:SetRotation(90) --Create text text = textshadow:Instantiate(layer) text:SetColor(1,1,1,1) text:SetPosition(52,32) text:SetRotation(90) --Main loop while window:Closed() == false do sprite:SetRotation(CurrentTime() / 30) world:Update() world:Render(framebuffer) end I have also added a GetTexCoords() command to the PickInfo structure. This will calculate the tangent and bitangent for the picked triangle and then calculate the UV coordinate at the picked position. It is necessary to calculate the non-normalized tangent and bitangent to get the texture coordinate, because the values that are stored in the vertex array are normalized and do not include the length of the vectors.
    local pick = camera:Pick(framebuffer, mousepos.x, mousepos.y, 0, true, 0) if pick ~= nil then local texcoords = pick:GetTexCoords() Print(texcoords) end Maybe I will make this into a Mesh method like GetPolygonTexCoord(), which would work just as well but could potentially be useful for other things. I have not decided yet.
    Now that we have 2D drawing to a texture, and the ability to calculate texture coordinates at a position on a mesh, the next step will be to set up a GUI displayed on a 3D surface, and to send input events to the GUI based on the user interactions in 3D space. The texture could be applied to a computer panel, like many of the interfaces in the newer DOOM games, or it could be used as a panel floating in the air that can be interacted with VR controllers.
  21. Josh
    Putting all the pieces together, I was able to create a GUI with a sprite layer, attach it to a camera with a texture buffer render target, and render the GUI onto a texture applied to a 3D surface. Then I used the picked UV coords to convert to mouse coordinates and send user events to the GUI. Here is the result:

    This can be used for GUIs rendered onto surfaces in your game, or for a user interface that can be interacted with in VR. This example will be included in the next beta update.
  22. Josh
    I started to implement quads for tessellation, and at that point the shader system reached the point of being unmanageable. Rendering an object to a shadow map and to a color buffer are two different processes that require two different shaders. Turbo introduces an early Z-pass which can use another shader, and if variance shadow maps are not in use this can be a different shader from the shadow shader. Rendering with tessellation requires another set of shaders, with one different set for each primitive type (isolines, triangles, and quads). And then each one of these needs a masked and opaque option, if alpha discard is enabled.
    All in all, there are currently 48 different shaders a material could use based on what is currently being drawn. This is unmanageable.
    To handle this I am introducing the concept of a "shader family". This is a JSON file that lists all possible permutations of a shader. Instead of setting lots of different shaders in a material, you just set the shader family one:
    shaderFamily: "PBR.json" Or in code:
    material->SetShaderFamily(LoadShaderFamily("PBR.json")); The shader family file is a big JSON structure that contains all the different shader modules for each different rendering configuration: Here are the partial contents of my PBR.json file:
    { "turboShaderFamily" : { "OPAQUE": { "default": { "base": { "vertex": "Shaders/PBR.vert.spv", "fragment": "Shaders/PBR.frag.spv" }, "depthPass": { "vertex": "Shaders/Depthpass.vert.spv" }, "shadow": { "vertex": "Shaders/Shadow.vert.spv" } }, "isolines": { "base": { "vertex": "Shaders/PBR_Tess.vert.spv", "tessellationControl": "Shaders/Isolines.tesc.spv", "tessellationEvaluation": "Shaders/Isolines.tese.spv", "fragment": "Shaders/PBR_Tess.frag.spv" }, "shadow": { "vertex": "Shaders/DepthPass_Tess.vert.spv", "tessellationControl": "Shaders/DepthPass_Isolines.tesc.spv", "tessellationEvaluation": "Shaders/DepthPass_Isolines.tese.spv" }, "depthPass": { "vertex": "Shaders/DepthPass_Tess.vert.spv", "tessellationControl": "DepthPass_Isolines.tesc.spv", "tessellationEvaluation": "DepthPass_Isolines.tese.spv" } }, "triangles": { "base": { "vertex": "Shaders/PBR_Tess.vert.spv", "tessellationControl": "Shaders/Triangles.tesc.spv", "tessellationEvaluation": "Shaders/Triangles.tese.spv", "fragment": "Shaders/PBR_Tess.frag.spv" }, "shadow": { "vertex": "Shaders/DepthPass_Tess.vert.spv", "tessellationControl": "Shaders/DepthPass_Triangles.tesc.spv", "tessellationEvaluation": "Shaders/DepthPass_Triangles.tese.spv" }, "depthPass": { "vertex": "Shaders/DepthPass_Tess.vert.spv", "tessellationControl": "DepthPass_Triangles.tesc.spv", "tessellationEvaluation": "DepthPass_Triangles.tese.spv" } }, "quads": { "base": { "vertex": "Shaders/PBR_Tess.vert.spv", "tessellationControl": "Shaders/Quads.tesc.spv", "tessellationEvaluation": "Shaders/Quads.tese.spv", "fragment": "Shaders/PBR_Tess.frag.spv" }, "shadow": { "vertex": "Shaders/DepthPass_Tess.vert.spv", "tessellationControl": "Shaders/DepthPass_Quads.tesc.spv", "tessellationEvaluation": "Shaders/DepthPass_Quads.tese.spv" }, "depthPass": { "vertex": "Shaders/DepthPass_Tess.vert.spv", "tessellationControl": "DepthPass_Quads.tesc.spv", "tessellationEvaluation": "DepthPass_Quads.tese.spv" } } } } } A shader family file can indicate a root to inherit values from. The Blinn-Phong shader family pulls settings from the PBR file and just switches some of the fragment shader values.
    { "turboShaderFamily" : { "root": "PBR.json", "OPAQUE": { "default": { "base": { "fragment": "Shaders/Blinn-Phong.frag.spv" } }, "isolines": { "base": { "fragment": "Shaders/Blinn-Phong_Tess.frag.spv" } }, "triangles": { "base": { "fragment": "Shaders/Blinn-Phong_Tess.frag.spv" } }, "quads": { "base": { "fragment": "Shaders/Blinn-Phong_Tess.frag.spv" } } } } } If you want to implement a custom shader, this is more work because you have to define all your changes for each possible shader variation. But once that is done, you have a new shader that will work with all of these different settings, which in the end is easier. I considered making a more complex inheritance / cascading schema but I think eliminating ambiguity is the most important goal in this and that should override any concern about the verbosity of these files. After all, I only plan on providing a couple of these files and you aren't going to need any more unless you are doing a lot of custom shaders. And if you are, this is the best solution for you anyways.
    Consequently, the baseShader, depthShader, etc. values in the material file definition are going away. Leadwerks .mat files will always use the Blinn-Phong shader family, and there is no way to change this without creating a material file in the new JSON material format.
    The shader class is no longer derived from the Asset class because it doesn't correspond to a single file. Instead, it is just a dumb container. A ShaderModule class derived from the Asset class has been added, and this does correspond with a single .spv file. But you, the user, won't really need to deal with any of this.
    The result of this is that one material will work with tessellation enabled or disabled, quad, triangle, or line meshes, and animated meshes. I also added an optional parameter in the CreatePlane(), CreateBox(), and CreateQuadSphere() commands that will create these primitives out of quads instead of triangles. The main reason for supporting quad meshes is that the tessellation is cleaner when quads are used. (Note that Vulkan still displays quads in wireframe mode as if they are triangles. I think the renderer probably converts them to normal triangles after the tessellation stage.)

    I also was able to implement PN Quads, which is a quad version of the Bezier curve that PN Triangles add to tessellation.

    Basically all the complexity is being packed into the shader family file so that these decisions only have to be made once instead of thousands of times for each different material.
  23. Josh
    In Leadwerks 4, render-to-texture was accomplished with the SetRenderTarget command, which allowed a camera to draw directly to a specified texture, while hiding the underlying framebuffer object (FBO). In the new engine we have a bit more explicit handling of this behavior. This is largely in part due to the use of Vulkan's bindless design, which greatly improves the context-binding design of OpenGL. The Leadwerks "Buffer" class was never documented or officially supported because the underlying OpenGL functionality made the system pretty messy, but the design of Vulkan simplifies this aspect of graphics.
    We have seen that the Framebuffer classes replaces the LE4 context. I've added a TextureBuffer class which can be created similarly:
    shared_ptr<TextureBuffer> CreateTextureBuffer(const int width, const int height, const int colorcomponents = 1, const bool depthcomponent = true, const int samples = 0); Once a TextureBuffer is created, you can set a camera to target it for rendering:
    camera->SetRenderTarget(texbuffer); You can also apply its color component(s) to a material:
    material->SetTexture(texbuffer->GetColorBuffer(0), TEXTURE_BASE); You could also retrieve the depth buffer and apply that to a material, rendering the scene from the top down and using the depth in a rain or snow shader, for example.
    This functionality will later be used to render the GUI system to a texture for use in VR or with in-game menus painted onto 3D surfaces.
    Like everything with Vulkan, this involved a very long process of figuring out everything we need to use, discarding the things we don't, and packaging it up in a structure that is actually usable by the end user. However, once all that is done we have a very powerful system that is optimized for exactly the way modern GPUs work. Here is a small sample of some of my code, just to give you an idea of how complicated this stuff is:
    for (auto pair : visset->cameravislists) { auto cam = pair.first; clear[1].color = { cam->clearcolor.r, cam->clearcolor.g, cam->clearcolor.b, cam->clearcolor.a }; auto light = dynamic_pointer_cast<RenderLight>(cam); if (light == nullptr and cam->rendertarget == nullptr) continue; renderpass[0] = device->shadowpass; renderpass[1] = device->renderpass[CLEAR_COLOR | CLEAR_DEPTH]; int faces = 1; if (light) { if (light->description.type == LIGHT_POINT) faces = 6; } if (MULTIPASS_CUBEMAP) faces = 1; for (int face = 0; face < faces; ++face) { renderPassBeginInfo.clearValueCount = 2; if (light) { renderPassBeginInfo.renderPass = device->shadowpass->pass; if (light->description.type == LIGHT_POINT and MULTIPASS_CUBEMAP == true) { renderPassBeginInfo.renderPass = device->cubeshadowpass->pass; } renderPassBeginInfo.framebuffer = light->shadowbuffer[face]->framebuffer; renderPassBeginInfo.renderArea.extent.width = light->shadowbuffer[face]->size.width; renderPassBeginInfo.renderArea.extent.height = light->shadowbuffer[face]->size.height; } else { renderpass[0] = device->renderpass[CLEAR_COLOR | CLEAR_DEPTH]; int cc = cam->rendertarget->CountColorTextures(); renderPassBeginInfo.renderPass = device->rendertotexturepass[cc][int(cam->rendertarget->depthtexture != nullptr)]->pass; renderPassBeginInfo.framebuffer = cam->rendertarget->framebuffer; renderPassBeginInfo.renderArea.extent.width = cam->rendertarget->size.width; renderPassBeginInfo.renderArea.extent.height = cam->rendertarget->size.height; } vkCmdBeginRenderPass(commandbuffers[currentFrame]->commandbuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); RecordDraw(currentFrame, cam, pair.second, renderpass[0], face); commandbuffers[currentFrame]->EndRenderPass(); if (light) commandbuffers[currentFrame]->BindResource(light->shadowbuffer[face]); //Copy output to render texture if (cam->rendertarget) { for (int n = 0; n < cam->rendertarget->colortarget.size(); ++n) { if (cam->rendertarget->colortarget[n] != nullptr) { commandbuffers[currentFrame]->TransitionImageLayout(pair.first->rendertarget->colortexture[n], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, -1); commandbuffers[currentFrame]->TransitionImageLayout(pair.first->rendertarget->colortarget[n], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, -1); VkImageCopy regions = {}; regions.dstOffset = {0u,0u,0u}; regions.extent = { uint32_t(cam->rendertarget->colortarget[n]->size.x), uint32_t(cam->rendertarget->colortarget[n]->size.y), 1u}; regions.srcOffset = regions.dstOffset; regions.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; regions.dstSubresource.baseArrayLayer = 0; regions.dstSubresource.layerCount = 1; regions.dstSubresource.mipLevel = 0; regions.srcSubresource = regions.dstSubresource; vkCmdCopyImage(commandbuffers[currentFrame]->commandbuffer, cam->rendertarget->colortexture[n]->vkimage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, cam->rendertarget->colortarget[n]->vkimage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &regions); commandbuffers[currentFrame]->TransitionImageLayout(pair.first->rendertarget->colortarget[n], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, -1); } } } } } Below is a simple Lua program that sets up a scene with two cameras, and renders one camera to a texture buffer which is displayed on the middle box itself.
    --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; if display == nil then DebugError("Primary display not found.") end local displayscale = display:GetScale() --Create a window local window = CreateWindow(display, "Render to Texture", 0, 0, math.min(1280 * displayscale.x, display.size.x), math.min(720 * displayscale.y, display.size.y), WINDOW_TITLEBAR) --Create a rendering framebuffer local framebuffer = CreateFramebuffer(window); --Create a world local world = CreateWorld() --Create second camera local texcam = CreateCamera(world) texcam:SetClearColor(1,0,1,1) --Create a camera local camera = CreateCamera(world) camera:Move(0,0,-2) camera:SetClearColor(0,0,1,1) --Create a texture buffer local texbuffer = CreateTextureBuffer(512,512,1,true) texcam:SetRenderTarget(texbuffer) --Create scene local box = CreateBox(world) local cone = CreateCone(world) cone:SetPosition(2,0,0) cone:SetColor(1,0,0,1) local sphere = CreateSphere(world) sphere:SetPosition(-2,0,0) sphere:SetColor(0,1,0,1) --Create render-to-texture material local material = CreateMaterial() local tex = texbuffer:GetColorBuffer() material:SetTexture(tex, TEXTURE_BASE) box:SetMaterial(material) --Create a light local light = CreateLight(world,LIGHT_DIRECTIONAL) light:SetRotation(35,-55,0) --Main loop while window:Closed() == false do texcam:SetPosition(0,0,0) texcam:Turn(0,1,0) texcam:Move(0,0,-2) world:Update() world:Render(framebuffer) end Here is the result. Look how simple it is to control this powerful system!

  24. Josh
    It's 2020, and we are officially now living in the future. I predict a 1980s  neon / Cyberpunk will start to manifest in real life as the number of this decade starts to imprint on peoples' minds.
    In fact, it has already begun. Tesla will be remembered as the start of this trend.

    I am happy with this direction because pretty much all entertainment and style has sucked since the late 1990s. Installing some microchip neural enhancement implants is a small price to pay in order to get some good new electronic music.
    Here are some reminders of the current state of the world.
    The U.S. Space Force is now a real thing.

    This thing is real:

    You can also buy one of these right now:

    Even more impressively, Leadwerks Software will release a new 3D engine in 2020 optimized for virtual reality with Vulkan graphics and 2-3x faster performance.
  25. Josh
    The GUIBlock structure now has an iVec4 "clipregion" member. This allows you to set a clipping region around a block to prevent it from drawing outside the region. The region will automatically be shrunk to show only the visible area of the widget, within the visible area of its parent. The purpose of this is to prevent text from overflowing outside of the widget. I found this necessary because the multi-line text area widget involves the dynamic creation of different persistent 2D elements, and keeping the outer border on top was becoming a problem.
    The Sprite class now has a SetClipRegion(iVec4) method, which is what gets used to display the above effect when the GUI is rendered with 3D graphics.
    Precompiled headers are enabled for the Visual Studio project. The first build will still be prohibitively slow but subsequent builds should be faster.
    Vulkan headers and lib are now included in the project, so you don't have to download the Vulkan SDK separately.
    Some other new things are included, so be sure to take a look.
  • Create New...