armory/Shaders/volumetric_light/volumetric_light.frag.glsl

167 lines
4 KiB
Plaintext
Raw Permalink Normal View History

2016-08-31 00:46:10 +02:00
// http://sebastien.hillaire.free.fr/index.php?option=com_content&view=article&id=72&Itemid=106
#version 450
#include "compiled.inc"
2017-12-13 14:21:42 +01:00
#include "std/gbuffer.glsl"
#include "std/shadows.glsl"
2019-01-28 11:28:21 +01:00
#ifdef _Clusters
#include "std/clusters.glsl"
#endif
2016-09-08 14:08:31 +02:00
2016-08-31 00:46:10 +02:00
uniform sampler2D gbufferD;
uniform sampler2D snoise;
2019-01-28 11:28:21 +01:00
#ifdef _Clusters
uniform vec4 lightsArray[maxLights * 3];
2019-01-28 11:28:21 +01:00
#ifdef _Spot
uniform vec4 lightsArraySpot[maxLights];
#endif
uniform sampler2D clustersData;
uniform vec2 cameraPlane;
#endif
#ifdef _ShadowMap
#ifdef _SinglePoint
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[1];
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
uniform mat4 LWVPSpot[1];
2019-01-28 11:28:21 +01:00
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform vec2 lightProj;
#endif
#endif
#ifdef _Clusters
uniform samplerCubeShadow shadowMapPoint[4];
uniform vec2 lightProj;
#ifdef _Spot
uniform sampler2DShadow shadowMapSpot[4];
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
uniform mat4 LWVPSpot[maxLightsCluster];
2019-01-28 11:28:21 +01:00
#endif
#endif
#endif
#ifdef _Sun
uniform vec3 sunDir;
uniform vec3 sunCol;
#ifdef _ShadowMap
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSun;
#else
uniform sampler2DShadow shadowMapAtlas;
#endif
#else
2019-01-28 11:28:21 +01:00
uniform sampler2DShadow shadowMap;
#endif
2019-01-28 11:28:21 +01:00
uniform float shadowsBias;
#ifdef _CSM
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
#else
uniform mat4 LWVP;
#endif
#endif // _ShadowMap
#endif
#ifdef _SinglePoint // Fast path for single light
uniform vec3 pointPos;
uniform vec3 pointCol;
#ifdef _ShadowMap
uniform float pointBias;
#endif
#ifdef _Spot
uniform vec3 spotDir;
uniform vec2 spotData;
#endif
#endif
uniform vec2 cameraProj;
2017-07-08 14:12:18 +02:00
uniform vec3 eye;
2019-01-28 11:28:21 +01:00
uniform vec3 eyeLook;
2016-08-31 00:46:10 +02:00
2019-01-28 11:28:21 +01:00
in vec2 texCoord;
in vec3 viewRay;
2018-01-28 13:53:06 +01:00
out float fragColor;
2016-08-31 00:46:10 +02:00
2018-01-07 20:01:43 +01:00
const float tScat = 0.08;
2016-08-31 00:46:10 +02:00
const float tAbs = 0.0;
2018-01-06 00:33:33 +01:00
const float tExt = tScat + tAbs;
2018-01-07 20:01:43 +01:00
const float stepLen = 1.0 / volumSteps;
2018-01-06 00:33:33 +01:00
const float lighting = 0.4;
2016-08-31 00:46:10 +02:00
void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatteredLightAmount, float stepLenWorld, vec3 viewVecNorm) {
curPos += stepLenWorld * viewVecNorm;
2018-01-06 00:33:33 +01:00
const float density = 1.0;
2018-01-06 00:33:33 +01:00
float l1 = lighting * stepLenWorld * tScat * density;
2016-08-31 00:46:10 +02:00
curOpticalDepth *= exp(-tExt * stepLenWorld * density);
2020-10-21 16:18:30 +02:00
float visibility = 0.0;
2019-01-28 11:28:21 +01:00
#ifdef _Sun
#ifdef _CSM
2020-10-21 16:18:30 +02:00
mat4 LWVP = mat4(casData[4], casData[4 + 1], casData[4 + 2], casData[4 + 3]);
2019-01-28 11:28:21 +01:00
#endif
vec4 lPos = LWVP * vec4(curPos, 1.0);
lPos.xyz /= lPos.w;
visibility = texture(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun
#else
shadowMapAtlas
#endif
#else
shadowMap
#endif
, vec3(lPos.xy, lPos.z - shadowsBias));
2019-01-28 11:28:21 +01:00
#endif
2018-01-28 14:57:49 +01:00
2019-01-28 11:28:21 +01:00
#ifdef _SinglePoint
#ifdef _Spot
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
vec4 lPos = LWVPSpot[0] * vec4(curPos, 1.0);
visibility = shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, pointBias);
2019-01-28 11:28:21 +01:00
float spotEffect = dot(spotDir, normalize(pointPos - curPos)); // lightDir
if (spotEffect < spotData.x) { // x - cutoff, y - cutoff - exponent
visibility *= smoothstep(spotData.y, spotData.x, spotEffect);
2016-08-31 00:46:10 +02:00
}
2019-01-28 11:28:21 +01:00
#else
vec3 ld = pointPos - curPos;
visibility = PCFCube(shadowMapPoint[0], ld, -normalize(ld), pointBias, lightProj, vec3(0.0));
2019-01-28 11:28:21 +01:00
#endif
#endif
#ifdef _Clusters
#endif
2016-08-31 00:46:10 +02:00
scatteredLightAmount += curOpticalDepth * l1 * visibility;
}
void main() {
2018-12-06 15:23:08 +01:00
float pixelRayMarchNoise = textureLod(snoise, texCoord * 100, 0.0).r * 2.0 - 1.0;
2016-08-31 00:46:10 +02:00
2018-12-06 15:23:08 +01:00
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
2019-01-28 11:28:21 +01:00
vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
2016-08-31 00:46:10 +02:00
2019-01-28 11:28:21 +01:00
vec3 viewVec = p - eye;
2016-10-17 17:39:40 +02:00
float worldPosDist = length(viewVec);
vec3 viewVecNorm = viewVec / worldPosDist;
float startDepth = 0.1;
startDepth = min(worldPosDist, startDepth);
float endDepth = 20.0;
endDepth = min(worldPosDist, endDepth);
2017-07-08 14:12:18 +02:00
vec3 curPos = eye + viewVecNorm * startDepth;
2016-10-17 17:39:40 +02:00
float stepLenWorld = stepLen * (endDepth - startDepth);
float curOpticalDepth = exp(-tExt * stepLenWorld);
float scatteredLightAmount = 0.0;
2018-01-07 20:01:43 +01:00
curPos += stepLenWorld * viewVecNorm * pixelRayMarchNoise;
2016-10-17 17:39:40 +02:00
2018-01-06 00:33:33 +01:00
for (float l = stepLen; l < 0.99999; l += stepLen) { // Do not do the first and last steps
2016-10-17 17:39:40 +02:00
rayStep(curPos, curOpticalDepth, scatteredLightAmount, stepLenWorld, viewVecNorm);
2018-01-06 00:33:33 +01:00
}
2016-10-17 17:39:40 +02:00
2018-01-28 13:53:06 +01:00
fragColor = scatteredLightAmount * volumAirTurbidity;
2016-08-31 00:46:10 +02:00
}