Several fixes to directional shadows, closes #10926

Added option to change directional light range mode, between optimized and stable. For Orthogonal, you might need to use optimized.
This commit is contained in:
Juan Linietsky 2017-09-07 18:00:47 -03:00
parent 1eeda0f32f
commit eedb39091a
13 changed files with 1044 additions and 962 deletions

View file

@ -265,76 +265,28 @@ void CameraMatrix::get_viewport_size(real_t &r_width, real_t &r_height) const {
bool CameraMatrix::get_endpoints(const Transform &p_transform, Vector3 *p_8points) const {
const real_t *matrix = (const real_t *)this->matrix;
Vector<Plane> planes = get_projection_planes(Transform());
const Planes intersections[8][3]={
{PLANE_FAR,PLANE_LEFT,PLANE_TOP},
{PLANE_FAR,PLANE_LEFT,PLANE_BOTTOM},
{PLANE_FAR,PLANE_RIGHT,PLANE_TOP},
{PLANE_FAR,PLANE_RIGHT,PLANE_BOTTOM},
{PLANE_NEAR,PLANE_LEFT,PLANE_TOP},
{PLANE_NEAR,PLANE_LEFT,PLANE_BOTTOM},
{PLANE_NEAR,PLANE_RIGHT,PLANE_TOP},
{PLANE_NEAR,PLANE_RIGHT,PLANE_BOTTOM},
};
///////--- Near Plane ---///////
Plane near_plane = Plane(matrix[3] + matrix[2],
matrix[7] + matrix[6],
matrix[11] + matrix[10],
-matrix[15] - matrix[14]);
near_plane.normalize();
for(int i=0;i<8;i++) {
///////--- Far Plane ---///////
Plane far_plane = Plane(matrix[2] - matrix[3],
matrix[6] - matrix[7],
matrix[10] - matrix[11],
matrix[15] - matrix[14]);
far_plane.normalize();
///////--- Right Plane ---///////
Plane right_plane = Plane(matrix[0] - matrix[3],
matrix[4] - matrix[7],
matrix[8] - matrix[11],
-matrix[15] + matrix[12]);
right_plane.normalize();
///////--- Top Plane ---///////
Plane top_plane = Plane(matrix[1] - matrix[3],
matrix[5] - matrix[7],
matrix[9] - matrix[11],
-matrix[15] + matrix[13]);
top_plane.normalize();
Vector3 near_endpoint_left, near_endpoint_right;
Vector3 far_endpoint_left, far_endpoint_right;
bool res = near_plane.intersect_3(right_plane, top_plane, &near_endpoint_right);
ERR_FAIL_COND_V(!res, false);
res = far_plane.intersect_3(right_plane, top_plane, &far_endpoint_right);
ERR_FAIL_COND_V(!res, false);
if ((matrix[8] == 0) && (matrix[9] == 0)) {
near_endpoint_left = near_endpoint_right;
near_endpoint_left.x = -near_endpoint_left.x;
far_endpoint_left = far_endpoint_right;
far_endpoint_left.x = -far_endpoint_left.x;
} else {
///////--- Left Plane ---///////
Plane left_plane = Plane(matrix[0] + matrix[3],
matrix[4] + matrix[7],
matrix[8] + matrix[11],
-matrix[15] - matrix[12]);
left_plane.normalize();
res = near_plane.intersect_3(left_plane, top_plane, &near_endpoint_left);
ERR_FAIL_COND_V(!res, false);
res = far_plane.intersect_3(left_plane, top_plane, &far_endpoint_left);
Vector3 point;
bool res = planes[intersections[i][0]].intersect_3(planes[intersections[i][1]],planes[intersections[i][2]], &point);
ERR_FAIL_COND_V(!res, false);
p_8points[i]=p_transform.xform(point);
}
p_8points[0] = p_transform.xform(Vector3(near_endpoint_right.x, near_endpoint_right.y, near_endpoint_right.z));
p_8points[1] = p_transform.xform(Vector3(near_endpoint_right.x, -near_endpoint_right.y, near_endpoint_right.z));
p_8points[2] = p_transform.xform(Vector3(near_endpoint_left.x, near_endpoint_left.y, near_endpoint_left.z));
p_8points[3] = p_transform.xform(Vector3(near_endpoint_left.x, -near_endpoint_left.y, near_endpoint_left.z));
p_8points[4] = p_transform.xform(Vector3(far_endpoint_right.x, far_endpoint_right.y, far_endpoint_right.z));
p_8points[5] = p_transform.xform(Vector3(far_endpoint_right.x, -far_endpoint_right.y, far_endpoint_right.z));
p_8points[6] = p_transform.xform(Vector3(far_endpoint_left.x, far_endpoint_left.y, far_endpoint_left.z));
p_8points[7] = p_transform.xform(Vector3(far_endpoint_left.x, -far_endpoint_left.y, far_endpoint_left.z));
return true;
}
Vector<Plane> CameraMatrix::get_projection_planes(const Transform &p_transform) const {
@ -610,6 +562,12 @@ int CameraMatrix::get_pixels_per_meter(int p_for_pixel_width) const {
return int((result.x * 0.5 + 0.5) * p_for_pixel_width);
}
bool CameraMatrix::is_orthogonal() const {
return matrix[3][3]==1.0;
}
real_t CameraMatrix::get_fov() const {
const real_t *matrix = (const real_t *)this->matrix;

View file

@ -69,6 +69,7 @@ struct CameraMatrix {
real_t get_z_near() const;
real_t get_aspect() const;
real_t get_fov() const;
bool is_orthogonal() const;
Vector<Plane> get_projection_planes(const Transform &p_transform) const;
@ -83,6 +84,7 @@ struct CameraMatrix {
Plane xform4(const Plane &p_vec4) const;
_FORCE_INLINE_ Vector3 xform(const Vector3 &p_vec3) const;
operator String() const;
void scale_translate_to_fit(const Rect3 &p_aabb);

View file

@ -2599,7 +2599,7 @@ void RasterizerSceneGLES3::_setup_directional_light(int p_index, const Transform
}
}
ubo_data.shadow_split_offsets[j] = 1.0 / li->shadow_transform[j].split;
ubo_data.shadow_split_offsets[j] = li->shadow_transform[j].split;
Transform modelview = (p_camera_inverse_transform * li->shadow_transform[j].transform).inverse();
@ -4331,7 +4331,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const
storage->canvas->draw_generic_textured_rect(Rect2(0, 0, storage->frame.current_rt->width / 2, storage->frame.current_rt->height / 2), Rect2(0, 0, 1, 1));
}
if (false && directional_shadow.fbo) {
if (true && directional_shadow.fbo) {
//_copy_texture_to_front_buffer(shadow_atlas->depth);
storage->canvas->canvas_begin();

View file

@ -4473,6 +4473,7 @@ RID RasterizerStorageGLES3::light_create(VS::LightType p_type) {
light->omni_shadow_mode = VS::LIGHT_OMNI_SHADOW_DUAL_PARABOLOID;
light->omni_shadow_detail = VS::LIGHT_OMNI_SHADOW_DETAIL_VERTICAL;
light->directional_blend_splits = false;
light->directional_range_mode = VS::LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_STABLE;
light->reverse_cull = false;
light->version = 0;
@ -4625,6 +4626,22 @@ VS::LightDirectionalShadowMode RasterizerStorageGLES3::light_directional_get_sha
return light->directional_shadow_mode;
}
void RasterizerStorageGLES3::light_directional_set_shadow_depth_range_mode(RID p_light, VS::LightDirectionalShadowDepthRangeMode p_range_mode) {
Light *light = light_owner.getornull(p_light);
ERR_FAIL_COND(!light);
light->directional_range_mode=p_range_mode;
}
VS::LightDirectionalShadowDepthRangeMode RasterizerStorageGLES3::light_directional_get_shadow_depth_range_mode(RID p_light) const {
const Light *light = light_owner.getornull(p_light);
ERR_FAIL_COND_V(!light, VS::LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_STABLE);
return light->directional_range_mode;
}
VS::LightType RasterizerStorageGLES3::light_get_type(RID p_light) const {
const Light *light = light_owner.getornull(p_light);

View file

@ -879,6 +879,7 @@ public:
VS::LightOmniShadowMode omni_shadow_mode;
VS::LightOmniShadowDetail omni_shadow_detail;
VS::LightDirectionalShadowMode directional_shadow_mode;
VS::LightDirectionalShadowDepthRangeMode directional_range_mode;
bool directional_blend_splits;
uint64_t version;
};
@ -906,6 +907,9 @@ public:
virtual VS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light);
virtual VS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light);
virtual void light_directional_set_shadow_depth_range_mode(RID p_light, VS::LightDirectionalShadowDepthRangeMode p_range_mode);
virtual VS::LightDirectionalShadowDepthRangeMode light_directional_get_shadow_depth_range_mode(RID p_light) const;
virtual bool light_has_shadow(RID p_light) const;
virtual VS::LightType light_get_type(RID p_light) const;

File diff suppressed because it is too large Load diff

View file

@ -230,7 +230,6 @@ void Light::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_shadow_color", "get_shadow_color");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "shadow_bias", PROPERTY_HINT_RANGE, "-16,16,0.01"), "set_param", "get_param", PARAM_SHADOW_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "shadow_contact", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_CONTACT_SHADOW_SIZE);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "shadow_max_distance", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_reverse_cull_face"), "set_shadow_reverse_cull_face", "get_shadow_reverse_cull_face");
ADD_GROUP("Editor", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only");
@ -308,6 +307,18 @@ DirectionalLight::ShadowMode DirectionalLight::get_shadow_mode() const {
return shadow_mode;
}
void DirectionalLight::set_shadow_depth_range(ShadowDepthRange p_range) {
shadow_depth_range=p_range;
VS::get_singleton()->light_directional_set_shadow_depth_range_mode(light, VS::LightDirectionalShadowDepthRangeMode(p_range));
}
DirectionalLight::ShadowDepthRange DirectionalLight::get_shadow_depth_range() const {
return shadow_depth_range;
}
void DirectionalLight::set_blend_splits(bool p_enable) {
blend_splits = p_enable;
@ -324,6 +335,9 @@ void DirectionalLight::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_mode", "mode"), &DirectionalLight::set_shadow_mode);
ClassDB::bind_method(D_METHOD("get_shadow_mode"), &DirectionalLight::get_shadow_mode);
ClassDB::bind_method(D_METHOD("set_shadow_depth_range", "mode"), &DirectionalLight::set_shadow_depth_range);
ClassDB::bind_method(D_METHOD("get_shadow_depth_range"), &DirectionalLight::get_shadow_depth_range);
ClassDB::bind_method(D_METHOD("set_blend_splits", "enabled"), &DirectionalLight::set_blend_splits);
ClassDB::bind_method(D_METHOD("is_blend_splits_enabled"), &DirectionalLight::is_blend_splits_enabled);
@ -335,10 +349,15 @@ void DirectionalLight::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional_shadow_blend_splits"), "set_blend_splits", "is_blend_splits_enabled");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_normal_bias", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_bias_split_scale", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_BIAS_SPLIT_SCALE);
ADD_PROPERTY(PropertyInfo(Variant::INT, "directional_shadow_depth_range", PROPERTY_HINT_ENUM, "Stable,Optimized"), "set_shadow_depth_range", "get_shadow_depth_range");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE);
BIND_ENUM_CONSTANT(SHADOW_ORTHOGONAL);
BIND_ENUM_CONSTANT(SHADOW_PARALLEL_2_SPLITS);
BIND_ENUM_CONSTANT(SHADOW_PARALLEL_4_SPLITS);
BIND_ENUM_CONSTANT( SHADOW_DEPTH_RANGE_STABLE );
BIND_ENUM_CONSTANT( SHADOW_DEPTH_RANGE_OPTIMIZED );
}
DirectionalLight::DirectionalLight()
@ -349,6 +368,8 @@ DirectionalLight::DirectionalLight()
set_param(PARAM_SHADOW_MAX_DISTANCE, 200);
set_param(PARAM_SHADOW_BIAS_SPLIT_SCALE, 0.25);
set_shadow_mode(SHADOW_PARALLEL_4_SPLITS);
set_shadow_depth_range(SHADOW_DEPTH_RANGE_STABLE);
blend_splits = false;
}

View file

@ -133,9 +133,15 @@ public:
SHADOW_PARALLEL_4_SPLITS
};
enum ShadowDepthRange {
SHADOW_DEPTH_RANGE_STABLE = VS::LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_STABLE,
SHADOW_DEPTH_RANGE_OPTIMIZED = VS::LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_OPTIMIZED,
};
private:
bool blend_splits;
ShadowMode shadow_mode;
ShadowDepthRange shadow_depth_range;
protected:
static void _bind_methods();
@ -144,6 +150,9 @@ public:
void set_shadow_mode(ShadowMode p_mode);
ShadowMode get_shadow_mode() const;
void set_shadow_depth_range(ShadowDepthRange p_mode);
ShadowDepthRange get_shadow_depth_range() const;
void set_blend_splits(bool p_enable);
bool is_blend_splits_enabled() const;
@ -151,6 +160,7 @@ public:
};
VARIANT_ENUM_CAST(DirectionalLight::ShadowMode)
VARIANT_ENUM_CAST(DirectionalLight::ShadowDepthRange)
class OmniLight : public Light {

View file

@ -335,6 +335,9 @@ public:
virtual void light_directional_set_shadow_mode(RID p_light, VS::LightDirectionalShadowMode p_mode) = 0;
virtual void light_directional_set_blend_splits(RID p_light, bool p_enable) = 0;
virtual bool light_directional_get_blend_splits(RID p_light) const = 0;
virtual void light_directional_set_shadow_depth_range_mode(RID p_light, VS::LightDirectionalShadowDepthRangeMode p_range_mode) = 0;
virtual VS::LightDirectionalShadowDepthRangeMode light_directional_get_shadow_depth_range_mode(RID p_light) const = 0;
virtual VS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light) = 0;
virtual VS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light) = 0;

View file

@ -795,6 +795,7 @@ public:
BIND2(light_directional_set_shadow_mode, RID, LightDirectionalShadowMode)
BIND2(light_directional_set_blend_splits, RID, bool)
BIND2(light_directional_set_shadow_depth_range_mode, RID, LightDirectionalShadowDepthRangeMode)
/* PROBE API */

View file

@ -886,12 +886,55 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
float max_distance = p_cam_projection.get_z_far();
float shadow_max = VSG::storage->light_get_param(p_instance->base, VS::LIGHT_PARAM_SHADOW_MAX_DISTANCE);
if (shadow_max > 0) {
if (shadow_max > 0 && !p_cam_orthogonal) { //its impractical (and leads to unwanted behaviors) to set max distance in orthogonal camera
max_distance = MIN(shadow_max, max_distance);
}
max_distance = MAX(max_distance, p_cam_projection.get_z_near() + 0.001);
float min_distance = MIN(p_cam_projection.get_z_near(),max_distance);
float range = max_distance - p_cam_projection.get_z_near();
VS::LightDirectionalShadowDepthRangeMode depth_range_mode = VSG::storage->light_directional_get_shadow_depth_range_mode(p_instance->base);
if (depth_range_mode==VS::LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_OPTIMIZED) {
//optimize min/max
Vector<Plane> planes = p_cam_projection.get_projection_planes(p_cam_transform);
int cull_count = p_scenario->octree.cull_convex(planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, VS::INSTANCE_GEOMETRY_MASK);
Plane base(p_cam_transform.origin,-p_cam_transform.basis.get_axis(2));
//check distance max and min
bool found_items=false;
float z_max=-1e20;
float z_min=1e20;
for(int i=0;i<cull_count;i++) {
Instance *instance = instance_shadow_cull_result[i];
if (!instance->visible || !((1 << instance->base_type) & VS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows) {
continue;
}
float max,min;
instance->transformed_aabb.project_range_in_plane(base, min, max);
if (max>z_max) {
z_max=max;
}
if (min<z_min) {
z_min=min;
}
found_items=true;
}
if (found_items) {
min_distance=MAX(min_distance,z_min);
max_distance=MIN(max_distance,z_max);
}
}
float range = max_distance - min_distance;
int splits = 0;
switch (VSG::storage->light_directional_get_shadow_mode(p_instance->base)) {
@ -902,9 +945,9 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
float distances[5];
distances[0] = p_cam_projection.get_z_near();
distances[0] = min_distance;
for (int i = 0; i < splits; i++) {
distances[i + 1] = p_cam_projection.get_z_near() + VSG::storage->light_get_param(p_instance->base, VS::LightParam(VS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET + i)) * range;
distances[i + 1] = min_distance + VSG::storage->light_get_param(p_instance->base, VS::LightParam(VS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET + i)) * range;
};
distances[splits] = max_distance;
@ -984,8 +1027,6 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
{
//camera viewport stuff
//this trick here is what stabilizes the shadow (make potential jaggies to not move)
//at the cost of some wasted resolution. Still the quality increase is very well worth it
Vector3 center;
@ -1006,7 +1047,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
radius = d;
}
radius *= texture_size / (texture_size - 2.0); //add a texel by each side, so stepified texture will always fit
radius *= texture_size / (texture_size - 2.0); //add a texel by each side
if (i == 0) {
first_radius = radius;
@ -1021,12 +1062,19 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
z_max_cam = z_vec.dot(center) + radius;
z_min_cam = z_vec.dot(center) - radius;
float unit = radius * 2.0 / texture_size;
if (depth_range_mode==VS::LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_STABLE) {
//this trick here is what stabilizes the shadow (make potential jaggies to not move)
//at the cost of some wasted resolution. Still the quality increase is very well worth it
float unit = radius * 2.0 / texture_size;
x_max_cam = Math::stepify(x_max_cam, unit);
x_min_cam = Math::stepify(x_min_cam, unit);
y_max_cam = Math::stepify(y_max_cam, unit);
y_min_cam = Math::stepify(y_min_cam, unit);
}
x_max_cam = Math::stepify(x_max_cam, unit);
x_min_cam = Math::stepify(x_min_cam, unit);
y_max_cam = Math::stepify(y_max_cam, unit);
y_min_cam = Math::stepify(y_min_cam, unit);
}
//now that we now all ranges, we can proceed to make the light frustum planes, for culling octree
@ -1069,6 +1117,8 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
}
{
CameraMatrix ortho_camera;
real_t half_x = (x_max_cam - x_min_cam) * 0.5;
real_t half_y = (y_max_cam - y_min_cam) * 0.5;

View file

@ -233,6 +233,7 @@ public:
FUNC2(light_directional_set_shadow_mode, RID, LightDirectionalShadowMode)
FUNC2(light_directional_set_blend_splits, RID, bool)
FUNC2(light_directional_set_shadow_depth_range_mode, RID, LightDirectionalShadowDepthRangeMode)
/* PROBE API */

View file

@ -406,6 +406,14 @@ public:
virtual void light_directional_set_shadow_mode(RID p_light, LightDirectionalShadowMode p_mode) = 0;
virtual void light_directional_set_blend_splits(RID p_light, bool p_enable) = 0;
enum LightDirectionalShadowDepthRangeMode {
LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_STABLE,
LIGHT_DIRECTIONAL_SHADOW_DEPTH_RANGE_OPTIMIZED,
};
virtual void light_directional_set_shadow_depth_range_mode(RID p_light, LightDirectionalShadowDepthRangeMode p_range_mode) = 0;
/* PROBE API */
virtual RID reflection_probe_create() = 0;