17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 01:07:28 +01:00

Add the optional Radius, ShadeSelf, CastShadows and SoftFalloff of FreeSpotLight and make FalloffAngle 360 a point light

This commit is contained in:
2026-10-28 16:18:03 +01:00
parent 413cebed9c
commit c4e23932bf
25 changed files with 327 additions and 72 deletions

View File

@@ -10,7 +10,7 @@
layout(location = 0) out vec4 out_light;
layout(location = 1) out vec4 out_albedo;
// closest distance (squared) the light of the lamp is computed for, as LOCAL_LIGHT_MIN_DISTANCE2 of light_common.glsl
// closest distance (squared) the light of the lamp is computed for, as lamp_falloff() of common.glsl takes it for a lamp without a radius
const float PROBE_LIGHT_MIN_DISTANCE2 = 0.01;
// the light of the sun on a diffuse surface, as SUN_DIFFUSE_SCALE and SUN_NDOTL_SHARPNESS of light_common.glsl
const float PROBE_SUN_SCALE = 1.5;

View File

@@ -149,6 +149,24 @@ struct light_s
vec4 headlight_weights;
};
// the falloff of the light of a lamp (SPOT, POINT) with the squared distance Distance2 from its middle. the lamps shine from a
// sphere, of the radius abs(ambient) (see gl::light_element, 0: a point): 1 / d^2 past it and 1 / r^2 within it, as a sphere light
// of blender gives it, or 1 / (d^2 + r^2) with its soft falloff (ambient below 0). a point is no brighter than 0.1 m from it
float lamp_falloff(light_s light, float distance2)
{
float radius = abs(light.ambient);
if (light.ambient < 0.0)
return 1.0 / max(distance2 + radius * radius, 1e-6);
return 1.0 / max(distance2, (radius > 0.0) ? radius * radius : 0.01);
}
// whether the lamp casts shadows: one without a shadow map (headlight_weights.x below 0) has y 1 when it casts none at all (no
// contact shadows either)
bool lamp_casts_shadows(light_s light)
{
return (light.headlight_weights.x >= 0.0) || (light.headlight_weights.y < 0.5);
}
// storage blocks are guaranteed only in fragment and compute shaders (GL 4.6 minimum for the other stages is 0)
#if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE)
layout(std430, binding = LIGHT_SSBO_BINDING) readonly buffer light_ssbo

View File

@@ -147,7 +147,7 @@ vec3 lamp_light(light_s light, vec3 position)
vec3 tolight = light.pos - position;
float distance2 = dot(tolight, tolight);
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, 0.01);
float atten = edge * edge * lamp_falloff(light, distance2);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
return light.color * (atten * light.intensity);
@@ -205,7 +205,8 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float
vec3 sum = vec3(0.0);
float closest = dot(lamp.pos - start, direction);
vec3 nearest = start + direction * closest;
float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width);
// the sphere of a lamp with a radius spreads its light as the width of the cell does
float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width + lamp.ambient * lamp.ambient);
float first = atan(-closest, reach);
float last = atan(span - closest, reach);
for (int i = 0; i < 2; i++)
@@ -214,7 +215,7 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float
vec3 tolight = cone.pos - position;
// the light without its falloff, which the integral holds
vec3 fromlamp = position - lamp.pos;
sum += lamp_light(cone, position) * max(dot(tolight, tolight), 0.01) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4);
sum += lamp_light(cone, position) / lamp_falloff(cone, dot(tolight, tolight)) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4);
}
return sum * (0.5 * (last - first) / (reach * max(span, 1e-4)));
}

View File

@@ -473,9 +473,6 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
return vec2(part.x, 1.0) * (atten * lightintensity);
}
// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit
const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01;
// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the
// map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise
float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps)
@@ -525,27 +522,65 @@ float local_light_shadow(light_s light, vec3 fragnormal)
return shadow / float(count);
}
// light reaching a surface from a sphere of uniform brightness (a lamp with a radius, as a sphere light of blender), in the units
// in which a point gives n.l / d^2: the same while the sphere is whole above the horizon of the surface, the part above it at
// grazing angles, and the light from all around within the sphere. Snyder, "Area light sources for real-time graphics", 1996, in
// the form of Lagarde, de Rousiers, "Moving Frostbite to physically based rendering", 2014
float sphere_light_irradiance(float NdotL, float distance2, float radius)
{
float radius2 = radius * radius;
if (distance2 <= radius2)
return 1.0 / radius2;
float sin_sigma2 = radius2 / distance2;
if (NdotL * NdotL > sin_sigma2)
return max(NdotL, 0.0) / distance2;
float sin_theta = sqrt(max(1.0 - NdotL * NdotL, 1e-8));
float x = sqrt(max(1.0 / sin_sigma2 - 1.0, 1e-8));
float y = clamp(-x * NdotL / sin_theta, -1.0, 1.0);
float sin_theta_sqrt_y = sin_theta * sqrt(max(1.0 - y * y, 0.0));
float form = (NdotL * acos(y) - x * sin_theta_sqrt_y) * sin_sigma2 + atan(sin_theta_sqrt_y / x);
return max(form, 0.0) / (BRDF_PI * radius2);
}
// the point of the sphere of a lamp (Radius, around the end of Tolight, view space) closest to the reflected ray of the view, where
// its highlight is, and in Energy the share of the light of the highlight widened by the sphere which the highlight of a point
// takes. Karis, "Real shading in Unreal Engine 4", 2013
vec3 sphere_light_specular(vec3 tolight, vec3 fragnormal, float radius, float roughness, out float energy)
{
vec3 reflected = reflect(normalize(f_pos.xyz), fragnormal);
vec3 toray = dot(tolight, reflected) * reflected - tolight;
vec3 closest = tolight + toray * clamp(radius * inversesqrt(max(dot(toray, toray), 1e-8)), 0.0, 1.0);
float alpha = roughness * roughness;
float widened = clamp(alpha + 0.5 * radius * inversesqrt(max(dot(tolight, tolight), 1e-8)), 1e-4, 1.0);
energy = (alpha / widened) * (alpha / widened);
return closest;
}
// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse
// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light,
// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the
// outer one. a lamp with a shadow map is held back by what is between it and the fragment
// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the
// distance (lamp_falloff(), a lamp with a radius shines from its sphere: sphere_light_irradiance()), and smoothly to nothing at the
// range of the light, 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its
// inner cone to the outer one. a lamp with a shadow map is held back by what is between it and the fragment
vec2 calc_local_light(light_s light, vec3 fragnormal)
{
vec3 tolight = light.pos - f_pos.xyz;
float distance2 = dot(tolight, tolight);
vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8));
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2);
float atten = edge * edge;
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0))
float falloff = lamp_falloff(light, distance2);
float NdotL = dot(fragnormal, light_dir);
float diffuse = (light.ambient > 0.0) ? sphere_light_irradiance(NdotL, distance2, light.ambient) : max(NdotL, 0.0) * falloff;
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (diffuse > 0.0))
atten *= 1.0 - local_light_shadow(light, fragnormal);
if ((atten > 0.0) && (contact_shadow_budget > 0))
if ((atten > 0.0) && (contact_shadow_budget > 0) && lamp_casts_shadows(light))
atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2),
atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
atten * diffuse * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
if (atten <= 0.0)
return vec2(0.0);
return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten;
return vec2(diffuse, falloff) * atten;
}
// index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip
@@ -568,7 +603,12 @@ void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffu
diffuse += color * part.x;
if (matte)
return;
specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT);
// the highlight of a lamp with a radius is that of its sphere
vec3 tolight = light.pos - f_pos.xyz;
float energy = 1.0;
if ((light.type != LIGHT_HEADLIGHTS) && (light.ambient != 0.0))
tolight = sphere_light_specular(tolight, fragnormal, abs(light.ambient), spec_roughness, energy);
specular += color * (part.y * param[1].y * energy) * specular_light(normalize(tolight), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT);
}
// adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in

View File

@@ -93,7 +93,7 @@ vec3 particle_lamp(light_s light, vec3 position)
vec3 tolight = light.pos - position;
float distance2 = dot(tolight, tolight);
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, 0.01);
float atten = edge * edge * lamp_falloff(light, distance2);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
return light.color * (atten * light.intensity);

View File

@@ -10,7 +10,7 @@
layout(location = 0) out vec4 out_light;
layout(location = 1) out vec4 out_albedo;
// closest distance (squared) the light of the lamp is computed for, as LOCAL_LIGHT_MIN_DISTANCE2 of light_common.glsl
// closest distance (squared) the light of the lamp is computed for, as lamp_falloff() of common.glsl takes it for a lamp without a radius
const float PROBE_LIGHT_MIN_DISTANCE2 = 0.01;
// the light of the sun on a diffuse surface, as SUN_DIFFUSE_SCALE and SUN_NDOTL_SHARPNESS of light_common.glsl
const float PROBE_SUN_SCALE = 1.5;

View File

@@ -182,6 +182,24 @@ struct light_s
vec4 headlight_weights;
};
// the falloff of the light of a lamp (SPOT, POINT) with the squared distance Distance2 from its middle. the lamps shine from a
// sphere, of the radius abs(ambient) (see gl::light_element, 0: a point): 1 / d^2 past it and 1 / r^2 within it, as a sphere light
// of blender gives it, or 1 / (d^2 + r^2) with its soft falloff (ambient below 0). a point is no brighter than 0.1 m from it
float lamp_falloff(light_s light, float distance2)
{
float radius = abs(light.ambient);
if (light.ambient < 0.0)
return 1.0 / max(distance2 + radius * radius, 1e-6);
return 1.0 / max(distance2, (radius > 0.0) ? radius * radius : 0.01);
}
// whether the lamp casts shadows: one without a shadow map (headlight_weights.x below 0) has y 1 when it casts none at all (no
// contact shadows either)
bool lamp_casts_shadows(light_s light)
{
return (light.headlight_weights.x >= 0.0) || (light.headlight_weights.y < 0.5);
}
// light clusters, see gl/cluster_grid.h
struct cluster_aabb_s
{

View File

@@ -152,7 +152,7 @@ vec3 lamp_light(light_s light, vec3 position)
vec3 tolight = light.pos - position;
float distance2 = dot(tolight, tolight);
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, 0.01);
float atten = edge * edge * lamp_falloff(light, distance2);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
return light.color * (atten * light.intensity);
@@ -210,7 +210,8 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float
vec3 sum = vec3(0.0);
float closest = dot(lamp.pos - start, direction);
vec3 nearest = start + direction * closest;
float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width);
// the sphere of a lamp with a radius spreads its light as the width of the cell does
float reach = sqrt(dot(lamp.pos - nearest, lamp.pos - nearest) + width * width + lamp.ambient * lamp.ambient);
float first = atan(-closest, reach);
float last = atan(span - closest, reach);
for (int i = 0; i < 2; i++)
@@ -219,7 +220,7 @@ vec3 lamp_segment(light_s lamp, light_s cone, vec3 start, vec3 direction, float
vec3 tolight = cone.pos - position;
// the light without its falloff, which the integral holds
vec3 fromlamp = position - lamp.pos;
sum += lamp_light(cone, position) * max(dot(tolight, tolight), 0.01) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4);
sum += lamp_light(cone, position) / lamp_falloff(cone, dot(tolight, tolight)) * henyey_greenstein(dot(fromlamp, -direction) * inversesqrt(max(dot(fromlamp, fromlamp), 1e-8)), 0.4);
}
return sum * (0.5 * (last - first) / (reach * max(span, 1e-4)));
}

View File

@@ -477,9 +477,6 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
return vec2(part.x, 1.0) * (atten * lightintensity);
}
// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit
const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01;
// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the
// map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise
float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps)
@@ -529,27 +526,65 @@ float local_light_shadow(light_s light, vec3 fragnormal)
return shadow / float(count);
}
// light reaching a surface from a sphere of uniform brightness (a lamp with a radius, as a sphere light of blender), in the units
// in which a point gives n.l / d^2: the same while the sphere is whole above the horizon of the surface, the part above it at
// grazing angles, and the light from all around within the sphere. Snyder, "Area light sources for real-time graphics", 1996, in
// the form of Lagarde, de Rousiers, "Moving Frostbite to physically based rendering", 2014
float sphere_light_irradiance(float NdotL, float distance2, float radius)
{
float radius2 = radius * radius;
if (distance2 <= radius2)
return 1.0 / radius2;
float sin_sigma2 = radius2 / distance2;
if (NdotL * NdotL > sin_sigma2)
return max(NdotL, 0.0) / distance2;
float sin_theta = sqrt(max(1.0 - NdotL * NdotL, 1e-8));
float x = sqrt(max(1.0 / sin_sigma2 - 1.0, 1e-8));
float y = clamp(-x * NdotL / sin_theta, -1.0, 1.0);
float sin_theta_sqrt_y = sin_theta * sqrt(max(1.0 - y * y, 0.0));
float form = (NdotL * acos(y) - x * sin_theta_sqrt_y) * sin_sigma2 + atan(sin_theta_sqrt_y / x);
return max(form, 0.0) / (BRDF_PI * radius2);
}
// the point of the sphere of a lamp (Radius, around the end of Tolight, view space) closest to the reflected ray of the view, where
// its highlight is, and in Energy the share of the light of the highlight widened by the sphere which the highlight of a point
// takes. Karis, "Real shading in Unreal Engine 4", 2013
vec3 sphere_light_specular(vec3 tolight, vec3 fragnormal, float radius, float roughness, out float energy)
{
vec3 reflected = reflect(normalize(f_pos.xyz), fragnormal);
vec3 toray = dot(tolight, reflected) * reflected - tolight;
vec3 closest = tolight + toray * clamp(radius * inversesqrt(max(dot(toray, toray), 1e-8)), 0.0, 1.0);
float alpha = roughness * roughness;
float widened = clamp(alpha + 0.5 * radius * inversesqrt(max(dot(tolight, tolight), 1e-8)), 1e-4, 1.0);
energy = (alpha / widened) * (alpha / widened);
return closest;
}
// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse
// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light,
// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the
// outer one. a lamp with a shadow map is held back by what is between it and the fragment
// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the
// distance (lamp_falloff(), a lamp with a radius shines from its sphere: sphere_light_irradiance()), and smoothly to nothing at the
// range of the light, 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its
// inner cone to the outer one. a lamp with a shadow map is held back by what is between it and the fragment
vec2 calc_local_light(light_s light, vec3 fragnormal)
{
vec3 tolight = light.pos - f_pos.xyz;
float distance2 = dot(tolight, tolight);
vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8));
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2);
float atten = edge * edge;
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0))
float falloff = lamp_falloff(light, distance2);
float NdotL = dot(fragnormal, light_dir);
float diffuse = (light.ambient > 0.0) ? sphere_light_irradiance(NdotL, distance2, light.ambient) : max(NdotL, 0.0) * falloff;
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (diffuse > 0.0))
atten *= 1.0 - local_light_shadow(light, fragnormal);
if ((atten > 0.0) && (contact_shadow_budget > 0))
if ((atten > 0.0) && (contact_shadow_budget > 0) && lamp_casts_shadows(light))
atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2),
atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
atten * diffuse * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
if (atten <= 0.0)
return vec2(0.0);
return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten;
return vec2(diffuse, falloff) * atten;
}
// index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip
@@ -572,7 +607,12 @@ void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffu
diffuse += color * part.x;
if (matte)
return;
specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT);
// the highlight of a lamp with a radius is that of its sphere
vec3 tolight = light.pos - f_pos.xyz;
float energy = 1.0;
if ((light.type != LIGHT_HEADLIGHTS) && (light.ambient != 0.0))
tolight = sphere_light_specular(tolight, fragnormal, abs(light.ambient), spec_roughness, energy);
specular += color * (part.y * param[1].y * energy) * specular_light(normalize(tolight), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT);
}
// adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in

View File

@@ -93,7 +93,7 @@ vec3 particle_lamp(light_s light, vec3 position)
vec3 tolight = light.pos - position;
float distance2 = dot(tolight, tolight);
float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0);
float atten = edge * edge / max(distance2, 0.01);
float atten = edge * edge * lamp_falloff(light, distance2);
if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-tolight * inversesqrt(max(distance2, 1e-8)), normalize(light.dir)));
return light.color * (atten * light.intensity);

View File

@@ -5,8 +5,9 @@
// geometry shader shadowcube.geom of the opengl renderer). the modelview takes the geometry to the space of the map, with the lamp at
// the origin. the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +z, -z), each looks the way glm::lookAt() with
// the usual up vectors of cube maps sets it, with a 90 degree projection of infinite reach and reversed depth like the one of the
// scene: the depth is near / d, d the distance along the axis of the face. the near plane of the map, LOCAL_SHADOW_NEAR: closer
// surfaces (the lamp itself) cast no shadows
// scene: the depth is near / d, d the distance along the axis of the face. the near plane of the map, shadow_fit.y of the scene block
// for the map of a scenery lamp (past the radius of its light), LOCAL_SHADOW_NEAR otherwise: closer surfaces (the lamp itself) cast no
// shadows
#include "common.glsl"
layout(location = 0) in vec3 v_vert;
layout(location = 2) in vec2 v_coord;
@@ -22,7 +23,8 @@ void main()
{
vec3 position = (effective_modelview() * vec4(v_vert, 1.0f)).xyz;
int face = int(gl_ViewIndex);
gl_Position = vec4(dot(position, face_right[face]), dot(position, face_up[face]), LOCAL_SHADOW_NEAR, dot(position, face_forward[face]));
float nearplane = (shadow_fit.y > 0.0) ? shadow_fit.y : LOCAL_SHADOW_NEAR;
gl_Position = vec4(dot(position, face_right[face]), dot(position, face_up[face]), nearplane, dot(position, face_forward[face]));
f_coord = v_coord;
#ifdef MODEL_DRAWS
// which block of the multi draw the fragments read (common.glsl)