2018-02-10 22:37:27 +01:00
|
|
|
package armory.renderpath;
|
|
|
|
|
|
|
|
import iron.RenderPath;
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
import iron.object.LightObject;
|
2018-02-10 22:37:27 +01:00
|
|
|
|
|
|
|
class Inc {
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
static var path: RenderPath;
|
2018-10-14 20:21:19 +02:00
|
|
|
public static var superSample = 1.0;
|
2018-02-16 17:12:19 +01:00
|
|
|
|
2018-12-10 23:29:04 +01:00
|
|
|
static var pointIndex = 0;
|
|
|
|
static var spotIndex = 0;
|
2019-01-15 20:49:03 +01:00
|
|
|
static var lastFrame = -1;
|
2018-12-10 23:29:04 +01:00
|
|
|
|
2019-04-06 18:52:21 +02:00
|
|
|
#if (rp_voxelao && arm_config)
|
2019-03-14 10:55:15 +01:00
|
|
|
static var voxelsCreated = false;
|
|
|
|
#end
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static function init(_path: RenderPath) {
|
2018-02-10 22:37:27 +01:00
|
|
|
path = _path;
|
2018-11-08 20:56:03 +01:00
|
|
|
|
|
|
|
#if arm_config
|
|
|
|
var config = armory.data.Config.raw;
|
2018-12-10 00:02:40 +01:00
|
|
|
for (l in iron.Scene.active.lights) {
|
|
|
|
l.data.raw.shadowmap_size = l.data.raw.type == "sun" ?
|
|
|
|
config.rp_shadowmap_cascade :
|
|
|
|
config.rp_shadowmap_cube;
|
|
|
|
}
|
2018-11-08 20:56:03 +01:00
|
|
|
superSample = config.rp_supersample;
|
2019-02-28 23:16:33 +01:00
|
|
|
#else
|
2019-12-19 23:54:08 +01:00
|
|
|
|
2019-02-28 23:16:33 +01:00
|
|
|
#if (rp_supersampling == 1.5)
|
|
|
|
superSample = 1.5;
|
|
|
|
#elseif (rp_supersampling == 2)
|
|
|
|
superSample = 2.0;
|
|
|
|
#elseif (rp_supersampling == 4)
|
|
|
|
superSample = 4.0;
|
|
|
|
#end
|
2019-12-19 23:54:08 +01:00
|
|
|
|
2018-11-08 20:56:03 +01:00
|
|
|
#end
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
|
|
|
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#if arm_shadowmap_atlas
|
|
|
|
public static function updatePointLightAtlasData(): Void {
|
|
|
|
var atlas = ShadowMapAtlas.shadowMapAtlases.get(ShadowMapAtlas.shadowMapAtlasName("point"));
|
|
|
|
if (atlas != null) {
|
|
|
|
if(LightObject.pointLightsData == null) {
|
|
|
|
LightObject.pointLightsData = new kha.arrays.Float32Array(
|
|
|
|
LightObject.maxLightsCluster * ShadowMapTile.tilesLightType("point") * 4 ); // max possible visible lights * 6 or 2 (faces) * 4 (xyzw)
|
|
|
|
}
|
|
|
|
|
|
|
|
var n = iron.Scene.active.lights.length > LightObject.maxLightsCluster ? LightObject.maxLightsCluster : iron.Scene.active.lights.length;
|
|
|
|
var i = 0;
|
|
|
|
var j = 0;
|
|
|
|
for (light in iron.Scene.active.lights) {
|
|
|
|
if (i >= n)
|
|
|
|
break;
|
|
|
|
if (LightObject.discardLightCulled(light)) continue;
|
|
|
|
if (light.data.raw.type == "point") {
|
2021-03-14 14:46:58 +01:00
|
|
|
if (!light.data.raw.cast_shadow) {
|
|
|
|
j += 4 * 6;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for(k in 0...6) {
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx
|
|
|
|
LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy
|
|
|
|
LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas
|
|
|
|
LightObject.pointLightsData[j + 3] = 0; // padding
|
|
|
|
j += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function bindShadowMapAtlas() {
|
|
|
|
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
|
|
|
path.bindTarget(atlas.target, atlas.target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static function getShadowMapAtlas(atlas:ShadowMapAtlas):String {
|
|
|
|
inline function createDepthTarget(name: String, size: Int) {
|
|
|
|
var t = new RenderTargetRaw();
|
|
|
|
t.name = name;
|
|
|
|
t.width = t.height = size;
|
|
|
|
t.format = "DEPTH16";
|
|
|
|
return path.createRenderTarget(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
var rt = path.renderTargets.get(atlas.target);
|
|
|
|
// Create shadowmap atlas texture on the fly and replace existing on size change
|
|
|
|
if (rt == null) {
|
|
|
|
rt = createDepthTarget(atlas.target, atlas.sizew);
|
|
|
|
}
|
|
|
|
else if (atlas.updateRenderTarget) {
|
|
|
|
atlas.updateRenderTarget = false;
|
|
|
|
// Resize shadow map
|
|
|
|
rt.unload();
|
|
|
|
rt = createDepthTarget(atlas.target, atlas.sizew);
|
|
|
|
}
|
|
|
|
return atlas.target;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function drawShadowMapAtlas() {
|
|
|
|
#if rp_shadowmap
|
|
|
|
#if rp_probes
|
|
|
|
// Share shadow map with probe
|
|
|
|
if (lastFrame == RenderPath.active.frame)
|
|
|
|
return;
|
|
|
|
lastFrame = RenderPath.active.frame;
|
|
|
|
#end
|
|
|
|
// add new lights to the atlases
|
2021-04-14 05:31:34 +02:00
|
|
|
#if arm_debug
|
2021-04-25 02:48:24 +02:00
|
|
|
beginShadowsLogicProfile();
|
2021-04-14 05:31:34 +02:00
|
|
|
// reset data on rejected lights
|
|
|
|
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
|
|
|
atlas.rejectedLights = [];
|
|
|
|
}
|
|
|
|
#end
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
for (light in iron.Scene.active.lights) {
|
|
|
|
if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0
|
|
|
|
&& light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) {
|
2021-04-07 15:02:12 +02:00
|
|
|
ShadowMapAtlas.addLight(light);
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// update point light data before rendering
|
|
|
|
updatePointLightAtlasData();
|
|
|
|
|
|
|
|
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
|
|
|
var tilesToRemove = [];
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
var tilesToChangeSize = [];
|
|
|
|
#end
|
|
|
|
|
|
|
|
var shadowmap = getShadowMapAtlas(atlas);
|
|
|
|
path.setTargetStream(shadowmap);
|
|
|
|
path.clearTarget(null, 1.0);
|
|
|
|
|
|
|
|
for (tile in atlas.activeTiles) {
|
|
|
|
if (tile.light == null || !tile.light.visible || tile.light.culledLight
|
|
|
|
|| !tile.light.data.raw.cast_shadow || tile.light.data.raw.strength == 0) {
|
|
|
|
tile.unlockLight = true;
|
|
|
|
tilesToRemove.push(tile);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
var newTileSize = atlas.getTileSize(tile.light.shadowMapScale);
|
|
|
|
if (newTileSize != tile.size) {
|
|
|
|
if (newTileSize == 0) {
|
|
|
|
tile.unlockLight = true;
|
|
|
|
tilesToRemove.push(tile);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// queue for size change
|
|
|
|
tile.newTileSize = newTileSize;
|
|
|
|
tilesToChangeSize.push(tile);
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
// set the tile offset for this tile and every linked tile to this one
|
|
|
|
var j = 0;
|
|
|
|
tile.forEachTileLinked(function (lTile) {
|
2021-04-18 15:32:36 +02:00
|
|
|
tile.light.tileOffsetX[j] = lTile.coordsX / atlas.sizew;
|
|
|
|
tile.light.tileOffsetY[j] = lTile.coordsY / atlas.sizew;
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
tile.light.tileScale[j] = lTile.size / atlas.sizew;
|
|
|
|
j++;
|
|
|
|
});
|
|
|
|
// set shadowmap size for uniform
|
|
|
|
tile.light.data.raw.shadowmap_size = atlas.sizew;
|
|
|
|
|
|
|
|
path.light = tile.light;
|
|
|
|
|
|
|
|
var face = 0;
|
|
|
|
var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type);
|
|
|
|
|
2021-04-25 02:48:24 +02:00
|
|
|
#if arm_debug
|
|
|
|
beginShadowsRenderProfile();
|
|
|
|
#end
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
tile.forEachTileLinked(function (lTile) {
|
|
|
|
if (faces > 1) {
|
|
|
|
#if arm_csm
|
|
|
|
switch (tile.light.data.raw.type) {
|
|
|
|
case "sun": tile.light.setCascade(iron.Scene.active.camera, face);
|
|
|
|
case "point": path.currentFace = face;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
path.currentFace = face;
|
|
|
|
#end
|
|
|
|
face++;
|
|
|
|
}
|
|
|
|
path.setCurrentViewportWithOffset(lTile.size, lTile.size, lTile.coordsX, lTile.coordsY);
|
|
|
|
|
|
|
|
path.drawMeshesStream("shadowmap");
|
|
|
|
});
|
2021-04-25 02:48:24 +02:00
|
|
|
#if arm_debug
|
|
|
|
endShadowsRenderProfile();
|
|
|
|
#end
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
|
|
|
|
path.currentFace = -1;
|
|
|
|
}
|
|
|
|
path.endStream();
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
for (tile in tilesToChangeSize) {
|
|
|
|
tilesToRemove.push(tile);
|
|
|
|
|
|
|
|
var newTile = ShadowMapTile.assignTiles(tile.light, atlas, tile);
|
|
|
|
if (newTile != null)
|
|
|
|
atlas.activeTiles.push(newTile);
|
|
|
|
}
|
|
|
|
// update point light data after changing size of tiles to avoid render issues
|
|
|
|
updatePointLightAtlasData();
|
|
|
|
#end
|
|
|
|
|
|
|
|
for (tile in tilesToRemove) {
|
|
|
|
atlas.activeTiles.remove(tile);
|
|
|
|
tile.freeTile();
|
|
|
|
}
|
|
|
|
}
|
2021-04-25 01:31:03 +02:00
|
|
|
#if arm_debug
|
2021-04-25 02:48:24 +02:00
|
|
|
endShadowsLogicProfile();
|
2021-04-25 01:31:03 +02:00
|
|
|
#end
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#end // rp_shadowmap
|
|
|
|
}
|
|
|
|
#else
|
2018-02-10 22:37:27 +01:00
|
|
|
public static function bindShadowMap() {
|
2018-11-22 11:08:03 +01:00
|
|
|
for (l in iron.Scene.active.lights) {
|
2020-01-20 01:49:30 +01:00
|
|
|
if (!l.visible || l.data.raw.type != "sun") continue;
|
2018-12-10 23:29:04 +01:00
|
|
|
var n = "shadowMap";
|
|
|
|
path.bindTarget(n, n);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (i in 0...pointIndex) {
|
2018-12-11 23:05:18 +01:00
|
|
|
var n = "shadowMapPoint[" + i + "]";
|
2018-12-10 23:29:04 +01:00
|
|
|
path.bindTarget(n, n);
|
|
|
|
}
|
|
|
|
for (i in 0...spotIndex) {
|
2018-12-11 23:05:18 +01:00
|
|
|
var n = "shadowMapSpot[" + i + "]";
|
2018-11-22 11:08:03 +01:00
|
|
|
path.bindTarget(n, n);
|
|
|
|
}
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
|
|
|
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
static function shadowMapName(light: LightObject): String {
|
|
|
|
switch (light.data.raw.type) {
|
|
|
|
case "sun":
|
|
|
|
return "shadowMap";
|
|
|
|
case "point":
|
|
|
|
return "shadowMapPoint[" + pointIndex + "]";
|
|
|
|
default:
|
|
|
|
return "shadowMapSpot[" + spotIndex + "]";
|
|
|
|
}
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
static function getShadowMap(l: iron.object.LightObject): String {
|
2018-11-22 11:08:03 +01:00
|
|
|
var target = shadowMapName(l);
|
2018-02-10 22:37:27 +01:00
|
|
|
var rt = path.renderTargets.get(target);
|
|
|
|
// Create shadowmap on the fly
|
|
|
|
if (rt == null) {
|
2018-11-22 11:08:03 +01:00
|
|
|
if (path.light.data.raw.shadowmap_cube) {
|
2018-02-10 22:37:27 +01:00
|
|
|
// Cubemap size
|
2018-12-28 13:31:43 +01:00
|
|
|
var size = path.light.data.raw.shadowmap_size;
|
2018-02-10 22:37:27 +01:00
|
|
|
var t = new RenderTargetRaw();
|
|
|
|
t.name = target;
|
|
|
|
t.width = size;
|
|
|
|
t.height = size;
|
|
|
|
t.format = "DEPTH16";
|
|
|
|
t.is_cubemap = true;
|
|
|
|
rt = path.createRenderTarget(t);
|
|
|
|
}
|
|
|
|
else { // Non-cube sm
|
2018-11-22 11:08:03 +01:00
|
|
|
var sizew = path.light.data.raw.shadowmap_size;
|
2018-02-10 22:37:27 +01:00
|
|
|
var sizeh = sizew;
|
|
|
|
#if arm_csm // Cascades - atlas on x axis
|
2018-12-11 23:05:18 +01:00
|
|
|
if (l.data.raw.type == "sun") {
|
|
|
|
sizew = sizew * iron.object.LightObject.cascadeCount;
|
|
|
|
}
|
2018-02-10 22:37:27 +01:00
|
|
|
#end
|
|
|
|
var t = new RenderTargetRaw();
|
|
|
|
t.name = target;
|
|
|
|
t.width = sizew;
|
|
|
|
t.height = sizeh;
|
|
|
|
t.format = "DEPTH16";
|
|
|
|
rt = path.createRenderTarget(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target;
|
|
|
|
}
|
2018-11-22 11:08:03 +01:00
|
|
|
|
|
|
|
public static function drawShadowMap() {
|
|
|
|
#if (rp_shadowmap)
|
2019-01-15 20:49:03 +01:00
|
|
|
|
|
|
|
#if rp_probes
|
|
|
|
// Share shadow map with probe
|
|
|
|
if (lastFrame == RenderPath.active.frame) return;
|
|
|
|
lastFrame = RenderPath.active.frame;
|
|
|
|
#end
|
|
|
|
|
2018-12-10 23:29:04 +01:00
|
|
|
pointIndex = 0;
|
|
|
|
spotIndex = 0;
|
2018-11-22 11:08:03 +01:00
|
|
|
for (l in iron.Scene.active.lights) {
|
2020-01-20 01:49:30 +01:00
|
|
|
if (!l.visible) continue;
|
|
|
|
|
2018-11-22 11:08:03 +01:00
|
|
|
path.light = l;
|
2018-12-10 23:29:04 +01:00
|
|
|
var shadowmap = Inc.getShadowMap(l);
|
2018-11-22 11:08:03 +01:00
|
|
|
var faces = l.data.raw.shadowmap_cube ? 6 : 1;
|
|
|
|
for (i in 0...faces) {
|
|
|
|
if (faces > 1) path.currentFace = i;
|
2018-12-10 23:29:04 +01:00
|
|
|
path.setTarget(shadowmap);
|
2018-11-22 11:08:03 +01:00
|
|
|
path.clearTarget(null, 1.0);
|
2020-01-20 01:49:30 +01:00
|
|
|
if (l.data.raw.cast_shadow) {
|
|
|
|
path.drawMeshes("shadowmap");
|
|
|
|
}
|
2018-11-22 11:08:03 +01:00
|
|
|
}
|
|
|
|
path.currentFace = -1;
|
2018-12-10 23:29:04 +01:00
|
|
|
|
|
|
|
if (l.data.raw.type == "point") pointIndex++;
|
2019-01-27 23:48:54 +01:00
|
|
|
else if (l.data.raw.type == "spot" || l.data.raw.type == "area") spotIndex++;
|
2018-11-22 11:08:03 +01:00
|
|
|
}
|
2019-12-19 23:54:08 +01:00
|
|
|
|
2018-11-22 11:08:03 +01:00
|
|
|
#end // rp_shadowmap
|
|
|
|
}
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#end
|
2019-12-19 23:54:08 +01:00
|
|
|
|
2018-08-29 23:01:23 +02:00
|
|
|
public static function applyConfig() {
|
|
|
|
#if arm_config
|
2018-10-14 20:21:19 +02:00
|
|
|
var config = armory.data.Config.raw;
|
2018-08-29 23:01:23 +02:00
|
|
|
// Resize shadow map
|
2018-11-22 11:08:03 +01:00
|
|
|
var l = path.light;
|
2018-12-10 00:02:40 +01:00
|
|
|
if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) {
|
2018-12-21 11:37:10 +01:00
|
|
|
l.data.raw.shadowmap_size = config.rp_shadowmap_cascade;
|
2018-10-14 20:21:19 +02:00
|
|
|
var rt = path.renderTargets.get("shadowMap");
|
|
|
|
if (rt != null) {
|
|
|
|
rt.unload();
|
|
|
|
path.renderTargets.remove("shadowMap");
|
|
|
|
}
|
2018-12-10 00:02:40 +01:00
|
|
|
}
|
|
|
|
else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) {
|
2018-12-21 11:37:10 +01:00
|
|
|
l.data.raw.shadowmap_size = config.rp_shadowmap_cube;
|
|
|
|
var rt = path.renderTargets.get("shadowMapCube");
|
2018-10-14 20:21:19 +02:00
|
|
|
if (rt != null) {
|
|
|
|
rt.unload();
|
|
|
|
path.renderTargets.remove("shadowMapCube");
|
|
|
|
}
|
2018-08-29 23:01:23 +02:00
|
|
|
}
|
2018-10-14 20:21:19 +02:00
|
|
|
if (superSample != config.rp_supersample) {
|
|
|
|
superSample = config.rp_supersample;
|
|
|
|
for (rt in path.renderTargets) {
|
|
|
|
if (rt.raw.width == 0 && rt.raw.scale != null) {
|
|
|
|
rt.raw.scale = getSuperSampling();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path.resize();
|
2018-08-29 23:01:23 +02:00
|
|
|
}
|
2019-03-14 10:55:15 +01:00
|
|
|
// Init voxels
|
2019-04-06 18:52:21 +02:00
|
|
|
#if rp_voxelao
|
2019-03-14 10:55:15 +01:00
|
|
|
if (!voxelsCreated) initGI();
|
2018-08-29 23:01:23 +02:00
|
|
|
#end
|
2019-03-14 10:55:15 +01:00
|
|
|
#end // arm_config
|
2018-08-29 23:01:23 +02:00
|
|
|
}
|
|
|
|
|
2018-02-10 22:37:27 +01:00
|
|
|
#if (rp_translucency)
|
|
|
|
public static function initTranslucency() {
|
|
|
|
path.createDepthBuffer("main", "DEPTH24");
|
|
|
|
|
|
|
|
var t = new RenderTargetRaw();
|
|
|
|
t.name = "accum";
|
|
|
|
t.width = 0;
|
|
|
|
t.height = 0;
|
|
|
|
t.displayp = getDisplayp();
|
|
|
|
t.format = "RGBA64";
|
2018-10-14 20:21:19 +02:00
|
|
|
t.scale = getSuperSampling();
|
2018-02-10 22:37:27 +01:00
|
|
|
t.depth_buffer = "main";
|
|
|
|
path.createRenderTarget(t);
|
|
|
|
|
|
|
|
var t = new RenderTargetRaw();
|
|
|
|
t.name = "revealage";
|
|
|
|
t.width = 0;
|
|
|
|
t.height = 0;
|
|
|
|
t.displayp = getDisplayp();
|
2018-05-17 22:47:00 +02:00
|
|
|
t.format = "R16";
|
2018-10-14 20:21:19 +02:00
|
|
|
t.scale = getSuperSampling();
|
2018-02-10 22:37:27 +01:00
|
|
|
t.depth_buffer = "main";
|
|
|
|
path.createRenderTarget(t);
|
|
|
|
|
|
|
|
path.loadShader("shader_datas/translucent_resolve/translucent_resolve");
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static function drawTranslucency(target: String) {
|
2018-02-10 22:37:27 +01:00
|
|
|
path.setTarget("accum");
|
|
|
|
path.clearTarget(0xff000000);
|
|
|
|
path.setTarget("revealage");
|
|
|
|
path.clearTarget(0xffffffff);
|
|
|
|
path.setTarget("accum", ["revealage"]);
|
|
|
|
#if rp_shadowmap
|
|
|
|
{
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#if arm_shadowmap_atlas
|
|
|
|
bindShadowMapAtlas();
|
|
|
|
#else
|
2018-02-10 22:37:27 +01:00
|
|
|
bindShadowMap();
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#end
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
|
|
|
#end
|
|
|
|
path.drawMeshes("translucent");
|
|
|
|
#if rp_render_to_texture
|
|
|
|
{
|
|
|
|
path.setTarget(target);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
{
|
|
|
|
path.setTarget("");
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
path.bindTarget("accum", "gbuffer0");
|
|
|
|
path.bindTarget("revealage", "gbuffer1");
|
|
|
|
path.drawShader("shader_datas/translucent_resolve/translucent_resolve");
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
|
2019-04-06 18:52:21 +02:00
|
|
|
#if rp_voxelao
|
2018-02-10 22:37:27 +01:00
|
|
|
public static function initGI(tname = "voxels") {
|
2019-03-14 10:55:15 +01:00
|
|
|
#if arm_config
|
|
|
|
var config = armory.data.Config.raw;
|
|
|
|
if (config.rp_gi != true || voxelsCreated) return;
|
|
|
|
voxelsCreated = true;
|
|
|
|
#end
|
|
|
|
|
2018-02-10 22:37:27 +01:00
|
|
|
var t = new RenderTargetRaw();
|
|
|
|
t.name = tname;
|
2019-04-06 18:52:21 +02:00
|
|
|
t.format = "R8";
|
2018-02-10 22:37:27 +01:00
|
|
|
var res = getVoxelRes();
|
|
|
|
var resZ = getVoxelResZ();
|
|
|
|
t.width = res;
|
|
|
|
t.height = res;
|
|
|
|
t.depth = Std.int(res * resZ);
|
|
|
|
t.is_image = true;
|
|
|
|
t.mipmaps = true;
|
|
|
|
path.createRenderTarget(t);
|
2019-03-14 10:55:15 +01:00
|
|
|
|
|
|
|
#if arm_voxelgi_temporal
|
|
|
|
{
|
|
|
|
var tB = new RenderTargetRaw();
|
|
|
|
tB.name = t.name + "B";
|
|
|
|
tB.format = t.format;
|
|
|
|
tB.width = t.width;
|
|
|
|
tB.height = t.height;
|
|
|
|
tB.depth = t.depth;
|
|
|
|
tB.is_image = t.is_image;
|
|
|
|
tB.mipmaps = t.mipmaps;
|
|
|
|
path.createRenderTarget(tB);
|
|
|
|
}
|
|
|
|
#end
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
|
|
|
#end
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getCubeSize(): Int {
|
2018-12-10 00:02:40 +01:00
|
|
|
#if (rp_shadowmap_cube == 256)
|
|
|
|
return 256;
|
|
|
|
#elseif (rp_shadowmap_cube == 512)
|
|
|
|
return 512;
|
|
|
|
#elseif (rp_shadowmap_cube == 1024)
|
|
|
|
return 1024;
|
|
|
|
#elseif (rp_shadowmap_cube == 2048)
|
|
|
|
return 2048;
|
|
|
|
#elseif (rp_shadowmap_cube == 4096)
|
|
|
|
return 4096;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getCascadeSize(): Int {
|
2018-12-10 00:02:40 +01:00
|
|
|
#if (rp_shadowmap_cascade == 256)
|
|
|
|
return 256;
|
|
|
|
#elseif (rp_shadowmap_cascade == 512)
|
2018-02-10 22:37:27 +01:00
|
|
|
return 512;
|
2018-12-10 00:02:40 +01:00
|
|
|
#elseif (rp_shadowmap_cascade == 1024)
|
2018-02-10 22:37:27 +01:00
|
|
|
return 1024;
|
2018-12-10 00:02:40 +01:00
|
|
|
#elseif (rp_shadowmap_cascade == 2048)
|
2018-02-10 22:37:27 +01:00
|
|
|
return 2048;
|
2018-12-10 00:02:40 +01:00
|
|
|
#elseif (rp_shadowmap_cascade == 4096)
|
2018-02-10 22:37:27 +01:00
|
|
|
return 4096;
|
2019-01-02 15:33:30 +01:00
|
|
|
#elseif (rp_shadowmap_cascade == 8192)
|
|
|
|
return 8192;
|
|
|
|
#elseif (rp_shadowmap_cascade == 16384)
|
|
|
|
return 16384;
|
2018-02-10 22:37:27 +01:00
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getVoxelRes(): Int {
|
2018-02-10 22:37:27 +01:00
|
|
|
#if (rp_voxelgi_resolution == 512)
|
|
|
|
return 512;
|
|
|
|
#elseif (rp_voxelgi_resolution == 256)
|
|
|
|
return 256;
|
|
|
|
#elseif (rp_voxelgi_resolution == 128)
|
|
|
|
return 128;
|
|
|
|
#elseif (rp_voxelgi_resolution == 64)
|
|
|
|
return 64;
|
|
|
|
#elseif (rp_voxelgi_resolution == 32)
|
|
|
|
return 32;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getVoxelResZ(): Float {
|
2018-02-10 22:37:27 +01:00
|
|
|
#if (rp_voxelgi_resolution_z == 1.0)
|
|
|
|
return 1.0;
|
|
|
|
#elseif (rp_voxelgi_resolution_z == 0.5)
|
|
|
|
return 0.5;
|
|
|
|
#elseif (rp_voxelgi_resolution_z == 0.25)
|
|
|
|
return 0.25;
|
|
|
|
#else
|
|
|
|
return 0.0;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getSuperSampling(): Float {
|
2018-10-14 20:21:19 +02:00
|
|
|
return superSample;
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getHdrFormat(): String {
|
2018-02-10 22:37:27 +01:00
|
|
|
#if rp_hdr
|
|
|
|
return "RGBA64";
|
|
|
|
#else
|
|
|
|
return "RGBA32";
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:54:08 +01:00
|
|
|
public static inline function getDisplayp(): Null<Int> {
|
2018-04-20 00:56:54 +02:00
|
|
|
#if rp_resolution_filter // Custom resolution set
|
|
|
|
return Main.resolutionSize;
|
2018-02-10 22:37:27 +01:00
|
|
|
#else
|
|
|
|
return null;
|
|
|
|
#end
|
|
|
|
}
|
2021-04-25 01:31:03 +02:00
|
|
|
|
|
|
|
#if arm_debug
|
2021-04-25 02:48:24 +02:00
|
|
|
public static var shadowsLogicTime = 0.0;
|
|
|
|
public static var shadowsRenderTime = 0.0;
|
|
|
|
static var startShadowsLogicTime = 0.0;
|
|
|
|
static var startShadowsRenderTime = 0.0;
|
2021-04-25 01:31:03 +02:00
|
|
|
static var callBackSetup = false;
|
|
|
|
static function setupEndFrameCallback() {
|
|
|
|
if (!callBackSetup) {
|
|
|
|
callBackSetup = true;
|
|
|
|
iron.App.endFrameCallbacks.push(endFrame);
|
|
|
|
}
|
|
|
|
}
|
2021-04-25 02:48:24 +02:00
|
|
|
static function beginShadowsLogicProfile() { setupEndFrameCallback(); startShadowsLogicTime = kha.Scheduler.realTime(); }
|
|
|
|
static function beginShadowsRenderProfile() { startShadowsRenderTime = kha.Scheduler.realTime(); }
|
|
|
|
static function endShadowsLogicProfile() { shadowsLogicTime += kha.Scheduler.realTime() - startShadowsLogicTime - shadowsRenderTime; }
|
|
|
|
static function endShadowsRenderProfile() { shadowsRenderTime += kha.Scheduler.realTime() - startShadowsRenderTime; }
|
|
|
|
public static function endFrame() { shadowsLogicTime = 0; shadowsRenderTime = 0; }
|
2021-04-25 01:31:03 +02:00
|
|
|
#end
|
2018-02-10 22:37:27 +01:00
|
|
|
}
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
|
|
|
|
#if arm_shadowmap_atlas
|
|
|
|
class ShadowMapAtlas {
|
|
|
|
|
|
|
|
public var target: String;
|
|
|
|
public var baseTileSizeConst: Int;
|
|
|
|
public var maxAtlasSizeConst: Int;
|
|
|
|
|
|
|
|
public var sizew: Int;
|
|
|
|
public var sizeh: Int;
|
|
|
|
|
|
|
|
public var currTileOffset = 0;
|
|
|
|
public var tiles: Array<ShadowMapTile> = [];
|
|
|
|
public var activeTiles: Array<ShadowMapTile> = [];
|
|
|
|
public var depth = 1;
|
|
|
|
#if arm_shadowmap_atlas_lod
|
2021-04-29 00:08:30 +02:00
|
|
|
var tileSizes: Array<Int> = [];
|
|
|
|
var tileSizeFactor: Array<Float> = [];
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#end
|
|
|
|
public var updateRenderTarget = false;
|
|
|
|
public static var shadowMapAtlases:Map<String, ShadowMapAtlas> = new Map(); // map a shadowmap atlas to their light type
|
|
|
|
|
2021-04-14 05:31:34 +02:00
|
|
|
#if arm_debug
|
|
|
|
public var lightType: String;
|
|
|
|
public var rejectedLights: Array<LightObject> = [];
|
|
|
|
#end
|
|
|
|
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
function new(light: LightObject) {
|
|
|
|
|
|
|
|
var maxTileSize = shadowMapAtlasSize(light);
|
|
|
|
this.target = shadowMapAtlasName(light.data.raw.type);
|
|
|
|
this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize;
|
|
|
|
this.depth = getSubdivisions();
|
|
|
|
this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type);
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
2021-04-29 00:08:30 +02:00
|
|
|
computeTileSizes(maxTileSize, depth);
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
#end
|
|
|
|
|
2021-04-14 05:31:34 +02:00
|
|
|
#if arm_debug
|
|
|
|
#if arm_shadowmap_atlas_single_map
|
|
|
|
this.lightType = "any";
|
|
|
|
#else
|
|
|
|
this.lightType = light.data.raw.type;
|
|
|
|
#end
|
|
|
|
#end
|
|
|
|
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a light to an atlas. The atlas is decided based on the type of the light
|
|
|
|
* @param light of type LightObject to be added to an yatlas
|
|
|
|
* @return if the light was added succesfully
|
|
|
|
*/
|
2021-04-07 15:02:12 +02:00
|
|
|
public static function addLight(light: LightObject) {
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
var atlasName = shadowMapAtlasName(light.data.raw.type);
|
|
|
|
var atlas = shadowMapAtlases.get(atlasName);
|
|
|
|
if (atlas == null) {
|
|
|
|
// create a new atlas
|
|
|
|
atlas = new ShadowMapAtlas(light);
|
|
|
|
shadowMapAtlases.set(atlasName, atlas);
|
|
|
|
}
|
|
|
|
|
|
|
|
// find a free tile for this light
|
|
|
|
var mainTile = ShadowMapTile.assignTiles(light, atlas, null);
|
2021-04-14 05:31:34 +02:00
|
|
|
if (mainTile == null) {
|
|
|
|
#if arm_debug
|
|
|
|
if (!atlas.rejectedLights.contains(light))
|
|
|
|
atlas.rejectedLights.push(light);
|
|
|
|
#end
|
2021-04-07 15:02:12 +02:00
|
|
|
return;
|
2021-04-14 05:31:34 +02:00
|
|
|
}
|
2021-04-07 15:02:12 +02:00
|
|
|
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
atlas.activeTiles.push(mainTile);
|
2021-04-07 14:40:41 +02:00
|
|
|
// notify the tile on light remove
|
|
|
|
light.tileNotifyOnRemove = mainTile.notifyOnLightRemove;
|
|
|
|
// notify atlas when this tile is freed
|
|
|
|
mainTile.notifyOnFree = atlas.freeActiveTile;
|
2021-04-07 15:02:12 +02:00
|
|
|
// "lock" light to make sure it's not eligible to be added again
|
|
|
|
light.lightInAtlas = true;
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline function shadowMapAtlasSize(light:LightObject):Int {
|
|
|
|
// TODO: this can break because we are changing shadowmap_size elsewhere.
|
|
|
|
return light.data.raw.shadowmap_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getTileSize(shadowMapScale: Float): Int {
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
// find the first scale factor that is smaller to the shadowmap scale, and then return the previous one.
|
|
|
|
var i = 0;
|
|
|
|
for (sizeFactor in tileSizeFactor) {
|
|
|
|
if (sizeFactor < shadowMapScale) break;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return tileSizes[i - 1];
|
|
|
|
#else
|
|
|
|
return this.baseTileSizeConst;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
2021-04-29 00:08:30 +02:00
|
|
|
function computeTileSizes(maxTileSize: Int, depth: Int): Void {
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
// find the highest value based on the calculation done in the cluster code
|
|
|
|
var base = LightObject.zToShadowMapScale(0, 16);
|
|
|
|
var subdiv = base / depth;
|
|
|
|
for(i in 0...depth){
|
2021-04-29 00:08:30 +02:00
|
|
|
this.tileSizes.push(Std.int(maxTileSize / Math.pow(2, i)));
|
|
|
|
this.tileSizeFactor.push(base);
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
base -= subdiv;
|
|
|
|
}
|
2021-04-29 00:08:30 +02:00
|
|
|
this.tileSizes.push(0);
|
|
|
|
this.tileSizeFactor.push(0.0);
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
}
|
|
|
|
#end
|
|
|
|
|
|
|
|
public inline function atlasLimitReached() {
|
|
|
|
// asume square atlas
|
|
|
|
return (currTileOffset + 1) * baseTileSizeConst > maxAtlasSizeConst;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static inline function shadowMapAtlasName(type: String): String {
|
|
|
|
#if arm_shadowmap_atlas_single_map
|
|
|
|
return "shadowMapAtlas";
|
|
|
|
#else
|
|
|
|
switch (type) {
|
|
|
|
case "point":
|
|
|
|
return "shadowMapAtlasPoint";
|
|
|
|
case "sun":
|
|
|
|
return "shadowMapAtlasSun";
|
|
|
|
default:
|
|
|
|
return "shadowMapAtlasSpot";
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
public static inline function getSubdivisions(): Int {
|
|
|
|
#if (rp_shadowmap_atlas_lod_subdivisions == 2)
|
|
|
|
return 2;
|
|
|
|
#elseif (rp_shadowmap_atlas_lod_subdivisions == 3)
|
|
|
|
return 3;
|
|
|
|
#elseif (rp_shadowmap_atlas_lod_subdivisions == 4)
|
|
|
|
return 4;
|
|
|
|
#elseif (rp_shadowmap_atlas_lod_subdivisions == 5)
|
|
|
|
return 5;
|
|
|
|
#elseif (rp_shadowmap_atlas_lod_subdivisions == 6)
|
|
|
|
return 6;
|
|
|
|
#elseif (rp_shadowmap_atlas_lod_subdivisions == 7)
|
|
|
|
return 7;
|
|
|
|
#elseif (rp_shadowmap_atlas_lod_subdivisions == 8)
|
|
|
|
return 8;
|
|
|
|
#elseif (!arm_shadowmap_atlas_lod)
|
|
|
|
return 1;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
public static inline function getMaxAtlasSize(type: String): Int {
|
|
|
|
#if arm_shadowmap_atlas_single_map
|
2021-07-06 15:39:07 +02:00
|
|
|
#if (rp_shadowmap_atlas_max_size == 512)
|
|
|
|
return 512;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 1024)
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
return 1024;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 2048)
|
|
|
|
return 2048;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 4096)
|
|
|
|
return 4096;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 8192)
|
|
|
|
return 8192;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 16384)
|
|
|
|
return 16384;
|
|
|
|
#end
|
|
|
|
#else
|
|
|
|
switch (type) {
|
|
|
|
case "point": {
|
|
|
|
#if (rp_shadowmap_atlas_max_size_point == 1024)
|
|
|
|
return 1024;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_point == 2048)
|
|
|
|
return 2048;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_point == 4096)
|
|
|
|
return 4096;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_point == 8192)
|
|
|
|
return 8192;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_point == 16384)
|
|
|
|
return 16384;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
case "spot": {
|
2021-07-06 15:39:07 +02:00
|
|
|
#if (rp_shadowmap_atlas_max_size_spot == 512)
|
|
|
|
return 512;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_spot == 1024)
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
return 1024;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_spot == 2048)
|
|
|
|
return 2048;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_spot == 4096)
|
|
|
|
return 4096;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_spot == 8192)
|
|
|
|
return 8192;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_spot == 16384)
|
|
|
|
return 16384;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
case "sun": {
|
2021-07-06 15:39:07 +02:00
|
|
|
#if (rp_shadowmap_atlas_max_size_sun == 512)
|
|
|
|
return 512;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_sun == 1024)
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
return 1024;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_sun == 2048)
|
|
|
|
return 2048;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_sun == 4096)
|
|
|
|
return 4096;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_sun == 8192)
|
|
|
|
return 8192;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size_sun == 16384)
|
|
|
|
return 16384;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
default: {
|
2021-07-06 15:39:07 +02:00
|
|
|
#if (rp_shadowmap_atlas_max_size == 512)
|
|
|
|
return 512;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 1024)
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
return 1024;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 2048)
|
|
|
|
return 2048;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 4096)
|
|
|
|
return 4096;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 8192)
|
|
|
|
return 8192;
|
|
|
|
#elseif (rp_shadowmap_atlas_max_size == 16384)
|
|
|
|
return 16384;
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
}
|
2021-04-07 14:40:41 +02:00
|
|
|
|
|
|
|
function freeActiveTile(tile: ShadowMapTile) {
|
|
|
|
activeTiles.remove(tile);
|
|
|
|
}
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class ShadowMapTile {
|
|
|
|
|
|
|
|
public var light:Null<LightObject> = null;
|
|
|
|
public var coordsX:Int;
|
|
|
|
public var coordsY:Int;
|
|
|
|
public var size:Int;
|
|
|
|
public var tiles:Array<ShadowMapTile> = [];
|
|
|
|
public var linkedTile:ShadowMapTile = null;
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
public var parentTile: ShadowMapTile = null;
|
|
|
|
public var activeSubTiles: Int = 0;
|
|
|
|
public var newTileSize: Int = -1;
|
|
|
|
|
|
|
|
static var tilePattern = [[0, 0], [1, 0], [0, 1], [1, 1]];
|
|
|
|
#end
|
|
|
|
|
|
|
|
function new(coordsX: Int, coordsY: Int, size: Int) {
|
|
|
|
this.coordsX = coordsX;
|
|
|
|
this.coordsY = coordsY;
|
|
|
|
this.size = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function assignTiles(light: LightObject, atlas: ShadowMapAtlas, oldTile: ShadowMapTile): ShadowMapTile {
|
|
|
|
var tileSize = 0;
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
if (oldTile != null && oldTile.newTileSize != -1) {
|
|
|
|
// reuse tilesize instead of computing it again
|
|
|
|
tileSize = oldTile.newTileSize;
|
|
|
|
oldTile.newTileSize = -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#end
|
|
|
|
tileSize = atlas.getTileSize(light.shadowMapScale);
|
|
|
|
|
|
|
|
if (tileSize == 0)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
var tiles = [];
|
|
|
|
tiles = findCreateTiles(light, oldTile, atlas, tilesLightType(light.data.raw.type), tileSize);
|
|
|
|
|
|
|
|
// lock new tiles with light
|
|
|
|
for (tile in tiles)
|
|
|
|
tile.lockTile(light);
|
|
|
|
|
|
|
|
return linkTiles(tiles);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline function linkTiles(tiles: Array<ShadowMapTile>): ShadowMapTile {
|
|
|
|
if (tiles.length > 1) {
|
|
|
|
var linkedTile = tiles[0];
|
|
|
|
for (i in 1...tiles.length) {
|
|
|
|
linkedTile.linkedTile = tiles[i];
|
|
|
|
linkedTile = tiles[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tiles[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array<ShadowMapTile> {
|
|
|
|
var tilesFound: Array<ShadowMapTile> = [];
|
|
|
|
|
|
|
|
while (tilesFound.length < tilesPerLightType) {
|
|
|
|
findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound);
|
|
|
|
|
|
|
|
if (tilesFound.length < tilesPerLightType) {
|
|
|
|
tilesFound = []; // empty tilesFound
|
|
|
|
// skip creating more tiles if limit has been reached
|
|
|
|
if (atlas.atlasLimitReached())
|
|
|
|
break;
|
|
|
|
|
|
|
|
createTiles(atlas.tiles, atlas.baseTileSizeConst, atlas.depth, atlas.currTileOffset, atlas.currTileOffset);
|
|
|
|
atlas.currTileOffset++;
|
|
|
|
// update texture to accomodate new size
|
|
|
|
atlas.updateRenderTarget = true;
|
|
|
|
atlas.sizew = atlas.sizeh = atlas.currTileOffset * atlas.baseTileSizeConst;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tilesFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static function findTiles(light:LightObject, oldTile: ShadowMapTile,
|
|
|
|
tiles: Array<ShadowMapTile>, size: Int, tilesCount: Int, tilesFound: Array<ShadowMapTile>): Void {
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
if (oldTile != null) {
|
|
|
|
// reuse children tiles
|
|
|
|
if (size < oldTile.size) {
|
|
|
|
oldTile.forEachTileLinked(function(lTile) {
|
|
|
|
var childTile = findFreeChildTile(lTile, size);
|
|
|
|
tilesFound.push(childTile);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// reuse parent tiles
|
|
|
|
else {
|
|
|
|
oldTile.forEachTileLinked(function(lTile) {
|
|
|
|
// find out if parents tiles are not occupied
|
|
|
|
var parentTile = findFreeParentTile(lTile, size);
|
|
|
|
// if parent is free, add it to found tiles
|
|
|
|
if (parentTile != null)
|
|
|
|
tilesFound.push(parentTile);
|
|
|
|
});
|
|
|
|
if (tilesFound.length < tilesCount) {
|
|
|
|
// find naively the rest of the tiles that couldn't be reused
|
|
|
|
findTilesNaive(light, tiles, size, tilesCount, tilesFound);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#end
|
|
|
|
findTilesNaive(light, tiles, size, tilesCount, tilesFound);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
static inline function findFreeChildTile(tile: ShadowMapTile, size: Int): ShadowMapTile {
|
|
|
|
var childrenTile = tile;
|
|
|
|
while (size < childrenTile.size) {
|
|
|
|
childrenTile = childrenTile.tiles[0];
|
|
|
|
}
|
|
|
|
return childrenTile;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline function findFreeParentTile(tile: ShadowMapTile, size: Int): ShadowMapTile {
|
|
|
|
var parentTile = tile;
|
|
|
|
while (size > parentTile.size) {
|
|
|
|
parentTile = parentTile.parentTile;
|
|
|
|
// stop if parent tile is occupied
|
|
|
|
if (parentTile.activeSubTiles > 1) {
|
|
|
|
parentTile = null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parentTile;
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
|
|
|
|
static function findTilesNaive(light:LightObject, tiles: Array<ShadowMapTile>, size: Int, tilesCount: Int, tilesFound: Array<ShadowMapTile>): Void {
|
|
|
|
for (tile in tiles) {
|
|
|
|
if (tile.size == size) {
|
|
|
|
if (tile.light == null #if arm_shadowmap_atlas_lod && tile.activeSubTiles == 0 #end) {
|
|
|
|
tilesFound.push(tile);
|
|
|
|
// stop after finding enough tiles
|
|
|
|
if (tilesFound.length == tilesCount)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// skip over if end of the tree or tile is occupied
|
|
|
|
if (tile.tiles.length == 0 || tile.light != null)
|
|
|
|
continue;
|
|
|
|
findTilesNaive(light, tile.tiles, size, tilesCount, tilesFound);
|
|
|
|
// skip iterating over the rest of the tiles if found enough
|
|
|
|
if (tilesFound.length == tilesCount)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a basic tile and subdivide it if needed
|
|
|
|
public static function createTiles(tiles:Array<ShadowMapTile>, size:Int, depth: Int, baseX:Int, baseY:Int) {
|
|
|
|
var i = baseX;
|
|
|
|
var j = 0;
|
|
|
|
var lastTile = tiles.length;
|
|
|
|
// assume occupied tiles start from 1 line before the base x
|
|
|
|
var occupiedTiles = baseX - 1;
|
|
|
|
|
|
|
|
while (i >= 0) {
|
|
|
|
if (i <= occupiedTiles) { // avoid overriding tiles
|
|
|
|
j = baseY;
|
|
|
|
}
|
|
|
|
while (j <= baseY) {
|
|
|
|
// create base tile of max-size
|
|
|
|
tiles.push(new ShadowMapTile(size * i, size * j, size));
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
tiles[lastTile].tiles = subDivTile(tiles[lastTile], size, size * i, size * j, depth - 1);
|
|
|
|
#end
|
|
|
|
lastTile++;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
static function subDivTile(parent: ShadowMapTile, size: Int, baseCoordsX: Int, baseCoordsY: Int, depth: Int): Array<ShadowMapTile> {
|
|
|
|
var tileSize = Std.int(size / 2);
|
|
|
|
|
|
|
|
var tiles = [];
|
|
|
|
|
|
|
|
for (i in 0...4) {
|
|
|
|
var coordsX = baseCoordsX + tilePattern[i][0] * tileSize;
|
|
|
|
var coordsY = baseCoordsY + tilePattern[i][1] * tileSize;
|
|
|
|
|
|
|
|
var tile = new ShadowMapTile(coordsX, coordsY, tileSize);
|
|
|
|
tile.parentTile = parent;
|
|
|
|
|
|
|
|
if (depth > 1)
|
|
|
|
tile.tiles = subDivTile(tile, tileSize, coordsX, coordsY, depth - 1);
|
|
|
|
tiles.push(tile);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tiles;
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
|
|
|
|
public static inline function tilesLightType(type: String): Int {
|
|
|
|
switch (type) {
|
|
|
|
case "sun":
|
|
|
|
return LightObject.cascadeCount;
|
|
|
|
case "point":
|
|
|
|
return 6;
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-07 14:40:41 +02:00
|
|
|
public function notifyOnLightRemove() {
|
|
|
|
unlockLight = true;
|
|
|
|
freeTile();
|
|
|
|
}
|
|
|
|
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
inline function lockTile(light: LightObject): Void {
|
|
|
|
if (this.light != null)
|
|
|
|
return;
|
|
|
|
this.light = light;
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
// update the count of used tiles for parents
|
|
|
|
this.forEachParentTile(function (pTile) {
|
|
|
|
pTile.activeSubTiles++;
|
|
|
|
});
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
public var unlockLight: Bool = false;
|
2021-04-07 14:40:41 +02:00
|
|
|
public var notifyOnFree: ShadowMapTile -> Void;
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
|
|
|
|
public function freeTile(): Void {
|
|
|
|
// prevent duplicates
|
|
|
|
if (light != null && unlockLight) {
|
|
|
|
light.lightInAtlas = false;
|
|
|
|
unlockLight = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var linkedTile = this;
|
|
|
|
var tempTile = this;
|
|
|
|
while (linkedTile != null) {
|
|
|
|
linkedTile.light = null;
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
// update the count of used tiles for parents
|
|
|
|
linkedTile.forEachParentTile(function (pTile) {
|
|
|
|
if (pTile.activeSubTiles > 0)
|
|
|
|
pTile.activeSubTiles--;
|
|
|
|
});
|
|
|
|
#end
|
|
|
|
|
|
|
|
linkedTile = linkedTile.linkedTile;
|
|
|
|
// unlink linked tiles
|
|
|
|
tempTile.linkedTile = null;
|
|
|
|
tempTile = linkedTile;
|
|
|
|
}
|
2021-04-07 14:40:41 +02:00
|
|
|
// notify atlas that this tile has been freed
|
|
|
|
if (notifyOnFree != null) {
|
|
|
|
notifyOnFree(this);
|
|
|
|
notifyOnFree = null;
|
|
|
|
}
|
Add support for shadow map atlasing
With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.
The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().
Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.
the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public inline function forEachTileLinked(action: ShadowMapTile->Void): Void {
|
|
|
|
var linkedTile = this;
|
|
|
|
while (linkedTile != null) {
|
|
|
|
action(linkedTile);
|
|
|
|
linkedTile = linkedTile.linkedTile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if arm_shadowmap_atlas_lod
|
|
|
|
public inline function forEachParentTile(action: ShadowMapTile->Void): Void {
|
|
|
|
var parentTile = this.parentTile;
|
|
|
|
while (parentTile != null) {
|
|
|
|
action(parentTile);
|
|
|
|
parentTile = parentTile.parentTile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
#end
|