Skip to content

Commit

Permalink
Add soft shadows to the CPU lightmapper
Browse files Browse the repository at this point in the history
Adds the "light_size" property to Lights. It's only considered in baked
lightmaps for soft shadowing purposes.
  • Loading branch information
JFonS committed Jul 5, 2021
1 parent 19359a9 commit a2ba791
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 95 deletions.
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

0 comments on commit a2ba791

Please sign in to comment.