Shadows: Convex shape shadow improvement/fixes

Fixed pixel cracking on convex shadow edges
Added convex shadow offset support
Fixed some convex shadow fringing issues
Added convex shadow demo code

# Conflicts:
#	imgui_demo.cpp
This commit is contained in:
Ben Carter 2020-07-07 15:50:07 +09:00 committed by ocornut
parent 01078a6077
commit cc7387f680
2 changed files with 222 additions and 34 deletions

View File

@ -7618,47 +7618,180 @@ static void ShowExampleAppCustomRendering(bool* p_open)
if (ImGui::BeginTabItem("Shadows")) if (ImGui::BeginTabItem("Shadows"))
{ {
static float shadow_thickness = 40.0f; static float shadow_thickness = 40.0f;
static bool shadow_filled = false;
static ImVec4 shadow_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); static ImVec4 shadow_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
static ImVec4 shape_color = ImVec4(0.9f, 0.0f, 0.0f, 1.0f); static bool shadow_filled = false;
static ImVec4 shape_color = ImVec4(0.9f, 0.6f, 0.3f, 1.0f);
static float shape_rounding = 0.0f; static float shape_rounding = 0.0f;
static ImVec4 bg_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); static ImVec2 shadow_offset(0.0f, 0.0f);
static ImVec4 background_color = ImVec4(0.5f, 0.5f, 0.7f, 1.0f);
static bool wireframe = false;
static bool aa = true;
static int poly_shape_index = 0;
ImGui::Checkbox("Shadow filled", &shadow_filled); ImGui::Checkbox("Shadow filled", &shadow_filled);
ImGui::SameLine(); ImGui::SameLine();
HelpMarker("This will fill the section behind the shape to shadow. It's often unnecessary and wasteful but provided for consistency."); HelpMarker("This will fill the section behind the shape to shadow. It's often unnecessary and wasteful but provided for consistency.");
ImGui::Checkbox("Wireframe shapes", &wireframe);
ImGui::SameLine();
HelpMarker("This draws the shapes in wireframe so you can see the shadow underneath.");
ImGui::Checkbox("Anti-aliasing", &aa);
ImGui::DragFloat("Shadow Thickness", &shadow_thickness, 1.0f, 0.0f, 100.0f, "%.02f"); ImGui::DragFloat("Shadow Thickness", &shadow_thickness, 1.0f, 0.0f, 100.0f, "%.02f");
ImGui::SliderFloat2("Offset", (float*)&shadow_offset, -32.0f, 32.0f);
ImGui::SameLine();
HelpMarker("Note that currently circles/convex shapes do not support non-zero offsets for unfilled shadows.");
ImGui::ColorEdit4("Background Color", &background_color.x);
ImGui::ColorEdit4("Shadow Color", &shadow_color.x); ImGui::ColorEdit4("Shadow Color", &shadow_color.x);
ImGui::ColorEdit4("Shape Color", &shape_color.x); ImGui::ColorEdit4("Shape Color", &shape_color.x);
ImGui::DragFloat("Shape Rounding", &shape_rounding, 1.0f, 0.0f, 20.0f, "%.02f"); ImGui::DragFloat("Shape Rounding", &shape_rounding, 1.0f, 0.0f, 20.0f, "%.02f");
ImGui::ColorEdit4("Background Color", &bg_color.x); ImGui::Combo("Convex shape", &poly_shape_index, "Shape 1\0Shape 2\0Shape 3\0Shape 4\0Shape 4 (winding reversed)");
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImDrawListFlags old_flags = draw_list->Flags;
if (aa)
draw_list->Flags |= ~ImDrawListFlags_AntiAliasedFill;
else
draw_list->Flags &= ~ImDrawListFlags_AntiAliasedFill;
// Fill a strip of background
draw_list->AddRectFilled(ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y), ImVec2(ImGui::GetCursorScreenPos().x + ImGui::GetWindowContentRegionMax().x, ImGui::GetCursorScreenPos().y + 200.0f), ImGui::GetColorU32(background_color));
// Rectangle
{ {
ImVec2 p = ImGui::GetCursorScreenPos(); ImVec2 p = ImGui::GetCursorScreenPos();
ImVec2 r1(p.x + 50.0f, p.y + 50.0f); ImVec2 r1(p.x + 50.0f, p.y + 50.0f);
ImVec2 r2(p.x + 150.0f, p.y + 150.0f); ImVec2 r2(p.x + 150.0f, p.y + 150.0f);
draw_list->AddRectFilled(p, ImVec2(p.x + 200.0f, p.y + 200.0f), ImGui::GetColorU32(bg_color));
if (shadow_filled) if (shadow_filled)
draw_list->AddShadowRectFilled(r1, r2, shadow_thickness, ImVec2(0.0f, 0.0f), ImGui::GetColorU32(shadow_color)); draw_list->AddShadowRectFilled(r1, r2, shadow_thickness, shadow_offset, ImGui::GetColorU32(shadow_color));
else else
draw_list->AddShadowRect(r1, r2, shadow_thickness, ImVec2(0.0f, 0.0f), ImGui::GetColorU32(shadow_color), shape_rounding); draw_list->AddShadowRect(r1, r2, shadow_thickness, shadow_offset, ImGui::GetColorU32(shadow_color), shape_rounding);
draw_list->AddRectFilled(r1, r2, ImGui::GetColorU32(shape_color), shape_rounding); if (wireframe)
draw_list->AddRect(r1, r2, ImGui::GetColorU32(shape_color), shape_rounding);
else
draw_list->AddRectFilled(r1, r2, ImGui::GetColorU32(shape_color), shape_rounding);
ImGui::Dummy(ImVec2(200.0f, 200.0f)); ImGui::Dummy(ImVec2(200.0f, 200.0f));
} }
ImGui::SameLine();
// Circle
{ {
ImVec2 p = ImGui::GetCursorScreenPos(); ImVec2 p = ImGui::GetCursorScreenPos();
ImVec2 center(p.x + 100.0f, p.y + 100.0f); float off = 10.0f;
float radius = 50.0f; ImVec2 r1(p.x + 50.0f + off, p.y + 50.0f + off);
draw_list->AddRectFilled(p, ImVec2(p.x + 200.0f, p.y + 200.0f), ImGui::GetColorU32(bg_color)); ImVec2 r2(p.x + 150.0f - off, p.y + 150.0f - off);
ImVec2 c(p.x + 100.0f, p.y + 100.0f);
if (shadow_filled) if (shadow_filled)
draw_list->AddShadowCircleFilled(center, radius, shadow_thickness, ImVec2(0.0f, 0.0f), ImGui::GetColorU32(shadow_color), 0); draw_list->AddShadowCircleFilled(c, 50.0f, shadow_thickness, shadow_offset, ImGui::GetColorU32(shadow_color));
else else
draw_list->AddShadowCircle(center, radius, shadow_thickness, ImVec2(0.0f, 0.0f), ImGui::GetColorU32(shadow_color), 0); draw_list->AddShadowCircle(c, 50.0f, shadow_thickness, ImVec2(0.0f, 0.0f), ImGui::GetColorU32(shadow_color)); // Offset forced to zero here because it isn't supported
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32(shape_color), 0); if (wireframe)
draw_list->AddCircle(c, 50.0f, ImGui::GetColorU32(shape_color), 0);
else
draw_list->AddCircleFilled(c, 50.0f, ImGui::GetColorU32(shape_color), 0);
ImGui::Dummy(ImVec2(200.0f, 200.0f)); ImGui::Dummy(ImVec2(200.0f, 200.0f));
} }
ImGui::SameLine();
// Convex shape
{
ImVec2 pos = ImGui::GetCursorScreenPos();
const ImVec2 poly_centre(pos.x + 50.0f, pos.y + 100.0f);
ImVec2* poly_points;
int num_poly_points;
switch (poly_shape_index)
{
default:
case 0:
{
ImVec2 poly_point_data[] =
{
ImVec2(poly_centre.x - 32.0f, poly_centre.y),
ImVec2(poly_centre.x - 24.0f, poly_centre.y + 24.0f),
ImVec2(poly_centre.x, poly_centre.y + 32.0f),
ImVec2(poly_centre.x + 24.0f, poly_centre.y + 24.0f),
ImVec2(poly_centre.x + 32.0f, poly_centre.y),
ImVec2(poly_centre.x + 24.0f, poly_centre.y - 24.0f),
ImVec2(poly_centre.x, poly_centre.y - 32.0f),
ImVec2(poly_centre.x - 32.0f, poly_centre.y - 32.0f)
};
poly_points = poly_point_data;
num_poly_points = 8;
break;
}
case 1:
{
ImVec2 poly_point_data[] =
{
ImVec2(poly_centre.x + 40.0f, poly_centre.y - 20.0f),
ImVec2(poly_centre.x, poly_centre.y + 32.0f),
ImVec2(poly_centre.x - 24.0f, poly_centre.y - 32.0f)
};
poly_points = poly_point_data;
num_poly_points = 3;
break;
}
case 2:
{
ImVec2 poly_point_data[] =
{
ImVec2(poly_centre.x - 32.0f, poly_centre.y),
ImVec2(poly_centre.x, poly_centre.y + 32.0f),
ImVec2(poly_centre.x + 32.0f, poly_centre.y),
ImVec2(poly_centre.x, poly_centre.y - 32.0f)
};
poly_points = poly_point_data;
num_poly_points = 4;
break;
}
case 3:
{
ImVec2 poly_point_data[] =
{
ImVec2(poly_centre.x - 4.0f, poly_centre.y - 20.0f),
ImVec2(poly_centre.x + 12.0f, poly_centre.y + 2.0f),
ImVec2(poly_centre.x + 8.0f, poly_centre.y + 16.0f),
ImVec2(poly_centre.x, poly_centre.y + 32.0f),
ImVec2(poly_centre.x - 16.0f, poly_centre.y - 32.0f)
};
poly_points = poly_point_data;
num_poly_points = 5;
break;
}
case 4: // Same as test case 3 but with reversed winding
{
ImVec2 poly_point_data[] =
{
ImVec2(poly_centre.x - 16.0f, poly_centre.y - 32.0f),
ImVec2(poly_centre.x, poly_centre.y + 32.0f),
ImVec2(poly_centre.x + 8.0f, poly_centre.y + 16.0f),
ImVec2(poly_centre.x + 12.0f, poly_centre.y + 2.0f),
ImVec2(poly_centre.x - 4.0f, poly_centre.y - 20.0f)
};
poly_points = poly_point_data;
num_poly_points = 5;
break;
}
}
if (shadow_filled)
draw_list->AddShadowConvexPolyFilled(poly_points, num_poly_points, shadow_thickness, shadow_offset, ImGui::GetColorU32(shadow_color));
else
draw_list->AddShadowConvexPoly(poly_points, num_poly_points, shadow_thickness, ImVec2(0.0f, 0.0f), ImGui::GetColorU32(shadow_color)); // Offset forced to zero because it isn't supported
if (wireframe)
draw_list->AddPolyline(poly_points, num_poly_points, ImGui::GetColorU32(shape_color), true, 1.0f);
else
draw_list->AddConvexPolyFilled(poly_points, num_poly_points, ImGui::GetColorU32(shape_color));
ImGui::Dummy(ImVec2(200.0f, 200.0f));
}
draw_list->Flags = old_flags;
ImGui::EndTabItem(); ImGui::EndTabItem();
} }

View File

@ -2168,7 +2168,15 @@ void ImDrawList::AddShadowRect(const ImVec2& p_min, const ImVec2& p_max, float s
// Add a shadow for a convex shape described by points and num_points // Add a shadow for a convex shape described by points and num_points
static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col, bool is_filled) static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col, bool is_filled)
{ {
IM_UNUSED(offset); // Offset not currently supported IM_ASSERT((is_filled || (ImLengthSqr(offset) < 0.00001f)) && "Drawing circle/convex shape shadows with no center fill and an offset is not currently supported");
IM_ASSERT(num_points >= 3);
// Calculate poly vertex order
int vertex_winding = (((points[0].x * (points[1].y - points[2].y)) + (points[1].x * (points[2].y - points[0].y)) + (points[2].x * (points[0].y - points[1].y))) < 0.0f) ? -1 : 1;
// If we're using anti-aliasing, then inset the shadow by 0.5 pixels to avoid unpleasant fringing artifacts
const bool use_inset_distance = (draw_list->Flags & ImDrawListFlags_AntiAliasedFill) && (!is_filled);
const float inset_distance = 0.5f;
const ImVec4 uvs = draw_list->_Data->ShadowRectUvs[9]; const ImVec4 uvs = draw_list->_Data->ShadowRectUvs[9];
@ -2191,16 +2199,54 @@ static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points,
// Normalize a vector // Normalize a vector
#define NORMALIZE(vec) ((vec) / ImLength((vec), 0.001f)) #define NORMALIZE(vec) ((vec) / ImLength((vec), 0.001f))
const int required_stack_mem = (num_edges * sizeof(ImVec2)) + (num_edges * sizeof(float));
const ImU8* base_mem_for_normals_and_edges = (ImU8*)alloca(required_stack_mem);
ImU8* mem_for_normals_and_edges = (ImU8*)base_mem_for_normals_and_edges;
// Calculate edge normals // Calculate edge normals
ImVec2* edge_normals = (ImVec2*)alloca(num_edges * sizeof(ImVec2)); ImVec2* edge_normals = (ImVec2*)mem_for_normals_and_edges;
mem_for_normals_and_edges += num_edges * sizeof(ImVec2);
for (int edge_index = 0; edge_index < num_edges; edge_index++) for (int edge_index = 0; edge_index < num_edges; edge_index++)
{ {
ImVec2 edge_start = points[edge_index]; ImVec2 edge_start = points[edge_index]; // No need to apply offset here because the normal is unaffected
ImVec2 edge_end = points[(edge_index + 1) % num_edges]; ImVec2 edge_end = points[(edge_index + 1) % num_edges];
ImVec2 edge_normal = NORMALIZE(ImVec2(edge_end.y - edge_start.y, -(edge_end.x - edge_start.x))); ImVec2 edge_normal = NORMALIZE(ImVec2(edge_end.y - edge_start.y, -(edge_end.x - edge_start.x)));
edge_normals[edge_index] = edge_normal; edge_normals[edge_index] = edge_normal * (float)vertex_winding; // Flip normals for reverse winding
}
// Pre-calculate edge scales
// We need to do this because we need the edge strips to have widths that match up with the corner sections, otherwise pixel cracking can occur along the boundaries
float* edge_size_scales = (float*)mem_for_normals_and_edges;
mem_for_normals_and_edges += num_edges * sizeof(float);
IM_ASSERT_PARANOID(mem_for_normals_and_edges == (base_mem_for_normals_and_edges + required_stack_mem)); // Check we used exactly what we allocated
{
ImVec2 prev_edge_normal = edge_normals[num_edges - 1];
for (int edge_index = 0; edge_index < num_edges; edge_index++)
{
ImVec2 edge_normal = edge_normals[edge_index];
float cos_angle_coverage = ImDot(edge_normal, prev_edge_normal);
if (cos_angle_coverage < 0.999999f)
{
float angle_coverage = ImAcos(cos_angle_coverage);
// If we are covering more than 90 degrees we need an intermediate vertex to stop the required expansion tending towards infinity, and thus the effective angle will be halved
if (cos_angle_coverage <= 0.0f)
angle_coverage *= 0.5f;
edge_size_scales[edge_index] = 1.0f / ImCos(angle_coverage * 0.5f); // How much we need to expand our size by to avoid clipping the corner of the texture off
}
else
{
edge_size_scales[edge_index] = 1.0f; // No corner, thus default scale
}
prev_edge_normal = edge_normal;
}
} }
const int max_vertices = (4 + (3 * 2) + (is_filled ? 1 : 0)) * num_edges; // 4 vertices per edge plus 3*2 for potentially two corner triangles, plus one per vertex for fill const int max_vertices = (4 + (3 * 2) + (is_filled ? 1 : 0)) * num_edges; // 4 vertices per edge plus 3*2 for potentially two corner triangles, plus one per vertex for fill
@ -2210,14 +2256,22 @@ static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points,
ImDrawVert* vtx_write = draw_list->_VtxWritePtr; ImDrawVert* vtx_write = draw_list->_VtxWritePtr;
ImDrawIdx current_idx = (ImDrawIdx)draw_list->_VtxCurrentIdx; ImDrawIdx current_idx = (ImDrawIdx)draw_list->_VtxCurrentIdx;
//ImVec2 previous_edge_start = points[0]; ImVec2 previous_edge_start = points[0] + offset;
ImVec2 edge_start = points[0];
ImVec2 prev_edge_normal = edge_normals[num_edges - 1]; ImVec2 prev_edge_normal = edge_normals[num_edges - 1];
ImVec2 edge_start = points[0] + offset;
if (use_inset_distance)
edge_start -= NORMALIZE(edge_normals[0] + prev_edge_normal) * inset_distance;
for (int edge_index = 0; edge_index < num_edges; edge_index++) for (int edge_index = 0; edge_index < num_edges; edge_index++)
{ {
const ImVec2 edge_end = points[(edge_index + 1) % num_edges]; ImVec2 edge_end = points[(edge_index + 1) % num_edges] + offset;
ImVec2 edge_normal = edge_normals[edge_index]; ImVec2 edge_normal = edge_normals[edge_index];
const float size_scale_start = edge_size_scales[edge_index];
const float size_scale_end = edge_size_scales[(edge_index + 1) % num_edges];
if (use_inset_distance)
edge_end -= NORMALIZE(edge_normals[(edge_index + 1) % num_edges] + edge_normal) * inset_distance;
// Add corner section // Add corner section
float cos_angle_coverage = ImDot(edge_normal, prev_edge_normal); float cos_angle_coverage = ImDot(edge_normal, prev_edge_normal);
@ -2241,14 +2295,12 @@ static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points,
// Calculate UV for the section of the curved texture // Calculate UV for the section of the curved texture
float angle_coverage = ImAcos(cos_angle_coverage); const float angle_coverage = ImAcos(cos_angle_coverage);
float sin_angle_coverage = ImSin(angle_coverage); const float sin_angle_coverage = ImSin(angle_coverage);
float size_scale = 1.0f / ImCos(angle_coverage * 0.5f); // How much we need to expand our size by to avoid clipping the corner of the texture off
ImVec2 edge_delta = solid_to_edge_delta_texels; ImVec2 edge_delta = solid_to_edge_delta_texels;
edge_delta *= size_scale; edge_delta *= size_scale_start;
ImVec2 rotated_edge_delta = ImVec2((edge_delta.x * cos_angle_coverage) + (edge_delta.y * sin_angle_coverage), (edge_delta.x * sin_angle_coverage) + (edge_delta.y * cos_angle_coverage)); ImVec2 rotated_edge_delta = ImVec2((edge_delta.x * cos_angle_coverage) + (edge_delta.y * sin_angle_coverage), (edge_delta.x * sin_angle_coverage) + (edge_delta.y * cos_angle_coverage));
@ -2261,7 +2313,7 @@ static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points,
ImVec2 expanded_edge_uv = solid_uv + edge_delta; ImVec2 expanded_edge_uv = solid_uv + edge_delta;
ImVec2 other_edge_uv = solid_uv + rotated_edge_delta; // Rotated UV to encompass the necessary section of the curve ImVec2 other_edge_uv = solid_uv + rotated_edge_delta; // Rotated UV to encompass the necessary section of the curve
float expanded_thickness = shadow_thickness * size_scale; float expanded_thickness = shadow_thickness * size_scale_start;
// Add a triangle to fill the corner // Add a triangle to fill the corner
ImVec2 outer_edge_start = edge_start + (prev_edge_normal * expanded_thickness); ImVec2 outer_edge_start = edge_start + (prev_edge_normal * expanded_thickness);
@ -2285,14 +2337,17 @@ static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points,
if (edge_length > 0.00001f) // Don't try and process degenerate edges if (edge_length > 0.00001f) // Don't try and process degenerate edges
{ {
ImVec2 outer_edge_start = edge_start + (edge_normal * shadow_thickness); ImVec2 outer_edge_start = edge_start + (edge_normal * shadow_thickness * size_scale_start);
ImVec2 outer_edge_end = edge_end + (edge_normal * shadow_thickness); ImVec2 outer_edge_end = edge_end + (edge_normal * shadow_thickness * size_scale_end);
ImVec2 scaled_edge_uv_start = solid_uv + ((edge_uv - solid_uv) * size_scale_start);
ImVec2 scaled_edge_uv_end = solid_uv + ((edge_uv - solid_uv) * size_scale_end);
// Write vertices, inner first, then outer // Write vertices, inner first, then outer
vtx_write->pos = edge_start; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++; vtx_write->pos = edge_start; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
vtx_write->pos = edge_end; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++; vtx_write->pos = edge_end; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
vtx_write->pos = outer_edge_end; vtx_write->col = col; vtx_write->uv = edge_uv; vtx_write++; vtx_write->pos = outer_edge_end; vtx_write->col = col; vtx_write->uv = scaled_edge_uv_end; vtx_write++;
vtx_write->pos = outer_edge_start; vtx_write->col = col; vtx_write->uv = edge_uv; vtx_write++; vtx_write->pos = outer_edge_start; vtx_write->col = col; vtx_write->uv = scaled_edge_uv_start; vtx_write++;
*(idx_write++) = current_idx; *(idx_write++) = current_idx;
*(idx_write++) = current_idx + 1; *(idx_write++) = current_idx + 1;
@ -2311,7 +2366,7 @@ static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points,
// Add vertices // Add vertices
for (int edge_index = 0; edge_index < num_edges; edge_index++) for (int edge_index = 0; edge_index < num_edges; edge_index++)
{ {
vtx_write->pos = points[edge_index]; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++; vtx_write->pos = points[edge_index] + offset; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
} }
// Add tris // Add tris
@ -3694,7 +3749,7 @@ static void ImFontAtlasBuildRenderShadowTexData(ImFontAtlas* atlas)
for (int x = 0; x < size; x++) for (int x = 0; x < size; x++)
{ {
float dist = DistanceFromPoint(ImVec2((float)x, (float)y), center_point); float dist = DistanceFromPoint(ImVec2((float)x, (float)y), center_point);
float alpha = 1.0f - ImMin(ImMax((float)dist /*+ shadow_cfg->TexDistanceFieldOffset*/, 0.0f) / ImMax((float)shadow_cfg->TexCornerSize /*+ shadow_cfg->TexDistanceFieldOffset*/, 0.001f), 1.0f); float alpha = 1.0f - ImMin(ImMax((float)dist + shadow_cfg->TexDistanceFieldOffset, 0.0f) / ImMax((float)shadow_cfg->TexCornerSize + shadow_cfg->TexDistanceFieldOffset, 0.001f), 1.0f);
alpha = ImPow(alpha, shadow_cfg->TexFalloffPower); // Apply power curve to give a nicer falloff alpha = ImPow(alpha, shadow_cfg->TexFalloffPower); // Apply power curve to give a nicer falloff
tex_data[x + (y * size)] = alpha; tex_data[x + (y * size)] = alpha;
} }