801668a0c2
Added 512 option to Inc so it doesn't fails compilation because of missing option as explained here https://github.com/armory3d/armory/issues/2252#issue-937328497
1087 lines
29 KiB
Haxe
1087 lines
29 KiB
Haxe
package armory.renderpath;
|
|
|
|
import iron.RenderPath;
|
|
import iron.object.LightObject;
|
|
|
|
class Inc {
|
|
|
|
static var path: RenderPath;
|
|
public static var superSample = 1.0;
|
|
|
|
static var pointIndex = 0;
|
|
static var spotIndex = 0;
|
|
static var lastFrame = -1;
|
|
|
|
#if (rp_voxelao && arm_config)
|
|
static var voxelsCreated = false;
|
|
#end
|
|
|
|
public static function init(_path: RenderPath) {
|
|
path = _path;
|
|
|
|
#if arm_config
|
|
var config = armory.data.Config.raw;
|
|
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;
|
|
}
|
|
superSample = config.rp_supersample;
|
|
#else
|
|
|
|
#if (rp_supersampling == 1.5)
|
|
superSample = 1.5;
|
|
#elseif (rp_supersampling == 2)
|
|
superSample = 2.0;
|
|
#elseif (rp_supersampling == 4)
|
|
superSample = 4.0;
|
|
#end
|
|
|
|
#end
|
|
}
|
|
|
|
#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") {
|
|
if (!light.data.raw.cast_shadow) {
|
|
j += 4 * 6;
|
|
continue;
|
|
}
|
|
for(k in 0...6) {
|
|
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
|
|
#if arm_debug
|
|
beginShadowsLogicProfile();
|
|
// reset data on rejected lights
|
|
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
|
atlas.rejectedLights = [];
|
|
}
|
|
#end
|
|
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) {
|
|
ShadowMapAtlas.addLight(light);
|
|
}
|
|
}
|
|
// 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) {
|
|
tile.light.tileOffsetX[j] = lTile.coordsX / atlas.sizew;
|
|
tile.light.tileOffsetY[j] = lTile.coordsY / atlas.sizew;
|
|
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);
|
|
|
|
#if arm_debug
|
|
beginShadowsRenderProfile();
|
|
#end
|
|
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");
|
|
});
|
|
#if arm_debug
|
|
endShadowsRenderProfile();
|
|
#end
|
|
|
|
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();
|
|
}
|
|
}
|
|
#if arm_debug
|
|
endShadowsLogicProfile();
|
|
#end
|
|
#end // rp_shadowmap
|
|
}
|
|
#else
|
|
public static function bindShadowMap() {
|
|
for (l in iron.Scene.active.lights) {
|
|
if (!l.visible || l.data.raw.type != "sun") continue;
|
|
var n = "shadowMap";
|
|
path.bindTarget(n, n);
|
|
break;
|
|
}
|
|
for (i in 0...pointIndex) {
|
|
var n = "shadowMapPoint[" + i + "]";
|
|
path.bindTarget(n, n);
|
|
}
|
|
for (i in 0...spotIndex) {
|
|
var n = "shadowMapSpot[" + i + "]";
|
|
path.bindTarget(n, n);
|
|
}
|
|
}
|
|
|
|
static function shadowMapName(light: LightObject): String {
|
|
switch (light.data.raw.type) {
|
|
case "sun":
|
|
return "shadowMap";
|
|
case "point":
|
|
return "shadowMapPoint[" + pointIndex + "]";
|
|
default:
|
|
return "shadowMapSpot[" + spotIndex + "]";
|
|
}
|
|
}
|
|
|
|
static function getShadowMap(l: iron.object.LightObject): String {
|
|
var target = shadowMapName(l);
|
|
var rt = path.renderTargets.get(target);
|
|
// Create shadowmap on the fly
|
|
if (rt == null) {
|
|
if (path.light.data.raw.shadowmap_cube) {
|
|
// Cubemap size
|
|
var size = path.light.data.raw.shadowmap_size;
|
|
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
|
|
var sizew = path.light.data.raw.shadowmap_size;
|
|
var sizeh = sizew;
|
|
#if arm_csm // Cascades - atlas on x axis
|
|
if (l.data.raw.type == "sun") {
|
|
sizew = sizew * iron.object.LightObject.cascadeCount;
|
|
}
|
|
#end
|
|
var t = new RenderTargetRaw();
|
|
t.name = target;
|
|
t.width = sizew;
|
|
t.height = sizeh;
|
|
t.format = "DEPTH16";
|
|
rt = path.createRenderTarget(t);
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
public static function drawShadowMap() {
|
|
#if (rp_shadowmap)
|
|
|
|
#if rp_probes
|
|
// Share shadow map with probe
|
|
if (lastFrame == RenderPath.active.frame) return;
|
|
lastFrame = RenderPath.active.frame;
|
|
#end
|
|
|
|
pointIndex = 0;
|
|
spotIndex = 0;
|
|
for (l in iron.Scene.active.lights) {
|
|
if (!l.visible) continue;
|
|
|
|
path.light = l;
|
|
var shadowmap = Inc.getShadowMap(l);
|
|
var faces = l.data.raw.shadowmap_cube ? 6 : 1;
|
|
for (i in 0...faces) {
|
|
if (faces > 1) path.currentFace = i;
|
|
path.setTarget(shadowmap);
|
|
path.clearTarget(null, 1.0);
|
|
if (l.data.raw.cast_shadow) {
|
|
path.drawMeshes("shadowmap");
|
|
}
|
|
}
|
|
path.currentFace = -1;
|
|
|
|
if (l.data.raw.type == "point") pointIndex++;
|
|
else if (l.data.raw.type == "spot" || l.data.raw.type == "area") spotIndex++;
|
|
}
|
|
|
|
#end // rp_shadowmap
|
|
}
|
|
#end
|
|
|
|
public static function applyConfig() {
|
|
#if arm_config
|
|
var config = armory.data.Config.raw;
|
|
// Resize shadow map
|
|
var l = path.light;
|
|
if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) {
|
|
l.data.raw.shadowmap_size = config.rp_shadowmap_cascade;
|
|
var rt = path.renderTargets.get("shadowMap");
|
|
if (rt != null) {
|
|
rt.unload();
|
|
path.renderTargets.remove("shadowMap");
|
|
}
|
|
}
|
|
else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) {
|
|
l.data.raw.shadowmap_size = config.rp_shadowmap_cube;
|
|
var rt = path.renderTargets.get("shadowMapCube");
|
|
if (rt != null) {
|
|
rt.unload();
|
|
path.renderTargets.remove("shadowMapCube");
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
// Init voxels
|
|
#if rp_voxelao
|
|
if (!voxelsCreated) initGI();
|
|
#end
|
|
#end // arm_config
|
|
}
|
|
|
|
#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";
|
|
t.scale = getSuperSampling();
|
|
t.depth_buffer = "main";
|
|
path.createRenderTarget(t);
|
|
|
|
var t = new RenderTargetRaw();
|
|
t.name = "revealage";
|
|
t.width = 0;
|
|
t.height = 0;
|
|
t.displayp = getDisplayp();
|
|
t.format = "R16";
|
|
t.scale = getSuperSampling();
|
|
t.depth_buffer = "main";
|
|
path.createRenderTarget(t);
|
|
|
|
path.loadShader("shader_datas/translucent_resolve/translucent_resolve");
|
|
}
|
|
|
|
public static function drawTranslucency(target: String) {
|
|
path.setTarget("accum");
|
|
path.clearTarget(0xff000000);
|
|
path.setTarget("revealage");
|
|
path.clearTarget(0xffffffff);
|
|
path.setTarget("accum", ["revealage"]);
|
|
#if rp_shadowmap
|
|
{
|
|
#if arm_shadowmap_atlas
|
|
bindShadowMapAtlas();
|
|
#else
|
|
bindShadowMap();
|
|
#end
|
|
}
|
|
#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
|
|
|
|
#if rp_voxelao
|
|
public static function initGI(tname = "voxels") {
|
|
#if arm_config
|
|
var config = armory.data.Config.raw;
|
|
if (config.rp_gi != true || voxelsCreated) return;
|
|
voxelsCreated = true;
|
|
#end
|
|
|
|
var t = new RenderTargetRaw();
|
|
t.name = tname;
|
|
t.format = "R8";
|
|
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);
|
|
|
|
#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
|
|
}
|
|
#end
|
|
|
|
public static inline function getCubeSize(): Int {
|
|
#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
|
|
}
|
|
|
|
public static inline function getCascadeSize(): Int {
|
|
#if (rp_shadowmap_cascade == 256)
|
|
return 256;
|
|
#elseif (rp_shadowmap_cascade == 512)
|
|
return 512;
|
|
#elseif (rp_shadowmap_cascade == 1024)
|
|
return 1024;
|
|
#elseif (rp_shadowmap_cascade == 2048)
|
|
return 2048;
|
|
#elseif (rp_shadowmap_cascade == 4096)
|
|
return 4096;
|
|
#elseif (rp_shadowmap_cascade == 8192)
|
|
return 8192;
|
|
#elseif (rp_shadowmap_cascade == 16384)
|
|
return 16384;
|
|
#else
|
|
return 0;
|
|
#end
|
|
}
|
|
|
|
public static inline function getVoxelRes(): Int {
|
|
#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
|
|
}
|
|
|
|
public static inline function getVoxelResZ(): Float {
|
|
#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
|
|
}
|
|
|
|
public static inline function getSuperSampling(): Float {
|
|
return superSample;
|
|
}
|
|
|
|
public static inline function getHdrFormat(): String {
|
|
#if rp_hdr
|
|
return "RGBA64";
|
|
#else
|
|
return "RGBA32";
|
|
#end
|
|
}
|
|
|
|
public static inline function getDisplayp(): Null<Int> {
|
|
#if rp_resolution_filter // Custom resolution set
|
|
return Main.resolutionSize;
|
|
#else
|
|
return null;
|
|
#end
|
|
}
|
|
|
|
#if arm_debug
|
|
public static var shadowsLogicTime = 0.0;
|
|
public static var shadowsRenderTime = 0.0;
|
|
static var startShadowsLogicTime = 0.0;
|
|
static var startShadowsRenderTime = 0.0;
|
|
static var callBackSetup = false;
|
|
static function setupEndFrameCallback() {
|
|
if (!callBackSetup) {
|
|
callBackSetup = true;
|
|
iron.App.endFrameCallbacks.push(endFrame);
|
|
}
|
|
}
|
|
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; }
|
|
#end
|
|
}
|
|
|
|
#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
|
|
var tileSizes: Array<Int> = [];
|
|
var tileSizeFactor: Array<Float> = [];
|
|
#end
|
|
public var updateRenderTarget = false;
|
|
public static var shadowMapAtlases:Map<String, ShadowMapAtlas> = new Map(); // map a shadowmap atlas to their light type
|
|
|
|
#if arm_debug
|
|
public var lightType: String;
|
|
public var rejectedLights: Array<LightObject> = [];
|
|
#end
|
|
|
|
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
|
|
computeTileSizes(maxTileSize, depth);
|
|
#end
|
|
|
|
#if arm_debug
|
|
#if arm_shadowmap_atlas_single_map
|
|
this.lightType = "any";
|
|
#else
|
|
this.lightType = light.data.raw.type;
|
|
#end
|
|
#end
|
|
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public static function addLight(light: LightObject) {
|
|
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);
|
|
if (mainTile == null) {
|
|
#if arm_debug
|
|
if (!atlas.rejectedLights.contains(light))
|
|
atlas.rejectedLights.push(light);
|
|
#end
|
|
return;
|
|
}
|
|
|
|
atlas.activeTiles.push(mainTile);
|
|
// notify the tile on light remove
|
|
light.tileNotifyOnRemove = mainTile.notifyOnLightRemove;
|
|
// notify atlas when this tile is freed
|
|
mainTile.notifyOnFree = atlas.freeActiveTile;
|
|
// "lock" light to make sure it's not eligible to be added again
|
|
light.lightInAtlas = true;
|
|
}
|
|
|
|
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
|
|
function computeTileSizes(maxTileSize: Int, depth: Int): Void {
|
|
// 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){
|
|
this.tileSizes.push(Std.int(maxTileSize / Math.pow(2, i)));
|
|
this.tileSizeFactor.push(base);
|
|
base -= subdiv;
|
|
}
|
|
this.tileSizes.push(0);
|
|
this.tileSizeFactor.push(0.0);
|
|
}
|
|
#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
|
|
#if (rp_shadowmap_atlas_max_size == 512)
|
|
return 512;
|
|
#elseif (rp_shadowmap_atlas_max_size == 1024)
|
|
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": {
|
|
#if (rp_shadowmap_atlas_max_size_spot == 512)
|
|
return 512;
|
|
#elseif (rp_shadowmap_atlas_max_size_spot == 1024)
|
|
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": {
|
|
#if (rp_shadowmap_atlas_max_size_sun == 512)
|
|
return 512;
|
|
#elseif (rp_shadowmap_atlas_max_size_sun == 1024)
|
|
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: {
|
|
#if (rp_shadowmap_atlas_max_size == 512)
|
|
return 512;
|
|
#elseif (rp_shadowmap_atlas_max_size == 1024)
|
|
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
|
|
}
|
|
|
|
function freeActiveTile(tile: ShadowMapTile) {
|
|
activeTiles.remove(tile);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
public function notifyOnLightRemove() {
|
|
unlockLight = true;
|
|
freeTile();
|
|
}
|
|
|
|
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;
|
|
public var notifyOnFree: ShadowMapTile -> Void;
|
|
|
|
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;
|
|
}
|
|
// notify atlas that this tile has been freed
|
|
if (notifyOnFree != null) {
|
|
notifyOnFree(this);
|
|
notifyOnFree = null;
|
|
}
|
|
}
|
|
|
|
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
|