Overlay to show concave/convex edges #115335

Open
Eugene-Kuznetsov wants to merge 1 commits from Eugene-Kuznetsov/blender:ek_angle_overlay into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
16 changed files with 360 additions and 7 deletions

View File

@ -776,10 +776,13 @@ set(GLSL_SRC
engines/overlay/shaders/overlay_edit_lattice_wire_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_frag.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_v2_frag.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_v2_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_common_lib.glsl
engines/overlay/shaders/overlay_edit_mesh_depth_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_frag.glsl
engines/overlay/shaders/overlay_edit_mesh_geom.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_v2_geom.glsl
engines/overlay/shaders/overlay_edit_mesh_normal_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_skin_root_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_vert.glsl

View File

@ -138,6 +138,17 @@ void OVERLAY_edit_mesh_cache_init(OVERLAY_Data *vedata)
pd->edit_mesh_analysis_grp = grp = DRW_shgroup_create(sh, psl->edit_mesh_analysis_ps);
DRW_shgroup_uniform_texture(grp, "weightTex", G_draw.weight_ramp);
}
{
/* Mesh Analysis Pass */
state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_ALPHA;
DRW_PASS_CREATE(psl->edit_mesh_analysis_v2_ps, state | pd->clipping_state);
sh = OVERLAY_shader_edit_mesh_analysis_v2();
pd->edit_mesh_analysis_v2_grp = grp = DRW_shgroup_create(sh, psl->edit_mesh_analysis_v2_ps);
DRW_shgroup_uniform_texture(grp, "weightTex", G_draw.weight_ramp);
}
/* Run Twice for in-front passes. */
for (int i = 0; i < 2; i++) {
GPUShader *edge_sh = OVERLAY_shader_edit_mesh_edge(!select_vert);
@ -288,9 +299,19 @@ void OVERLAY_edit_mesh_cache_populate(OVERLAY_Data *vedata, Object *ob)
bool lnormals_do = (pd->edit_mesh.flag & V3D_OVERLAY_EDIT_LOOP_NORMALS) != 0;
if (do_show_mesh_analysis && !pd->xray_enabled) {
geom = DRW_cache_mesh_surface_mesh_analysis_get(ob);
if (geom) {
DRW_shgroup_call_no_cull(pd->edit_mesh_analysis_grp, geom, ob);
const DRWContextState *draw_ctx = DRW_context_state_get();
auto type = draw_ctx->scene->toolsettings->statvis.type;
if (type == SCE_STATVIS_ANGLE || type == SCE_STATVIS_SHARP) {
geom = DRW_cache_mesh_surface_mesh_analysis_v2_get(ob);
if (geom) {
DRW_shgroup_call_no_cull(pd->edit_mesh_analysis_v2_grp, geom, ob);
}
}
else {
geom = DRW_cache_mesh_surface_mesh_analysis_get(ob);
if (geom) {
DRW_shgroup_call_no_cull(pd->edit_mesh_analysis_grp, geom, ob);
}
}
}
@ -362,6 +383,7 @@ void OVERLAY_edit_mesh_draw(OVERLAY_Data *vedata)
}
DRW_draw_pass(psl->edit_mesh_analysis_ps);
DRW_draw_pass(psl->edit_mesh_analysis_v2_ps);
DRW_draw_pass(psl->edit_mesh_depth_ps[NOT_IN_FRONT]);

View File

@ -84,6 +84,7 @@ struct OVERLAY_PassList {
DRWPass *edit_curves_handles_ps;
DRWPass *edit_curves_lines_ps[2];
DRWPass *edit_mesh_analysis_ps;
DRWPass *edit_mesh_analysis_v2_ps;
DRWPass *edit_mesh_normals_ps;
DRWPass *edit_particle_ps;
DRWPass *edit_text_cursor_ps;
@ -265,6 +266,7 @@ struct OVERLAY_PrivateData {
DRWShadingGroup *edit_mesh_skin_roots_grp[2];
DRWShadingGroup *edit_mesh_normals_grp;
DRWShadingGroup *edit_mesh_analysis_grp;
DRWShadingGroup *edit_mesh_analysis_v2_grp;
DRWShadingGroup *edit_particle_strand_grp;
DRWShadingGroup *edit_particle_point_grp;
DRWShadingGroup *edit_text_cursor_grp;
@ -746,6 +748,7 @@ GPUShader *OVERLAY_shader_edit_gpencil_wire();
GPUShader *OVERLAY_shader_edit_lattice_point();
GPUShader *OVERLAY_shader_edit_lattice_wire();
GPUShader *OVERLAY_shader_edit_mesh_analysis();
GPUShader *OVERLAY_shader_edit_mesh_analysis_v2();
GPUShader *OVERLAY_shader_edit_mesh_depth();
GPUShader *OVERLAY_shader_edit_mesh_edge(bool use_flat_interp);
GPUShader *OVERLAY_shader_edit_mesh_face();

View File

@ -52,6 +52,7 @@ struct OVERLAY_Shaders {
GPUShader *edit_mesh_normals;
GPUShader *edit_mesh_fnormals;
GPUShader *edit_mesh_analysis;
GPUShader *edit_mesh_analysis_v2;
GPUShader *edit_particle_strand;
GPUShader *edit_particle_point;
GPUShader *edit_uv_verts;
@ -465,6 +466,18 @@ GPUShader *OVERLAY_shader_edit_mesh_analysis()
return sh_data->edit_mesh_analysis;
}
GPUShader *OVERLAY_shader_edit_mesh_analysis_v2()
{
const DRWContextState *draw_ctx = DRW_context_state_get();
OVERLAY_Shaders *sh_data = &e_data.sh_data[draw_ctx->sh_cfg];
if (!sh_data->edit_mesh_analysis_v2) {
sh_data->edit_mesh_analysis_v2 = GPU_shader_create_from_info_name(
(draw_ctx->sh_cfg == GPU_SHADER_CFG_CLIPPED) ? "overlay_edit_mesh_analysis_v2_clipped" :
"overlay_edit_mesh_analysis_v2");
}
return sh_data->edit_mesh_analysis_v2;
}
GPUShader *OVERLAY_shader_edit_mesh_skin_root()
{
const DRWContextState *draw_ctx = DRW_context_state_get();

View File

@ -178,6 +178,26 @@ GPU_SHADER_CREATE_INFO(overlay_edit_mesh_analysis)
.fragment_source("overlay_edit_mesh_analysis_frag.glsl")
.additional_info("draw_modelmat");
GPU_SHADER_INTERFACE_INFO(overlay_edit_mesh_analysis_v2_iface, "geometry_in")
.smooth(Type::VEC4, "weightColor")
.flat(Type::IVEC2, "vid");
GPU_SHADER_INTERFACE_INFO(overlay_edit_mesh_analysis_geo_out_v2_iface, "geometry_out")
.smooth(Type::VEC4, "weightColorGeo");
GPU_SHADER_CREATE_INFO(overlay_edit_mesh_analysis_v2)
.do_static_compilation(true)
.vertex_in(0, Type::VEC3, "pos")
.vertex_in(1, Type::VEC3, "weight")
.vertex_out(overlay_edit_mesh_analysis_v2_iface)
.geometry_out(overlay_edit_mesh_analysis_geo_out_v2_iface)
.sampler(0, ImageType::FLOAT_1D, "weightTex")
.fragment_out(0, Type::VEC4, "fragColor")
.vertex_source("overlay_edit_mesh_analysis_v2_vert.glsl")
.fragment_source("overlay_edit_mesh_analysis_v2_frag.glsl")
.geometry_layout(PrimitiveIn::TRIANGLES, PrimitiveOut::TRIANGLE_STRIP, 15)
.geometry_source("overlay_edit_mesh_analysis_v2_geom.glsl")
.additional_info("draw_modelmat");
GPU_SHADER_CREATE_INFO(overlay_edit_mesh_skin_root)
.do_static_compilation(true)
.vertex_in(0, Type::VEC3, "pos")
@ -231,6 +251,10 @@ GPU_SHADER_CREATE_INFO(overlay_edit_mesh_analysis_clipped)
.do_static_compilation(true)
.additional_info("overlay_edit_mesh_analysis", "drw_clipped");
GPU_SHADER_CREATE_INFO(overlay_edit_mesh_analysis_v2_clipped)
.do_static_compilation(true)
.additional_info("overlay_edit_mesh_analysis_v2", "drw_clipped");
GPU_SHADER_CREATE_INFO(overlay_edit_mesh_skin_root_clipped)
.do_static_compilation(true)
.additional_info("overlay_edit_mesh_skin_root", "drw_clipped");

View File

@ -0,0 +1,8 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
fragColor = geometry_out.weightColorGeo;
}

View File

@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2018-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
void draw_edge(vec4 a, vec4 b, vec4 c, vec4 color)
{
vec4 trans = c - (a + b) * 0.5f;
vec4 eps = vec4(0.0f, 0.0f, -1e-5f, 0.0f);
gl_Position = a;
geometry_out.weightColorGeo = color;
gpu_EmitVertex();
gl_Position = a * 0.80f + b * 0.10f + c * 0.10f + eps;
geometry_out.weightColorGeo = color;
gpu_EmitVertex();
gl_Position = b;
geometry_out.weightColorGeo = color;
gpu_EmitVertex();
gl_Position = b * 0.80f + a * 0.10f + c * 0.10f + eps;
geometry_out.weightColorGeo = color;
gpu_EmitVertex();
EndPrimitive();
}
void main(void)
{
vec4 gray = vec4(0.25f, 0.25f, 0.25f, 1.0f);
vec4 red = vec4(1.0f, 0.0f, 0.0f, 1.0f);
vec4 green = vec4(0.0f, 1.0f, 0.0f, 1.0f);
vec4 w[3] = {gray, gray, gray};
ivec2 vid0 = geometry_in[0].vid;
ivec2 vid1 = geometry_in[1].vid;
ivec2 vid2 = geometry_in[2].vid;
gl_Position = gl_in[0].gl_Position;
geometry_out.weightColorGeo = gray;
gpu_EmitVertex();
gl_Position = gl_in[1].gl_Position;
geometry_out.weightColorGeo = gray;
gpu_EmitVertex();
gl_Position = gl_in[2].gl_Position;
geometry_out.weightColorGeo = gray;
gpu_EmitVertex();
EndPrimitive();
if (vid0[1] == vid1[0])
draw_edge(gl_in[0].gl_Position,
gl_in[1].gl_Position,
gl_in[2].gl_Position,
geometry_in[0].weightColor);
if (vid1[1] == vid2[0])
draw_edge(gl_in[1].gl_Position,
gl_in[2].gl_Position,
gl_in[0].gl_Position,
geometry_in[1].weightColor);
if (vid2[1] == vid0[0])
draw_edge(gl_in[2].gl_Position,
gl_in[0].gl_Position,
gl_in[1].gl_Position,
geometry_in[2].weightColor);
}

View File

@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2016-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
vec3 hsv_to_rgb(vec3 hsv)
{
vec3 nrgb = abs(hsv.x * 6.0 - vec3(3.0, 2.0, 4.0)) * vec3(1, -1, -1) + vec3(-1, 2, 2);
nrgb = clamp(nrgb, 0.0, 1.0);
return ((nrgb - 1.0) * hsv.y + 1.0) * hsv.z;
}
vec3 weight_to_rgb(float x)
{
if (x >= 1.0f) // angle overlay
{
x -= 1.5f;
float t;
if (x > -0.001f && x < 0.001f)
return hsv_to_rgb(vec3(0.333, 0, 0.25));
else {
t = x;
}
float h = clamp(0.333 + t + (t > 0 ? 0.20f : -0.20f), 0, 0.666);
float s = clamp((t > 0 ? t : -t) * 6, 0.10, 1);
float v = 0.25;
return hsv_to_rgb(vec3(h, s, v));
}
// sharp overlay
if (x < 0.0) {
/* Minimum color, gray */
return vec3(0.25, 0.25, 0.25);
}
return texture(weightTex, x).rgb;
}
void main()
{
GPU_INTEL_VERTEX_SHADER_WORKAROUND
vec3 world_pos = point_object_to_world(pos);
gl_Position = point_world_to_ndc(world_pos);
geometry_in.weightColor = vec4(weight_to_rgb(weight.x), 1);
geometry_in.vid.x = floatBitsToInt(weight.y);
if (weight.x < 0) {
/* sharp mode, unmarked edge */
geometry_in.vid.y = -1;
}
else {
geometry_in.vid.y = floatBitsToInt(weight.z);
}
view_clipping_distances(world_pos);
}

View File

@ -2983,6 +2983,13 @@ blender::gpu::Batch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob)
return DRW_mesh_batch_cache_get_edit_mesh_analysis(static_cast<Mesh *>(ob->data));
}
blender::gpu::Batch *DRW_cache_mesh_surface_mesh_analysis_v2_get(Object *ob)
{
using namespace blender::draw;
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_edit_mesh_analysis_v2(static_cast<Mesh *>(ob->data));
}
blender::gpu::Batch *DRW_cache_mesh_surface_viewer_attribute_get(Object *ob)
{
using namespace blender::draw;

View File

@ -174,6 +174,7 @@ blender::gpu::Batch *DRW_cache_mesh_surface_vertpaint_get(Object *ob);
blender::gpu::Batch *DRW_cache_mesh_surface_sculptcolors_get(Object *ob);
blender::gpu::Batch *DRW_cache_mesh_surface_weights_get(Object *ob);
blender::gpu::Batch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob);
blender::gpu::Batch *DRW_cache_mesh_surface_mesh_analysis_v2_get(Object *ob);
blender::gpu::Batch *DRW_cache_mesh_face_wireframe_get(Object *ob);
blender::gpu::Batch *DRW_cache_mesh_surface_viewer_attribute_get(Object *ob);

View File

@ -134,6 +134,7 @@ struct MeshBatchList {
gpu::Batch *surface;
gpu::Batch *surface_weights;
/* Edit mode */
<<<<<<< HEAD
gpu::Batch *edit_triangles;
gpu::Batch *edit_vertices;
gpu::Batch *edit_edges;
@ -142,6 +143,17 @@ struct MeshBatchList {
gpu::Batch *edit_fdots;
gpu::Batch *edit_mesh_analysis;
gpu::Batch *edit_skin_roots;
=======
GPUBatch *edit_triangles;
GPUBatch *edit_vertices;
GPUBatch *edit_edges;
GPUBatch *edit_vnor;
GPUBatch *edit_lnor;
GPUBatch *edit_fdots;
GPUBatch *edit_mesh_analysis;
GPUBatch *edit_mesh_analysis_v2;
GPUBatch *edit_skin_roots;
>>>>>>> 5559ab6f6de (Edge painting overlay shader)
/* Edit UVs */
gpu::Batch *edituv_faces_stretch_area;
gpu::Batch *edituv_faces_stretch_angle;

View File

@ -274,6 +274,7 @@ blender::gpu::Batch *DRW_mesh_batch_cache_get_edituv_facedots(Object *object, Me
blender::gpu::Batch *DRW_mesh_batch_cache_get_uv_edges(Object *object, Mesh *mesh);
blender::gpu::Batch *DRW_mesh_batch_cache_get_edit_mesh_analysis(Mesh *mesh);
blender::gpu::Batch *DRW_mesh_batch_cache_get_edit_mesh_analysis_v2(Mesh *mesh);
/** \} */

View File

@ -952,6 +952,13 @@ gpu::Batch *DRW_mesh_batch_cache_get_edit_mesh_analysis(Mesh *mesh)
return DRW_batch_request(&cache.batch.edit_mesh_analysis);
}
GPUBatch *DRW_mesh_batch_cache_get_edit_mesh_analysis_v2(Mesh *mesh)
{
MeshBatchCache &cache = *mesh_batch_cache_get(mesh);
mesh_batch_cache_add_request(cache, MBC_EDIT_MESH_ANALYSIS);
return DRW_batch_request(&cache.batch.edit_mesh_analysis_v2);
}
void DRW_mesh_get_attributes(const Object *object,
const Mesh *mesh,
const GPUMaterial *const *gpumat_array,
@ -1626,6 +1633,11 @@ void DRW_mesh_batch_cache_create_requested(TaskGraph *task_graph,
DRW_vbo_request(cache.batch.edit_mesh_analysis, &mbuflist->vbo.pos);
DRW_vbo_request(cache.batch.edit_mesh_analysis, &mbuflist->vbo.mesh_analysis);
}
if (DRW_batch_requested(cache.batch.edit_mesh_analysis_v2, GPU_PRIM_TRIS)) {
DRW_ibo_request(cache.batch.edit_mesh_analysis_v2, &mbuflist->ibo.tris);
DRW_vbo_request(cache.batch.edit_mesh_analysis_v2, &mbuflist->vbo.pos_nor);
DRW_vbo_request(cache.batch.edit_mesh_analysis_v2, &mbuflist->vbo.mesh_analysis);
}
/* Per Material */
assert_deps_valid(

View File

@ -32,12 +32,17 @@ static void extract_mesh_analysis_init(const MeshRenderData &mr,
void * /*tls_data*/)
{
gpu::VertBuf *vbo = static_cast<gpu::VertBuf *>(buf);
static GPUVertFormat format = {0};
static GPUVertFormat format = {0}, format_v2 = {0};
if (format.attr_len == 0) {
GPU_vertformat_attr_add(&format, "weight", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
GPU_vertformat_attr_add(&format_v2, "weight", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
}
GPU_vertbuf_init_with_format(vbo, &format);
if (mr.toolsettings->statvis.type == SCE_STATVIS_ANGLE ||
mr.toolsettings->statvis.type == SCE_STATVIS_SHARP)
GPU_vertbuf_init_with_format(vbo, &format_v2);
else
GPU_vertbuf_init_with_format(vbo, &format);
GPU_vertbuf_data_alloc(vbo, mr.corners_num);
}
@ -486,7 +491,7 @@ BLI_INLINE float sharp_remap(float fac, float min, float /*max*/, float minmax_i
/* important not '>=' */
if (fac > min) {
fac = (fac - min) * minmax_irange;
CLAMP(fac, 0.0f, 1.0f);
CLAMP(fac, 0.001f, 0.999f);
}
else {
/* fallback */
@ -589,6 +594,116 @@ static void statvis_calc_sharp(const MeshRenderData &mr, float *r_sharp)
MEM_freeN(vert_angles);
}
BLI_INLINE float angle_remap(float angle)
{
angle /= 2 * 3.1415926f;
CLAMP(angle, -0.4999f, 0.4999f);
return 1.5f + angle;
}
struct angle_data {
float angle;
int vs[2];
};
static void statvis_calc_angle(const MeshRenderData &mr, float *data, bool sharp)
{
BMEditMesh *em = mr.edit_bmesh;
const MeshStatVis *statvis = &mr.toolsettings->statvis;
const float min = statvis->sharp_min;
const float max = statvis->sharp_max;
const float minmax_irange = 1.0f / (max - min);
angle_data *r_angle = (angle_data *)data;
if (mr.extract_type == MR_EXTRACT_BMESH) {
for (int l_index = 0; l_index < mr.loop_len; l_index++) {
r_angle[l_index].angle = 0.0f;
r_angle[l_index].vs[0] = -1;
r_angle[l_index].vs[1] = -1;
}
BMIter iter;
BMesh *bm = em->bm;
BMFace *efa;
BMEdge *e;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
BMEdge *e = l_iter->e;
int l_index = BM_elem_index_get(l_iter);
int v_index = BM_elem_index_get(l_iter->v);
float angle = BM_edge_calc_face_angle_signed_ex(e, 0.0f);
if (sharp)
angle = sharp_remap(angle, min, max, minmax_irange);
else
angle = angle_remap(angle);
r_angle[l_index].angle = angle;
r_angle[l_index].vs[0] = BM_elem_index_get(l_iter->v);
r_angle[l_index].vs[1] = BM_elem_index_get(l_iter->next->v);
} while ((l_iter = l_iter->next) != l_first);
}
}
else {
// TODO: this is untested: how can I trigger this pathway?
angle_data *vert_angles = (angle_data *)MEM_mallocN(sizeof(angle_data) * mr.vert_len,
__func__);
memset(vert_angles, 0, sizeof(angle_data) * mr.vert_len);
Map<OrderedEdge, int> eh;
eh.reserve(mr.edge_len);
for (int face_index = 0; face_index < mr.face_len; face_index++) {
const IndexRange face = mr.faces[face_index];
for (int i = 0; i < face.size(); i++) {
const int vert_curr = mr.corner_verts[face.start() + (i + 0) % face.size()];
const int vert_next = mr.corner_verts[face.start() + (i + 1) % face.size()];
float angle;
bool angle_set = false;
eh.add_or_modify(
{vert_curr, vert_next},
[&](int *value) { *value = face_index; },
[&](int *value) {
const int other_face_index = *value;
if (other_face_index == -1) {
/* non-manifold edge */
// angle = DEG2RADF(90.0f);
return;
}
const float *f1_no = mr.face_normals[face_index];
const float *f2_no = mr.face_normals[other_face_index];
angle = angle_normalized_v3v3(f1_no, f2_no);
angle = is_edge_convex_v3(mr.vert_positions[vert_curr],
mr.vert_positions[vert_next],
f1_no,
f2_no) ?
angle :
-angle;
/* Tag as manifold. */
*value = -1;
angle_set = true;
});
if (angle_set) {
if (sharp)
angle = sharp_remap(angle, min, max, minmax_irange);
else
angle = angle_remap(angle);
vert_angles[vert_curr] = angle_data{angle, vert_curr, vert_next};
}
}
}
for (int l_index = 0; l_index < mr.loop_len; l_index++) {
const int vert = mr.corner_verts[l_index];
r_angle[l_index] = vert_angles[vert];
}
MEM_freeN(vert_angles);
}
}
static void extract_analysis_iter_finish_mesh(const MeshRenderData &mr,
MeshBatchCache & /*cache*/,
void *buf,
@ -613,7 +728,10 @@ static void extract_analysis_iter_finish_mesh(const MeshRenderData &mr,
statvis_calc_distort(mr, l_weight);
break;
case SCE_STATVIS_SHARP:
statvis_calc_sharp(mr, l_weight);
statvis_calc_angle(mr, l_weight, true);
break;
case SCE_STATVIS_ANGLE:
statvis_calc_angle(mr, l_weight, false);
break;
}
}

View File

@ -2461,6 +2461,7 @@ enum {
SCE_STATVIS_INTERSECT = 2,
SCE_STATVIS_DISTORT = 3,
SCE_STATVIS_SHARP = 4,
SCE_STATVIS_ANGLE = 5,
};
/** #ParticleEditSettings::selectmode for particles */

View File

@ -4440,6 +4440,7 @@ static void rna_def_statvis(BlenderRNA *brna)
{SCE_STATVIS_INTERSECT, "INTERSECT", 0, "Intersect", ""},
{SCE_STATVIS_DISTORT, "DISTORT", 0, "Distortion", ""},
{SCE_STATVIS_SHARP, "SHARP", 0, "Sharp", ""},
{SCE_STATVIS_ANGLE, "ANGLE", 0, "Angle", ""},
{0, nullptr, 0, nullptr, nullptr},
};