Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LightmapGI: Implement shadowmask for DirectionalLight #77284

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/classes/LightmapGI.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
If [code]true[/code], uses a CPU-based denoising algorithm on the generated lightmap. This eliminates most noise within the generated lightmap at the cost of longer bake times. File sizes are generally not impacted significantly by the use of a denoiser, although lossless compression may do a better job at compressing a denoised image.
[b]Note:[/b] The built-in denoiser (OpenImageDenoise) may crash when denoising lightmaps in large scenes. If you encounter a crash at the end of lightmap baking, try disabling [member use_denoiser].
</member>
<member name="use_shadowmask" type="bool" setter="set_use_shadowmask" getter="is_using_shadowmask" default="true">
If [code]true[/code], bakes the [DirectionalLight3D]s' direct light shadows into a [i]shadowmask[/i] which is stored in the lightmap textures' alpha channel. This shadowmask is used to keep static shadows visible past the DirectionalLights' [member DirectionalLight3D.directional_shadow_max_distance] by blending the last shadow split with the shadowmask. This in turn allows you to use a lower [member DirectionalLight3D.directional_shadow_max_distance] for dynamic objects. Lower real-time shadow distances improve shadow detail and performance while making shadow acne less visible.
[b]Note:[/b] If you have multiple [DirectionalLight3D]s that have their [member Light3D.light_bake_mode] set to [constant Light3D.BAKE_DYNAMIC], then the shadowmask will only be present in areas where their shadows overlap.
</member>
</members>
<constants>
<constant name="BAKE_QUALITY_LOW" value="0" enum="BakeQuality">
Expand Down
58 changes: 53 additions & 5 deletions modules/lightmapper_rd/lightmapper_rd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
return BAKE_OK;
}

LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_use_shadowmask, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
if (p_step_function) {
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
}
Expand All @@ -682,6 +682,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
return bake_error;
}

// If there are no valid directional lights for shadowmasking, the entire
// scene would be shadowed and this saves baking time.
if (p_use_shadowmask) {
bool vaild_shadowmask_light = false;
for (int i = 0; i < lights.size(); i++) {
if (lights[i].type == LIGHT_TYPE_DIRECTIONAL && !lights[i].static_bake) {
vaild_shadowmask_light = true;
break;
}
}

if (!vaild_shadowmask_light) {
p_use_shadowmask = false;
WARN_PRINT("Shadowmask disabled: no directional light with their bake mode set to dynamic exists.");
}
}

#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices; i++) {
albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png");
Expand All @@ -702,6 +719,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
RID light_accum_tex2;
RID light_primary_dynamic_tex;
RID light_environment_tex;
RID shadowmask_tex;

#define FREE_TEXTURES \
rd->free(albedo_array_tex); \
Expand All @@ -713,7 +731,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
rd->free(light_accum_tex2); \
rd->free(light_accum_tex); \
rd->free(light_primary_dynamic_tex); \
rd->free(light_environment_tex);
rd->free(light_environment_tex); \
rd->free(shadowmask_tex);

{ // create all textures

Expand Down Expand Up @@ -745,8 +764,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
position_tex = rd->texture_create(tf, RD::TextureView());
unocclude_tex = rd->texture_create(tf, RD::TextureView());

tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
tf.format = RD::DATA_FORMAT_R16_SFLOAT;

// We use the alpha channel of the lightmap during baking, so we
// need to bake the shadowmask into a separate texture first and
// then copy it into the lightmap's alpha channel once it's done
// being used.
shadowmask_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(shadowmask_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);

tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;

light_source_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
Expand Down Expand Up @@ -963,7 +991,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d

Ref<RDShaderFile> compute_shader;
compute_shader.instantiate();
err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : "");
err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, vformat("%s\n%s", p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : "", p_use_shadowmask ? "\n#define USE_SHADOWMASK\n" : ""));
if (err != OK) {
FREE_TEXTURES
FREE_BUFFERS
Expand Down Expand Up @@ -1129,6 +1157,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
u.append_id(light_primary_dynamic_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 6;
u.append_id(shadowmask_tex);
uniforms.push_back(u);
}
}

RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1);
Expand Down Expand Up @@ -1630,8 +1665,21 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d

for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
if (p_use_shadowmask) { // copy shadowmask into alpha
Vector<uint8_t> sms = rd->texture_get_data(shadowmask_tex, i);
uint32_t count = sms.size() / 2; //uint16s
const uint16_t *src = (const uint16_t *)sms.ptr();
uint16_t *dst = (uint16_t *)s.ptrw();
for (uint32_t j = 0; j < count; j++) {
dst[j * 4 + 3] = src[j];
}
}

Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->convert(Image::FORMAT_RGBH); //remove alpha
if (!p_use_shadowmask) {
img->convert(Image::FORMAT_RGBH); //remove alpha
}

bake_textures.push_back(img);
}

Expand Down
2 changes: 1 addition & 1 deletion modules/lightmapper_rd/lightmapper_rd.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class LightmapperRD : public Lightmapper {
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_probe(const Vector3 &p_position) override;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_use_shadowmask, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;

int get_bake_texture_count() const override;
Ref<Image> get_bake_texture(int p_index) const override;
Expand Down
16 changes: 16 additions & 0 deletions modules/lightmapper_rd/lm_compute.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ layout(set = 1, binding = 6) uniform texture2D environment;
#endif
#ifdef MODE_DIRECT_LIGHT
layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic;
#ifdef USE_SHADOWMASK
layout(r16, set = 1, binding = 6) uniform restrict writeonly image2DArray shadowmask;
#endif
#endif

#ifdef MODE_DILATE
Expand Down Expand Up @@ -306,6 +309,10 @@ void main() {
vec3 static_light = vec3(0.0);
vec3 dynamic_light = vec3(0.0);

#ifdef USE_SHADOWMASK
float sm = 0.0;
#endif

#ifdef USE_SH_LIGHTMAPS
vec4 sh_accum[4] = vec4[](
vec4(0.0, 0.0, 0.0, 1.0),
Expand Down Expand Up @@ -421,6 +428,11 @@ void main() {

} else {
dynamic_light += light;
#ifdef USE_SHADOWMASK
if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) {
sm = clamp(sm + attenuation * penumbra, 0.0, 1.0);
}
#endif
}
}

Expand Down Expand Up @@ -449,6 +461,10 @@ void main() {
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0));
#endif

#ifdef USE_SHADOWMASK
imageStore(shadowmask, ivec3(atlas_pos, params.atlas_slice), vec4(sm));
#endif

#endif

#ifdef MODE_BOUNCE_LIGHT
Expand Down
14 changes: 13 additions & 1 deletion scene/3d/lightmap_gi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}
}

Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, max_texture_size, directional, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, max_texture_size, use_shadowmask, directional, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);

if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES) {
return BAKE_ERROR_MESHES_INVALID;
Expand Down Expand Up @@ -1381,6 +1381,14 @@ bool LightmapGI::is_interior() const {
return interior;
}

void LightmapGI::set_use_shadowmask(bool p_enable) {
use_shadowmask = p_enable;
}

bool LightmapGI::is_using_shadowmask() const {
return use_shadowmask;
}

void LightmapGI::set_environment_mode(EnvironmentMode p_mode) {
environment_mode = p_mode;
notify_property_list_changed();
Expand Down Expand Up @@ -1515,6 +1523,9 @@ void LightmapGI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &LightmapGI::set_use_denoiser);
ClassDB::bind_method(D_METHOD("is_using_denoiser"), &LightmapGI::is_using_denoiser);

ClassDB::bind_method(D_METHOD("set_use_shadowmask", "use_shadowmask"), &LightmapGI::set_use_shadowmask);
ClassDB::bind_method(D_METHOD("is_using_shadowmask"), &LightmapGI::is_using_shadowmask);

ClassDB::bind_method(D_METHOD("set_interior", "enable"), &LightmapGI::set_interior);
ClassDB::bind_method(D_METHOD("is_interior"), &LightmapGI::is_interior);

Expand All @@ -1532,6 +1543,7 @@ void LightmapGI::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_shadowmask"), "set_use_shadowmask", "is_using_shadowmask");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size");
ADD_GROUP("Environment", "environment_");
Expand Down
4 changes: 4 additions & 0 deletions scene/3d/lightmap_gi.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class LightmapGI : public VisualInstance3D {
float bias = 0.0005;
int max_texture_size = 16384;
bool interior = false;
bool use_shadowmask = true;
EnvironmentMode environment_mode = ENVIRONMENT_MODE_SCENE;
Ref<Sky> environment_custom_sky;
Color environment_custom_color = Color(1, 1, 1);
Expand Down Expand Up @@ -244,6 +245,9 @@ class LightmapGI : public VisualInstance3D {
void set_interior(bool p_interior);
bool is_interior() const;

void set_use_shadowmask(bool p_enable);
bool is_using_shadowmask() const;

void set_environment_mode(EnvironmentMode p_mode);
EnvironmentMode get_environment_mode() const;

Expand Down
2 changes: 1 addition & 1 deletion scene/3d/lightmapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class Lightmapper : public RefCounted {
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_probe(const Vector3 &p_position) = 0;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_use_shadowmask, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;

virtual int get_bake_texture_count() const = 0;
virtual Ref<Image> get_bake_texture(int p_index) const = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@ void fragment_shader(in SceneData scene_data) {
vec3 specular_light = vec3(0.0, 0.0, 0.0);
vec3 diffuse_light = vec3(0.0, 0.0, 0.0);
vec3 ambient_light = vec3(0.0, 0.0, 0.0);
float shadowmask = 1.0;

#ifndef MODE_UNSHADED
// Used in regular draw pass and when drawing SDFs for SDFGI and materials for VoxelGI.
Expand Down Expand Up @@ -1209,7 +1210,14 @@ void fragment_shader(in SceneData scene_data) {

if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;

// Only the alpha channel of the first lightmap is used for
// shadowmasking. The alpha channels of the other three lightmaps
// are not used.
vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0);
shadowmask = lightmap_sample.a;

vec3 lm_light_l0 = lightmap_sample.rgb;
vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
Expand All @@ -1231,7 +1239,9 @@ void fragment_shader(in SceneData scene_data) {

} else {
uint idx = instances.data[instance_index].gi_offset >> 20;
ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb * lightmaps.data[idx].exposure_normalization;
vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0);
ambient_light += lightmap_sample.rgb * lightmaps.data[idx].exposure_normalization;
shadowmask = lightmap_sample.a;
}
}
#else
Expand Down Expand Up @@ -1736,6 +1746,8 @@ void fragment_shader(in SceneData scene_data) {
#undef BIAS_FUNC
} // shadows

shadow = min(shadow, shadowmask);

if (i < 4) {
shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ void main() {
vec3 specular_light = vec3(0.0, 0.0, 0.0);
vec3 diffuse_light = vec3(0.0, 0.0, 0.0);
vec3 ambient_light = vec3(0.0, 0.0, 0.0);
float shadowmask = 1.0;

#ifndef MODE_UNSHADED
// Used in regular draw pass and when drawing SDFs for SDFGI and materials for VoxelGI.
Expand Down Expand Up @@ -1112,7 +1113,14 @@ void main() {

if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;

// Only the alpha channel of the first lightmap is used for
// shadowmasking. The alpha channels of the other three lightmaps
// are not used.
vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0);
shadowmask = lightmap_sample.a;

vec3 lm_light_l0 = lightmap_sample.rgb;
vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
Expand All @@ -1132,7 +1140,9 @@ void main() {
}

} else {
ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb * lightmaps.data[idx].exposure_normalization;
vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0);
ambient_light += lightmap_sample.rgb * lightmaps.data[idx].exposure_normalization;
shadowmask = lightmap_sample.a;
}
}

Expand Down Expand Up @@ -1401,7 +1411,7 @@ void main() {
shadow = mix(shadow, shadow2, pssm_blend);
}

shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
shadow = mix(shadow, shadowmask, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance

#undef BIAS_FUNC
}
Expand Down Expand Up @@ -1502,6 +1512,8 @@ void main() {
}
#endif

shadow = min(shadow, shadowmask);

if (i < 4) {
shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
} else {
Expand Down