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

[3.x] Add soft shadows to the CPU lightmapper #50184

Merged
merged 1 commit into from
Jul 14, 2021
Merged
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
34 changes: 20 additions & 14 deletions doc/classes/Light.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
<member name="light_negative" type="bool" setter="set_negative" getter="is_negative" default="false">
If [code]true[/code], the light's effect is reversed, darkening areas and casting bright shadows.
</member>
<member name="light_size" type="float" setter="set_param" getter="get_param" default="0.0">
The size of the light in Godot units. Only considered in baked lightmaps and only if [member light_bake_mode] is set to [constant BAKE_ALL]. Increasing this value will make the shadows appear blurrier. This can be used to simulate area lights to an extent.
</member>
<member name="light_specular" type="float" setter="set_param" getter="get_param" default="0.5">
The intensity of the specular blob in objects affected by the light. At [code]0[/code], the light becomes a pure diffuse light. When not baking emission, this can be used to avoid unrealistic reflections when placing lights above an emissive surface.
</member>
Expand All @@ -80,46 +83,49 @@
<constant name="PARAM_INDIRECT_ENERGY" value="1" enum="Param">
Constant for accessing [member light_indirect_energy].
</constant>
<constant name="PARAM_SPECULAR" value="2" enum="Param">
<constant name="PARAM_SIZE" value="2" enum="Param">
Constant for accessing [member light_size].
</constant>
<constant name="PARAM_SPECULAR" value="3" enum="Param">
Constant for accessing [member light_specular].
</constant>
<constant name="PARAM_RANGE" value="3" enum="Param">
<constant name="PARAM_RANGE" value="4" enum="Param">
Constant for accessing [member OmniLight.omni_range] or [member SpotLight.spot_range].
</constant>
<constant name="PARAM_ATTENUATION" value="4" enum="Param">
<constant name="PARAM_ATTENUATION" value="5" enum="Param">
Constant for accessing [member OmniLight.omni_attenuation] or [member SpotLight.spot_attenuation].
</constant>
<constant name="PARAM_SPOT_ANGLE" value="5" enum="Param">
<constant name="PARAM_SPOT_ANGLE" value="6" enum="Param">
Constant for accessing [member SpotLight.spot_angle].
</constant>
<constant name="PARAM_SPOT_ATTENUATION" value="6" enum="Param">
<constant name="PARAM_SPOT_ATTENUATION" value="7" enum="Param">
Constant for accessing [member SpotLight.spot_angle_attenuation].
</constant>
<constant name="PARAM_CONTACT_SHADOW_SIZE" value="7" enum="Param">
<constant name="PARAM_CONTACT_SHADOW_SIZE" value="8" enum="Param">
Constant for accessing [member shadow_contact].
</constant>
<constant name="PARAM_SHADOW_MAX_DISTANCE" value="8" enum="Param">
<constant name="PARAM_SHADOW_MAX_DISTANCE" value="9" enum="Param">
Constant for accessing [member DirectionalLight.directional_shadow_max_distance].
</constant>
<constant name="PARAM_SHADOW_SPLIT_1_OFFSET" value="9" enum="Param">
<constant name="PARAM_SHADOW_SPLIT_1_OFFSET" value="10" enum="Param">
Constant for accessing [member DirectionalLight.directional_shadow_split_1].
</constant>
<constant name="PARAM_SHADOW_SPLIT_2_OFFSET" value="10" enum="Param">
<constant name="PARAM_SHADOW_SPLIT_2_OFFSET" value="11" enum="Param">
Constant for accessing [member DirectionalLight.directional_shadow_split_2].
</constant>
<constant name="PARAM_SHADOW_SPLIT_3_OFFSET" value="11" enum="Param">
<constant name="PARAM_SHADOW_SPLIT_3_OFFSET" value="12" enum="Param">
Constant for accessing [member DirectionalLight.directional_shadow_split_3].
</constant>
<constant name="PARAM_SHADOW_NORMAL_BIAS" value="12" enum="Param">
<constant name="PARAM_SHADOW_NORMAL_BIAS" value="13" enum="Param">
Constant for accessing [member DirectionalLight.directional_shadow_normal_bias].
</constant>
<constant name="PARAM_SHADOW_BIAS" value="13" enum="Param">
<constant name="PARAM_SHADOW_BIAS" value="14" enum="Param">
Constant for accessing [member shadow_bias].
</constant>
<constant name="PARAM_SHADOW_BIAS_SPLIT_SCALE" value="14" enum="Param">
<constant name="PARAM_SHADOW_BIAS_SPLIT_SCALE" value="15" enum="Param">
Constant for accessing [member DirectionalLight.directional_shadow_bias_split_scale].
</constant>
<constant name="PARAM_MAX" value="15" enum="Param">
<constant name="PARAM_MAX" value="16" enum="Param">
Represents the size of the [enum Param] enum.
</constant>
<constant name="BAKE_DISABLED" value="0" enum="BakeMode">
Expand Down
34 changes: 20 additions & 14 deletions doc/classes/VisualServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4534,46 +4534,52 @@
<constant name="LIGHT_PARAM_ENERGY" value="0" enum="LightParam">
The light's energy.
</constant>
<constant name="LIGHT_PARAM_SPECULAR" value="2" enum="LightParam">
<constant name="LIGHT_PARAM_INDIRECT_ENERGY" value="1" enum="LightParam">
Secondary multiplier used with indirect light (light bounces).
</constant>
<constant name="LIGHT_PARAM_SIZE" value="2" enum="LightParam">
The light's size, currently only used for soft shadows in baked lightmaps.
</constant>
<constant name="LIGHT_PARAM_SPECULAR" value="3" enum="LightParam">
The light's influence on specularity.
</constant>
<constant name="LIGHT_PARAM_RANGE" value="3" enum="LightParam">
<constant name="LIGHT_PARAM_RANGE" value="4" enum="LightParam">
The light's range.
</constant>
<constant name="LIGHT_PARAM_ATTENUATION" value="4" enum="LightParam">
<constant name="LIGHT_PARAM_ATTENUATION" value="5" enum="LightParam">
The light's attenuation.
</constant>
<constant name="LIGHT_PARAM_SPOT_ANGLE" value="5" enum="LightParam">
<constant name="LIGHT_PARAM_SPOT_ANGLE" value="6" enum="LightParam">
The spotlight's angle.
</constant>
<constant name="LIGHT_PARAM_SPOT_ATTENUATION" value="6" enum="LightParam">
<constant name="LIGHT_PARAM_SPOT_ATTENUATION" value="7" enum="LightParam">
The spotlight's attenuation.
</constant>
<constant name="LIGHT_PARAM_CONTACT_SHADOW_SIZE" value="7" enum="LightParam">
<constant name="LIGHT_PARAM_CONTACT_SHADOW_SIZE" value="8" enum="LightParam">
Scales the shadow color.
</constant>
<constant name="LIGHT_PARAM_SHADOW_MAX_DISTANCE" value="8" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_MAX_DISTANCE" value="9" enum="LightParam">
Max distance that shadows will be rendered.
</constant>
<constant name="LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET" value="9" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET" value="10" enum="LightParam">
Proportion of shadow atlas occupied by the first split.
</constant>
<constant name="LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET" value="10" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET" value="11" enum="LightParam">
Proportion of shadow atlas occupied by the second split.
</constant>
<constant name="LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET" value="11" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET" value="12" enum="LightParam">
Proportion of shadow atlas occupied by the third split. The fourth split occupies the rest.
</constant>
<constant name="LIGHT_PARAM_SHADOW_NORMAL_BIAS" value="12" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_NORMAL_BIAS" value="13" enum="LightParam">
Normal bias used to offset shadow lookup by object normal. Can be used to fix self-shadowing artifacts.
</constant>
<constant name="LIGHT_PARAM_SHADOW_BIAS" value="13" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_BIAS" value="14" enum="LightParam">
Bias the shadow lookup to fix self-shadowing artifacts.
</constant>
<constant name="LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE" value="14" enum="LightParam">
<constant name="LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE" value="15" enum="LightParam">
Increases bias on further splits to fix self-shadowing that only occurs far away from the camera.
</constant>
<constant name="LIGHT_PARAM_MAX" value="15" enum="LightParam">
<constant name="LIGHT_PARAM_MAX" value="16" enum="LightParam">
Represents the size of the [enum LightParam] enum.
</constant>
<constant name="LIGHT_BAKE_DISABLED" value="0" enum="LightBakeMode">
Expand Down
1 change: 1 addition & 0 deletions drivers/gles2/rasterizer_storage_gles2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3756,6 +3756,7 @@ RID RasterizerStorageGLES2::light_create(VS::LightType p_type) {

light->param[VS::LIGHT_PARAM_ENERGY] = 1.0;
light->param[VS::LIGHT_PARAM_INDIRECT_ENERGY] = 1.0;
light->param[VS::LIGHT_PARAM_SIZE] = 0.0;
light->param[VS::LIGHT_PARAM_SPECULAR] = 0.5;
light->param[VS::LIGHT_PARAM_RANGE] = 1.0;
light->param[VS::LIGHT_PARAM_SPOT_ANGLE] = 45;
Expand Down
1 change: 1 addition & 0 deletions drivers/gles3/rasterizer_storage_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5261,6 +5261,7 @@ RID RasterizerStorageGLES3::light_create(VS::LightType p_type) {

light->param[VS::LIGHT_PARAM_ENERGY] = 1.0;
light->param[VS::LIGHT_PARAM_INDIRECT_ENERGY] = 1.0;
light->param[VS::LIGHT_PARAM_SIZE] = 0.0;
light->param[VS::LIGHT_PARAM_SPECULAR] = 0.5;
light->param[VS::LIGHT_PARAM_RANGE] = 1.0;
light->param[VS::LIGHT_PARAM_SPOT_ANGLE] = 45;
Expand Down
157 changes: 99 additions & 58 deletions modules/lightmapper_cpu/lightmapper_cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -685,95 +685,133 @@ void LightmapperCPU::_plot_triangle(const Vector2 *p_vertices, const Vector3 *p_
}
}

_ALWAYS_INLINE_ float uniform_rand() {
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
static thread_local uint32_t state = Math::rand();
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
/* implicit conversion from 'unsigned int' to 'float' changes value from 4294967295 to 4294967296 */
return float(state) / float(UINT32_MAX);
}

void LightmapperCPU::_compute_direct_light(uint32_t p_idx, void *r_lightmap) {
LightmapTexel *lightmap = (LightmapTexel *)r_lightmap;
for (unsigned int i = 0; i < lights.size(); ++i) {
const Light &light = lights[i];
Vector3 normal = lightmap[p_idx].normal;
Vector3 position = lightmap[p_idx].pos;
Vector3 final_energy;
Color c = light.color;
Vector3 light_energy = Vector3(c.r, c.g, c.b) * light.energy;

if (light.type == LIGHT_TYPE_OMNI) {
Vector3 light_direction = (position - light.position).normalized();
if (normal.dot(light_direction) >= 0.0) {
continue;
}
float dist = position.distance_to(light.position);
Vector3 light_to_point = light.direction;
if (light.type == LIGHT_TYPE_OMNI || light.type == LIGHT_TYPE_SPOT) {
light_to_point = (position - light.position).normalized();
}

if (dist <= light.range) {
LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_direction, parameters.bias, dist - parameters.bias);
if (raycaster->intersect(ray)) {
continue;
}
float att = powf(1.0 - dist / light.range, light.attenuation);
final_energy = light_energy * att * MAX(0, normal.dot(-light_direction));
}
if (normal.dot(light_to_point) >= 0.0) {
continue;
}

if (light.type == LIGHT_TYPE_SPOT) {
Vector3 light_direction = (position - light.position).normalized();
if (normal.dot(light_direction) >= 0.0) {
float dist;
float attenuation;
float soft_shadowing_disk_size;

if (light.type == LIGHT_TYPE_OMNI || light.type == LIGHT_TYPE_SPOT) {
dist = position.distance_to(light.position);
if (dist > light.range) {
continue;
}
soft_shadowing_disk_size = light.size / dist;

float angle = Math::acos(light.direction.dot(light_direction));
if (light.type == LIGHT_TYPE_OMNI) {
attenuation = powf(1.0 - dist / light.range, light.attenuation);
} else /* (light.type == LIGHT_TYPE_SPOT) */ {
float angle = Math::acos(light.direction.dot(light_to_point));

if (angle > light.spot_angle) {
continue;
}
if (angle > light.spot_angle) {
continue;
}

float dist = position.distance_to(light.position);
if (dist > light.range) {
continue;
float normalized_dist = dist * (1.0f / MAX(0.001f, light.range));
float norm_light_attenuation = Math::pow(MAX(1.0f - normalized_dist, 0.001f), light.attenuation);

float spot_cutoff = Math::cos(light.spot_angle);
float scos = MAX(light_to_point.dot(light.direction), spot_cutoff);
float spot_rim = (1.0f - scos) / (1.0f - spot_cutoff);
attenuation = norm_light_attenuation * (1.0f - pow(MAX(spot_rim, 0.001f), light.spot_attenuation));
}
} else /*if (light.type == LIGHT_TYPE_DIRECTIONAL)*/ {
dist = INFINITY;
attenuation = 1.0f;
soft_shadowing_disk_size = light.size;
}

LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_direction, parameters.bias, dist);
if (raycaster->intersect(ray)) {
continue;
float penumbra = 0.0f;
if (light.size > 0.0) {
Vector3 light_to_point_tan;
Vector3 light_to_point_bitan;

if (light.type == LIGHT_TYPE_OMNI || light.type == LIGHT_TYPE_SPOT) {
light_to_point = (position - light.position).normalized();
Vector3 aux = light_to_point.y < 0.777 ? Vector3(0, 1, 0) : Vector3(1, 0, 0);
light_to_point_tan = light_to_point.cross(aux).normalized();
light_to_point_bitan = light_to_point.cross(light_to_point_tan).normalized();
} else /*if (light.type == LIGHT_TYPE_DIRECTIONAL)*/ {
Vector3 aux = light_to_point.y < 0.777 ? Vector3(0, 1, 0) : Vector3(1, 0, 0);
light_to_point_tan = light_to_point.cross(aux).normalized();
light_to_point_bitan = light_to_point.cross(light_to_point_tan).normalized();
}

float normalized_dist = dist * (1.0f / MAX(0.001f, light.range));
float norm_light_attenuation = Math::pow(MAX(1.0f - normalized_dist, 0.001f), light.attenuation);
const static int shadowing_rays_check_penumbra_denom = 2;
int shadowing_ray_count = parameters.samples;

int hits = 0;
Vector3 light_disk_to_point = light_to_point;
for (int j = 0; j < shadowing_ray_count; j++) {
// Optimization:
// Once already casted an important proportion of rays, if all are hits or misses,
// assume we're not in the penumbra so we can infer the rest would have the same result
if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) {
if (hits == j) {
// Assume totally lit
hits = shadowing_ray_count;
break;
} else if (hits == 0) {
// Assume totally dark
hits = 0;
break;
}
}

float spot_cutoff = Math::cos(light.spot_angle);
float scos = MAX(light_direction.dot(light.direction), spot_cutoff);
float spot_rim = (1.0f - scos) / (1.0f - spot_cutoff);
norm_light_attenuation *= 1.0f - pow(MAX(spot_rim, 0.001f), light.spot_attenuation);
final_energy = light_energy * norm_light_attenuation * MAX(0, normal.dot(-light_direction));
}
float r = uniform_rand();
float a = uniform_rand() * Math_TAU;
Vector2 disk_sample = (r * Vector2(Math::cos(a), Math::sin(a))) * soft_shadowing_disk_size;
light_disk_to_point = (light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan).normalized();

if (light.type == LIGHT_TYPE_DIRECTIONAL) {
if (normal.dot(light.direction) >= 0.0) {
continue;
}
LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_disk_to_point, parameters.bias, dist);
if (raycaster->intersect(ray)) {
continue;
}

LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position + normal * parameters.bias, -light.direction, parameters.bias);
if (raycaster->intersect(ray)) {
continue;
hits++;
}
penumbra = (float)hits / shadowing_ray_count;
} else {
LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_to_point, parameters.bias, dist);
if (!raycaster->intersect(ray)) {
penumbra = 1.0f;
}

final_energy = light_energy * MAX(0, normal.dot(-light.direction));
}

Vector3 final_energy = attenuation * penumbra * light_energy * MAX(0, normal.dot(-light_to_point));
lightmap[p_idx].direct_light += final_energy * light.indirect_multiplier;
if (light.bake_direct) {
lightmap[p_idx].output_light += final_energy;
}
}
}

_ALWAYS_INLINE_ float uniform_rand() {
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
static thread_local uint32_t state = Math::rand();
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
/* implicit conversion from 'unsigned int' to 'float' changes value from 4294967295 to 4294967296 */
return float(state) / float(UINT32_MAX);
}

void LightmapperCPU::_compute_indirect_light(uint32_t p_idx, void *r_lightmap) {
LightmapTexel *lightmap = (LightmapTexel *)r_lightmap;
LightmapTexel &texel = lightmap[p_idx];
Expand Down Expand Up @@ -1583,18 +1621,19 @@ void LightmapperCPU::add_mesh(const MeshData &p_mesh, Vector2i p_size) {
mesh_instances.push_back(mi);
}

void LightmapperCPU::add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier) {
void LightmapperCPU::add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_size) {
Light l;
l.type = LIGHT_TYPE_DIRECTIONAL;
l.direction = p_direction;
l.color = p_color;
l.energy = p_energy;
l.indirect_multiplier = p_indirect_multiplier;
l.bake_direct = p_bake_direct;
l.size = p_size;
lights.push_back(l);
}

void LightmapperCPU::add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation) {
void LightmapperCPU::add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_size) {
Light l;
l.type = LIGHT_TYPE_OMNI;
l.position = p_position;
Expand All @@ -1604,10 +1643,11 @@ void LightmapperCPU::add_omni_light(bool p_bake_direct, const Vector3 &p_positio
l.energy = p_energy;
l.indirect_multiplier = p_indirect_multiplier;
l.bake_direct = p_bake_direct;
l.size = p_size;
lights.push_back(l);
}

void LightmapperCPU::add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation) {
void LightmapperCPU::add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size) {
Light l;
l.type = LIGHT_TYPE_SPOT;
l.position = p_position;
Expand All @@ -1620,6 +1660,7 @@ void LightmapperCPU::add_spot_light(bool p_bake_direct, const Vector3 &p_positio
l.energy = p_energy;
l.indirect_multiplier = p_indirect_multiplier;
l.bake_direct = p_bake_direct;
l.size = p_size;
lights.push_back(l);
}

Expand Down
Loading