View on GitHub

3DreamEngine

3DreamEngine is an *awesome* 3d engine for LÖVE.

Engine

3Dreams idea is to be easy to use with a small but complete set of features. If the features provided are not enough, or too slow, you are probably looking for a more advanced 3D engine.

Now, let’s explain the basic components:

Classes

3Dream uses classes and instances to represent most data objects. For More information about specific classes, check out 3DreamEngine/classes/*.

Materials

Since object loading depends on having the correct materials and shader, it is recommended to start with defining the materials.

Importing materials from the respective file formats technically work (especially gltf), materials tend to be broken or misconfigured depending on the exporter.

Therefore, let’s focus on two ways of adding materials to the material library:

-- Create an empty material
local material = dream:newMaterial()

-- Configure it
material:setAlbedoTexture("some/image.png")
material:setMetallic(1.0)
material:setRoughness(1.0)

-- And register it under the same name as used in your 3D model
dream:registerMaterial(material, "yourMaterial")

Now, since materials are quite common things there is a recommended shortcut:

-- Load all materials in a directory
dream:loadMaterialLibrary("materials")

Where the structure of that directory can be recursive, and can contain three possible material types:

The first two variants should contain following textures:

The material description file is a lua file returning a table defining the values as in 3DreamEngine/classes/material.lua

Check out the examples/Tavern/materials/ demo for an example.

Alpha

By default materials are solid, and the alpha channel is ignored. To render alpha three approaches are available:

Objects

An object is a container for all kind of data, including other objects. It may store your scene graph, a character, animations, collision data, basically everything.

-- Create a new object
yourObject = dream:loadObject(path)

-- Print that object to get an better overview on what it contains.
yourObject:print()

-- Transform that object
yourObject:resetTransform()
yourObject:translate(1, 2, 3)

-- Create an identical object, which can have a different transform
yourSecondObject = yourObject:newInstance()

-- Clones an object, which can be modified (material, skeleton, ...) without affecting the original
yourSecondObject = yourObject:clone()

Camera

A camera is another class created using dream:newCamera(), but it is recommended to use the default one at dream.camera, if no multi-camera scene is required.

A first person camera could look something like this, which resets the camera, moves it to a player, rotates yaw and then pitch:

dream.camera:resetTransform()
dream.camera:translate(player.x, player.y, player.z)
dream.camera:rotateY(player.ry)
dream.camera:rotateX(player.rx)

Render loop

Your draw/update code should look something like that:

function love.draw()
	--clear render queue, lights, ...
	dream:prepare()
	
	--setup lights
	dream:addLight(...)
	
	--draw your stuff
	dream:draw(yourObject)
	dream:draw(yourSecondObject)
	
	--render
	dream:present()
end

function love.update(dt)
	--update resource loader
	dream:update()
end

function love.resize()
	--handle resizing
	dream:resize()
end

Advanced Object loading

For everything beyond loading and rendering a singular object, let’s take a look at advanced features:

Scenes

local yourScene = dream:loadScene(path) 

A scene is an object, but restructured to have similar named objects grouped. Imagine this object, loaded with loadObject():

└─test
  └─objects
    ├─Lamp
    │ └─meshes
    │   └─mesh (96 vertices)
    ├─Lamp
    │ └─meshes
    │   └─mesh (96 vertices)
    ├─Lamp
    │ └─physics
    │   └─mesh
    ├─Lamp
    │ └─meshes
    │   └─mesh (96 vertices)
    └─Crate
      └─meshes
        └─mesh (96 vertices)

It consists of two objects, a Lamp and a Crate. The Lamp has a collider, two LODs and a light source. Now, since Blender flattens any hierarchy we have a bit of a mess. You can not transform the entire lamp, since it’s mixed with the crate. Now take a look at the object loaded using dream:loadScene():

└─test
  └─objects
    ├─Lamp
    │ └─objects
    │   ├─Lamp
    │   │ └─meshes
    │   │   └─mesh (96 vertices)
    │   ├─Lamp
    │   │ └─meshes
    │   │   └─mesh (96 vertices)
    │   ├─Lamp
    │   │ └─meshes
    │   │   └─mesh (96 vertices)
    │   └─Lamp
    │     └─physics
    │       └─mesh
    └─Crate
      └─objects
        └─Crate
          └─meshes
            └─mesh (96 vertices)

We now have two objects, one for Lamp and one for Crate. Objects are merged based on their name, excluding any tags (explained later) and postfixes (seperated using a dot).

Tags

Sometimes it is required to tag certain objects, e.g. mark them to use a specific LOD, or to be colliders, or to represent reflection gloves etc. Names are therefore parsed for tags in the format {TAG:VALUE_|TAG_}name{.postfix}. E.g. an LOD, which is also used as a collider could be called LOD:0_PHYSICS_yourObject.001.

Refer to the function documentation for a list of tags: `3DreamEngine/loader.lua”.

Linked Objects

If you use an object in several scenes, you might want to consider creating an object library, then reuse that object in your respective scenes.

This can be especially useful if your object consists of LODs, collisions, maybe light sources etc and copying them around is tedious.

-- Load a scene and register all objects as library objects, with given prefix.
dream:loadLibrary(path, args, prefix)

-- Or register a specific objects under a name
dream:registerObject(object, name)

Now tag your reference object, e.g. LINK:yourObject_yourReferenceObject. That will replace that object during dream:loadObject() with the full library entry. Which is usually faster, requires less memory on disk and allows for easier reuse and updating.

Resource Loader

3Dream uses a resource manager to simplify texture loading.

dream:getImage(path, force) returns the image or false, if the image is not yet loaded. If not loaded, a request is made. Force enforces an immediate load.

dream:getImagePath(path) returns the path to the best image, e.g. test may return test.png if such image is given. It is therefore not necessary to provide extension in the material description files, getImage() function or any other setTexture() function you may encounter.

Pipeline

3Dream uses a forward renderer and optional hard coded post effects. All draw calls are batched and rendered for all required reflections and shadows after calling dream:present().