Files
Sebastien Loriot 7e1ced9398 Basic_viewer: clip-plane capping (#9566)
### Summary

Fills the flat cross-section where the clipping plane cuts a solid, so a
clipped closed solid reads as a real slice instead of a hollow shell.
For a volumetric input (a Linear_cell_complex) each volume's
cross-section is filled in that volume's own color, so the inner volumes
stay distinct at the cut.

Updated per the mailing thread. The cap now follows Guillaume's
suggestion to keep it out of the drawing functions and factorize the
grouping in the Basic_viewer through `Graphics_scene`, and Guillaume's
open-surface question is handled by a closed-input guard. Ready for
review.

### How it works

- **Stencil parity fill.** The clipped solid is drawn once into the
stencil buffer with an even-odd (parity) rule, so an odd number of
surface crossings marks the interior. Parity is winding-independent, so
it handles an LCC's inconsistent face orientation. A quad on the clip
plane is then filled where the stencil is odd.
- **Per-fragment clip is enough.** The existing shader discard gives a
clean cap edge and multisampling anti-aliases it, so no
`gl_ClipDistance` path is needed.
- **Per-volume grouping in `Graphics_scene`, over the single face
buffer.** `Graphics_scene` gains `volume_begin`/`volume_end` and, per
volume, a list of face indices into the single (de-duplicated) face
buffer. A shared wall is one face referenced by both neighboring
volumes, so each volume is still closed for the parity fill while no
geometry is duplicated and the display buffer stays de-duplicated. The
separate cap buffer (`POS_CAP_FACES`) from the first version is gone.
- **The LCC drawer only groups.** It calls `volume_begin`/`volume_end`
around each volume and assigns that volume's faces by index, using a
small dart-to-face-index map so a shared wall resolves to the same face
for both neighbors. The viewer's cap pass draws each volume's referenced
faces into the stencil, then a quad in the volume color.
- **Closed-input guard.** Parity is only defined for a closed surface,
so an open group has no meaningful cap. The viewer detects closedness
from the face buffer alone (an edge used by a single triangle means
open), matching vertices by position, and skips the cap for an open
group. This is computed once, lazily, on the first clip, and cached, so
a mesh that is never clipped pays nothing.
- **Surface-mesh fallback.** A non-volumetric input has no per-volume
groups and falls back to filling the whole face buffer as one cap in a
neutral grey (still subject to the closed guard).
- The stencil buffer is requested in `init_ogl_context`.
- Only the two solid clip modes are capped (solid-half-only,
solid-half-wireframe). The transparent-half mode is left uncapped so the
ghost stays visible.

### Changes from the first version (per review)

- **Placement.** Guillaume asked to keep the cap out of the many drawing
functions and factorize it in the Basic_viewer. Done: the grouping now
lives in `Graphics_scene` (`volume_begin`/`volume_end`) and the
per-volume cap pass is in the viewer; the LCC drawer only tags faces to
volumes.
- **No duplicated geometry.** The first version used a separate,
non-de-duplicated cap buffer. Now a shared wall is a single face
referenced by both volumes.
- **Open surfaces.** Added the closed-input guard for Guillaume's
boundary question.
- **Always-on.** The prototype `K` toggle is removed; the cap is always
on in the two solid clip modes.
- **Lazy.** The closed check is computed on the first clip and cached,
not eagerly on every draw.

### Open questions for review

- **Other volumetric drawers.** The grouping is emitted by the LCC
drawer. `Combinatorial_map` and `Generalized_map` could tag volumes the
same way; happy to add that here or in a follow-up.
- **Cap color.** The cap is the volume's own color, drawn flat so it
stays distinct from the real lit faces. Keep it, or make it
configurable?

### Performance

The only per-mesh cost is the closed check, which runs over every face
once on the first clip and is then cached. On the refined_elephant model
(~89k faces) it is about 100 ms, one time. It uses ordered maps; if a
much larger mesh ever makes that first-clip pause visible, the vertex
and edge lookups can move to hashed maps. The stencil fill itself is one
pass per volume per frame, fine for tens of volumes; a very large
multi-volume complex would want the passes batched.

### Testing

Tested on a single hexahedron, rows of sewn hexahedra sharing internal
walls (2, 6, and 40 volumes), disjoint hexahedra, a flat open sheet
(correctly skipped by the guard), and closed surface meshes including
the dense refined_elephant (~89k faces) across all three clip modes.
Each volume's cross-section fills in its own color with no alternation
across shared walls, the surface mesh caps grey, an open surface is left
uncapped, and the transparent-half mode stays uncapped. Before/after
screenshots are on the CGAL wiki.
2026-07-28 09:10:51 +02:00

1097 lines
26 KiB
C++

// Copyright (c) 2018 GeometryFactory Sarl (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Guillaume Damiand <guillaume.damiand@liris.cnrs.fr>
#ifndef CGAL_BASIC_SHADERS_H
#define CGAL_BASIC_SHADERS_H
#include <CGAL/license/GraphicsView.h>
namespace CGAL
{
//------------------------------------------------------------------------------
const char VERTEX_SOURCE_COLOR[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
in highp vec3 a_Normal;
in mediump vec3 a_Color;
out highp vec4 vs_fP; // view space position
out highp vec4 ls_fP; // local space position
out highp vec3 fN;
out mediump vec4 fColor;
uniform highp mat4 u_Mvp;
uniform highp mat4 u_Mv;
uniform mediump float u_PointSize;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
fColor = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
fColor = vec4(u_DefaultColor, 1.0);
}
vec4 pos = vec4(a_Pos, 1.0);
ls_fP = pos;
vs_fP = u_Mv * pos;
fN = mat3(u_Mv)* a_Normal;
gl_Position = u_Mvp * pos;
gl_PointSize = u_PointSize;
}
)DELIM";
const char FRAGMENT_SOURCE_COLOR[]=R"DELIM(
#version 150
in highp vec4 vs_fP;
in highp vec4 ls_fP;
in highp vec3 fN;
in mediump vec4 fColor;
out mediump vec4 out_color;
uniform highp vec4 u_LightPos;
uniform mediump vec4 u_LightDiff;
uniform mediump vec4 u_LightSpec;
uniform mediump vec4 u_LightAmb;
uniform mediump float u_SpecPower;
uniform highp vec4 u_ClipPlane;
uniform highp vec4 u_PointPlane;
uniform mediump float u_RenderingMode;
uniform mediump float u_RenderingTransparency;
void main(void)
{
highp vec3 L = u_LightPos.xyz - vs_fP.xyz;
highp vec3 V = -vs_fP.xyz;
highp vec3 a_Normal = normalize(fN);
L = normalize(L);
V = normalize(V);
highp vec3 R = reflect(-L, a_Normal);
highp vec4 diffuse = vec4(max(dot(a_Normal,L), 0.0) * u_LightDiff.rgb * fColor.rgb, 1.0);
highp vec4 ambient = vec4(u_LightAmb.rgb * fColor.rgb, 1.0);
highp vec4 specular = pow(max(dot(R,V), 0.0), u_SpecPower) * u_LightSpec;
// onPlane == 1: inside clipping plane, should be solid;
// onPlane == -1: outside clipping plane, should be transparent;
// onPlane == 0: on clipping plane, whatever;
float onPlane = sign(dot((ls_fP.xyz-u_PointPlane.xyz), u_ClipPlane.xyz));
// rendering_mode == -1: draw all solid;
// rendering_mode == 0: draw solid only;
// rendering_mode == 1: draw transparent only;
if (u_RenderingMode == (onPlane+1)/2) {
// discard other than the corresponding half when rendering
discard;
}
// draw corresponding part
out_color = u_RenderingMode < 1 ? (diffuse + ambient) :
vec4(diffuse.rgb + ambient.rgb, u_RenderingTransparency);
}
)DELIM";
const char VERTEX_SOURCE_P_L[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
in mediump vec3 a_Color;
out mediump vec4 fColor;
out highp vec4 ls_fP; // local space
uniform highp mat4 u_Mvp;
uniform mediump float u_PointSize;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
fColor = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
fColor = vec4(u_DefaultColor, 1.0);
}
vec4 pos = vec4(a_Pos, 1.0);
ls_fP = pos;
gl_Position = u_Mvp * pos;
// u_PointSize is a constant screen-pixel diameter, the same in 2D and 3D and
// independent of the scene scale (the legacy glPointSize() semantics). We do
// not divide by the clip-space w: that foreshortened points with depth, so on
// a large scene the points shrank below one pixel and disappeared.
gl_PointSize = u_PointSize;
}
)DELIM";
const char VERTEX_SOURCE_SHAPE[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
in mediump vec3 a_Color;
out mediump vec4 gColor;
out highp vec4 ls_fP;
uniform highp mat4 u_Mvp;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
gColor = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
gColor = vec4(u_DefaultColor, 1.0);
}
gl_Position = vec4(a_Pos, 1.0);
}
)DELIM";
const char GEOMETRY_SOURCE_SPHERE[]=R"DELIM(
#version 150
layout(points) in;
layout(triangle_strip, max_vertices = 72) out; // max_vertices = (resolution+1) * 2 * latResolution
#define PI 3.14159265358979323846
in mediump vec4 gColor[];
out mediump vec4 fColor;
out highp vec4 ls_fP;
uniform highp mat4 u_Mvp;
uniform mediump float u_Radius;
void drawSphere(in vec4 center, in float radius, in float resolution)
{
float latResolution = resolution*0.5;
float stepTheta = PI/latResolution;
float stepPhi = 2*PI/resolution;
for(int i=0; i<latResolution; ++i)
{
float theta1 = stepTheta*i;
float theta2 = stepTheta*(i+1);
for(int j=0; j<=resolution; ++j)
{
float phi = stepPhi*j;
float x1 = center.x + radius * sin(theta1) * cos(phi);
float y1 = center.y + radius * sin(theta1) * sin(phi);
float z1 = center.z + radius * cos(theta1);
ls_fP = vec4(x1, y1, z1, 1.0);
gl_Position = u_Mvp * ls_fP;
EmitVertex();
float x2 = center.x + radius * sin(theta2) * cos(phi);
float y2 = center.y + radius * sin(theta2) * sin(phi);
float z2 = center.z + radius * cos(theta2);
ls_fP = vec4(x2, y2, z2, 1.0);
gl_Position = u_Mvp * ls_fP;
EmitVertex();
}
EndPrimitive();
}
}
void main(void)
{
fColor = gColor[0];
int resolution = 8;
vec4 center = gl_in[0].gl_Position;
drawSphere(center, u_Radius, resolution);
}
)DELIM";
// Join spheres for the tube-edge mode. Takes each edge (a line) and emits a
// sphere at both endpoints, so the spheres land exactly where the open cylinders
// meet. Drawing from the edges (not the scene's point set) means a point that is
// not a tube endpoint (e.g. a triangulation's infinite vertex) never gets a
// sphere, and a zero-length (degenerate) edge, whose cylinder is not drawn, is
// skipped so no sphere floats where there is no tube.
const char GEOMETRY_SOURCE_SPHERE_JOIN[]=R"DELIM(
#version 150
layout(lines) in;
layout(triangle_strip, max_vertices = 84) out; // 2 spheres * (resolution+1)*2*(resolution/2), resolution = 6
#define PI 3.14159265358979323846
in mediump vec4 gColor[];
out mediump vec4 fColor;
out highp vec4 ls_fP;
uniform highp mat4 u_Mvp;
uniform mediump float u_Radius;
void drawSphere(in vec4 center, in float radius, in float resolution)
{
float latResolution = resolution*0.5;
float stepTheta = PI/latResolution;
float stepPhi = 2*PI/resolution;
for(int i=0; i<latResolution; ++i)
{
float theta1 = stepTheta*i;
float theta2 = stepTheta*(i+1);
for(int j=0; j<=resolution; ++j)
{
float phi = stepPhi*j;
float x1 = center.x + radius * sin(theta1) * cos(phi);
float y1 = center.y + radius * sin(theta1) * sin(phi);
float z1 = center.z + radius * cos(theta1);
ls_fP = vec4(x1, y1, z1, 1.0);
gl_Position = u_Mvp * ls_fP;
EmitVertex();
float x2 = center.x + radius * sin(theta2) * cos(phi);
float y2 = center.y + radius * sin(theta2) * sin(phi);
float z2 = center.z + radius * cos(theta2);
ls_fP = vec4(x2, y2, z2, 1.0);
gl_Position = u_Mvp * ls_fP;
EmitVertex();
}
EndPrimitive();
}
}
void main(void)
{
vec4 a = gl_in[0].gl_Position;
vec4 b = gl_in[1].gl_Position;
// Skip degenerate (zero-length) edges: there is no cylinder there, so a join
// sphere would float where no tube is.
if (length(b.xyz - a.xyz) < 1e-7)
return;
// Each endpoint sphere takes that endpoint's color, so it matches the cylinder.
fColor = gColor[0];
drawSphere(a, u_Radius, 6.0);
fColor = gColor[1];
drawSphere(b, u_Radius, 6.0);
}
)DELIM";
const char GEOMETRY_SOURCE_CYLINDER[]=R"DELIM(
#version 150
layout(lines) in;
layout(triangle_strip, max_vertices = 22) out;
#define PI 3.14159265358979323846
in mediump vec4 gColor[];
out mediump vec4 fColor;
out highp vec4 ls_fP;
uniform highp mat4 u_Mvp;
uniform mediump float u_Radius;
void drawCylinder(in vec3 u, in vec3 v, in vec4 bot, in vec4 top, in float radius, in float resolution)
{
float step = 2*PI/resolution;
for(int i=0; i<=resolution; ++i)
{
float theta = step*i;
float cosf = radius*cos(theta);
float sinf = radius*sin(theta);
vec3 xAxis = cosf*u.xyz;
vec3 yAxis = sinf*v.xyz;
ls_fP = vec4(top.xyz+xAxis.xyz+yAxis.xyz, 1.0);
gl_Position = u_Mvp * ls_fP;
EmitVertex();
ls_fP = vec4(bot.xyz+xAxis.xyz+yAxis.xyz, 1.0);
gl_Position = u_Mvp * ls_fP;
EmitVertex();
}
EndPrimitive();
}
void main(void)
{
fColor = gColor[0];
vec4 a = gl_in[0].gl_Position;
vec4 b = gl_in[1].gl_Position;
vec3 n = normalize(vec3(b.x-a.x, b.y-a.y, b.z-a.z)); // compute top normal
vec3 w = normalize(vec3(-n.z, n.x, n.y));
// Axis vectors
vec3 u = normalize(cross(n, w));
vec3 v = normalize(cross(n, u));
int resolution = 10;
drawCylinder(u, v, a, b, u_Radius, resolution);
}
)DELIM";
const char FRAGMENT_SOURCE_P_L[]=R"DELIM(
#version 150
in mediump vec4 fColor;
in highp vec4 ls_fP;
out mediump vec4 out_color;
uniform highp vec4 u_ClipPlane;
uniform highp vec4 u_PointPlane;
uniform mediump float u_RenderingMode;
void main(void)
{
// onPlane == 1: inside clipping plane, should be solid;
// onPlane == -1: outside clipping plane, should be transparent;
// onPlane == 0: on clipping plane, whatever;
float onPlane = sign(dot((ls_fP.xyz-u_PointPlane.xyz), u_ClipPlane.xyz));
// rendering_mode == -1: draw both inside and outside;
// rendering_mode == 0: draw inside only;
// rendering_mode == 1: draw outside only;
if (u_RenderingMode == (onPlane+1)/2) {
// discard other than the corresponding half when rendering
discard;
}
out_color = fColor;
}
)DELIM";
const char VERTEX_SOURCE_CLIPPING_PLANE[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
uniform highp mat4 u_Vp;
uniform highp mat4 u_M;
void main(void)
{
gl_Position = u_Vp * u_M * vec4(a_Pos, 1.0);
}
)DELIM";
const char FRAGMENT_SOURCE_CLIPPING_PLANE[]=R"DELIM(
#version 150
out mediump vec4 out_color;
uniform mediump vec4 u_Color;
void main(void)
{
out_color = u_Color;
}
)DELIM";
const char VERTEX_SOURCE_LINE[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
in mediump vec3 a_Color;
out VS_OUT {
mediump vec4 color;
} vs_out; // vertex shader output
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
vs_out.color = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
vs_out.color = vec4(u_DefaultColor, 1.0);
}
gl_Position = vec4(a_Pos, 1.0);
}
)DELIM";
const char GEOMETRY_SOURCE_ARROW[]=R"DELIM(
#version 150
layout(lines) in;
layout(triangle_strip, max_vertices = 82) out; // max_vertices = resolution * 2 + 2 (cylinder) + resolution * 3 (disc) + resolution * 3 (cone)
#define PI 3.14159265358979323846
in VS_OUT {
mediump vec4 color;
} gs_in[]; // geometry shader input
out mediump vec4 fColor;
uniform highp mat4 u_Mvp;
uniform mediump float u_SceneRadius;
void drawTriangle(in vec4 v1, in vec4 v2, in vec4 v3)
{
gl_Position = u_Mvp*v1;
EmitVertex();
gl_Position = u_Mvp*v2;
EmitVertex();
gl_Position = u_Mvp*v3;
EmitVertex();
EndPrimitive();
}
void drawTriangleFan(in vec3 u, in vec3 v, in vec4 center, in vec4 edge0, in float radius, in int resolution)
{
float step = 2*PI/resolution;
for(int i=0; i<resolution; ++i)
{
float theta = step*i;
float cosf = radius*cos(theta);
float sinf = radius*sin(theta);
vec3 xAxis = cosf*u.xyz;
vec3 yAxis = sinf*v.xyz;
vec4 edge1 = vec4(edge0.xyz+xAxis.xyz+yAxis.xyz, 1.0);
theta = step*(i+1);
cosf = radius*cos(theta);
sinf = radius*sin(theta);
xAxis = cosf*u.xyz;
yAxis = sinf*v.xyz;
vec4 edge2 = vec4(edge0.xyz+xAxis.xyz+yAxis.xyz, 1.0);
drawTriangle(center, edge1, edge2);
}
}
void drawDisc(in vec3 u, in vec3 v, in vec4 center, in float radius, in int resolution)
{
drawTriangleFan(u, v, center, center, radius, resolution);
}
void drawCone(in vec3 u, in vec3 v, in vec3 n, in vec4 center, in float radius, in float height, in int resolution)
{
drawTriangleFan(u, v, center, vec4(center.xyz-height*n.xyz, 1.0), radius, resolution);
}
void drawCylinder(in vec3 u, in vec3 v, in vec4 bot, in vec4 top, in float radius, in float resolution)
{
float step = 2*PI/resolution;
for(int i=0; i<=resolution; ++i)
{
float theta = step*i;
float cosf = radius*cos(theta);
float sinf = radius*sin(theta);
vec3 xAxis = cosf*u.xyz;
vec3 yAxis = sinf*v.xyz;
gl_Position = u_Mvp * vec4(top.xyz+xAxis.xyz+yAxis.xyz, 1.0);
EmitVertex();
gl_Position = u_Mvp * vec4(bot.xyz+xAxis.xyz+yAxis.xyz, 1.0);
EmitVertex();
}
EndPrimitive();
}
void main(void)
{
fColor = gs_in[0].color;
vec4 a = gl_in[0].gl_Position;
vec4 b = gl_in[1].gl_Position;
vec3 n = normalize(vec3(b.x-a.x, b.y-a.y, b.z-a.z)); // compute top normal
vec3 w = normalize(vec3(-n.z, n.x, n.y));
// Axis vectors
vec3 u = normalize(cross(n, w));
vec3 v = normalize(cross(n, u));
float radius = 0.013 * u_SceneRadius;
float height = 0.035 * u_SceneRadius;
int resolution = 10;
vec4 c = vec4(b.xyz-height*n.xyz, 1.0);
drawDisc(u, v, c, radius, resolution);
drawCone(u, v, n, b, radius, height, resolution);
drawCylinder(u, v, a, c, radius*0.5, resolution);
}
)DELIM";
const char GEOMETRY_SOURCE_LINE[]=R"DELIM(
#version 150
layout(lines) in;
layout(line_strip, max_vertices = 2) out;
in VS_OUT {
mediump vec4 color;
} gs_in[]; // geometry shader input
out mediump vec4 fColor;
uniform highp mat4 u_Mvp;
void main(void)
{
fColor = gs_in[0].color;
gl_Position = u_Mvp * gl_in[0].gl_Position;
EmitVertex();
gl_Position = u_Mvp * gl_in[1].gl_Position;
EmitVertex();
EndPrimitive();
}
)DELIM";
const char FRAGMENT_SOURCE_LINE[]=R"DELIM(
#version 150
in mediump vec4 fColor;
out mediump vec4 out_color;
void main(void)
{
out_color = fColor;
}
)DELIM";
const char VERTEX_SOURCE_NORMAL[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
in highp vec3 a_Normal;
out VS_OUT {
mediump vec4 color;
highp vec3 normal;
} vs_out; // vertex shader output
uniform highp mat4 u_Mv;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
vs_out.color = vec4(abs(normalize(a_Normal)), 1.0);
if (u_UseDefaultColor)
{
vs_out.color = vec4(u_DefaultColor, 1.0);
}
mat3 normalMatrix = mat3(transpose(inverse(u_Mv)));
vs_out.normal = normalize(vec3(vec4(normalMatrix * a_Normal, 0.0)));
gl_Position = vec4(a_Pos, 1.0);
}
)DELIM";
const char GEOMETRY_SOURCE_NORMAL[]=R"DELIM(
#version 150
layout (triangles) in;
layout (line_strip, max_vertices = 6) out;
in VS_OUT {
mediump vec4 color;
highp vec3 normal;
} gs_in[]; // geometry shader input
out mediump vec4 fColor;
out highp vec4 ls_fP;
uniform highp mat4 u_Projection;
uniform mediump float u_Factor;
uniform mediump float u_SceneRadius;
uniform highp mat4 u_Mv;
uniform bool u_DisplayFaceNormal;
void GenerateLine(int index)
{
fColor = gs_in[index].color;
ls_fP = gl_in[index].gl_Position;
gl_Position = u_Projection * u_Mv * gl_in[index].gl_Position;
EmitVertex();
vec4 newPosition = u_Mv * gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * u_SceneRadius * u_Factor;
ls_fP = inverse(u_Mv) * newPosition;
gl_Position = u_Projection * newPosition;
EmitVertex();
EndPrimitive();
}
void DrawVerticesNormal()
{
GenerateLine(0); // first vertex normal
GenerateLine(1); // second vertex normal
GenerateLine(2); // third vertex normal
}
void DrawFaceNormal()
{
fColor = (gs_in[0].color + gs_in[1].color + gs_in[2].color) / 3;
vec4 center = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3;
ls_fP = center;
gl_Position = u_Projection * u_Mv * center;
EmitVertex();
vec3 n = normalize((gs_in[0].normal.xyz + gs_in[1].normal.xyz + gs_in[2].normal.xyz) / 3);
vec4 newPosition = u_Mv * center + vec4(n, 0.0) * u_SceneRadius * u_Factor;
ls_fP = inverse(u_Mv) * newPosition;
gl_Position = u_Projection * newPosition;
EmitVertex();
}
void main()
{
if (u_DisplayFaceNormal)
{
DrawFaceNormal();
}
else
{
DrawVerticesNormal();
}
}
)DELIM";
const char VERTEX_SOURCE_TRIANGLE[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
out VS_OUT {
mediump vec4 color;
highp vec4 ls_fP;
} vs_out;
uniform highp mat4 u_Mvp;
void main(void)
{
vec4 pos = vec4(a_Pos, 1.0);
vs_out.color = vec4(0.85, 0.85, 0.85, 1.0);
vs_out.ls_fP = pos;
gl_Position = u_Mvp * pos;
}
)DELIM";
const char GEOMETRY_SOURCE_TRIANGLE[]=R"DELIM(
#version 150
layout (triangles) in;
layout (line_strip, max_vertices=4) out;
in VS_OUT {
mediump vec4 color;
highp vec4 ls_fP;
} gs_in[];
out mediump vec4 fColor;
out highp vec4 ls_fP;
void main(void)
{
fColor = gs_in[0].color;
ls_fP = gs_in[0].ls_fP;
gl_Position = gl_in[0].gl_Position;
EmitVertex();
ls_fP = gs_in[1].ls_fP;
gl_Position = gl_in[1].gl_Position;
EmitVertex();
ls_fP = gs_in[2].ls_fP;
gl_Position = gl_in[2].gl_Position;
EmitVertex();
ls_fP = gs_in[0].ls_fP;
gl_Position = gl_in[0].gl_Position;
EmitVertex();
}
)DELIM";
const char VERTEX_SOURCE_LINE_WIDTH[]=R"DELIM(
#version 150
in highp vec3 a_Pos;
in mediump vec3 a_Color;
out VS_OUT {
mediump float pointSize;
mediump vec4 color;
highp vec4 ls_fP;
} vs_out;
uniform highp mat4 u_Mvp;
uniform mediump float u_PointSize;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
vec4 pos = vec4(a_Pos, 1.0);
vs_out.ls_fP = pos;
vs_out.color = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
vs_out.color = vec4(u_DefaultColor, 1.0);
}
gl_Position = u_Mvp * pos;
// u_PointSize is a constant screen-pixel width, the same in 2D and 3D and
// independent of the scene scale (the legacy glLineWidth() semantics). We do
// not divide by the clip-space w here: doing so foreshortened the width with
// depth, which made far edges collapse below one pixel and made the +/- step
// depend on the scene scale.
vs_out.pointSize = u_PointSize;
}
)DELIM";
const char GEOMETRY_SOURCE_LINE_WIDTH[]=R"DELIM(
#version 150
layout (lines) in;
layout (triangle_strip, max_vertices = 4) out;
in VS_OUT {
mediump float pointSize;
mediump vec4 color;
highp vec4 ls_fP;
} gs_in[];
out mediump vec4 fColor;
out highp vec4 ls_fP;
uniform mediump float u_PointSize;
uniform mediump vec2 u_Viewport;
// clip space -> viewport pixels
vec2 to_pixel(vec4 clip)
{
return ((clip.xy / clip.w) * 0.5 + 0.5) * u_Viewport;
}
// viewport pixels -> clip space, reusing one endpoint's depth (z, w) so the quad
// keeps perspective-correct depth without any inverse(u_Mvp)
vec4 to_clip(vec2 px, vec4 endpoint)
{
vec2 ndc = (px / u_Viewport) * 2.0 - 1.0;
return vec4(ndc * endpoint.w, endpoint.z, endpoint.w);
}
void emit(vec2 px, vec4 endpoint, int i)
{
gl_Position = to_clip(px, endpoint);
fColor = gs_in[i].color;
ls_fP = gs_in[i].ls_fP;
EmitVertex();
}
void main(void)
{
vec4 c0 = gl_in[0].gl_Position;
vec4 c1 = gl_in[1].gl_Position;
// skip degenerate clip positions to avoid a division by zero
if (c0.w == 0.0 || c1.w == 0.0)
return;
vec2 p0 = to_pixel(c0);
vec2 p1 = to_pixel(c1);
vec2 edge = p1 - p0;
float segLen = length(edge);
vec2 dir = (segLen > 1e-5) ? edge / segLen : vec2(1.0, 0.0);
vec2 perp = vec2(-dir.y, dir.x);
// Half-width in pixels. u_PointSize (carried in pointSize) is the full edge
// width in screen pixels, so the per-side offset is half of it, averaged over
// the two endpoints. Clamp to a minimum so a very thin edge still keeps a solid
// one-pixel core instead of disappearing.
float halfWidth = max(0.5 * 0.5 * (gs_in[0].pointSize + gs_in[1].pointSize), 1.0);
// Opaque square-capped edge: emit the exact rectangle from p0 to p1, half the
// width on each side. The edge is drawn solid and the framebuffer multisampling
// smooths the silhouette, so it stays fully opaque at any width: there is no
// per-pixel blending, so a thin edge cannot fade into the background.
vec2 dperp = perp * halfWidth;
emit(p0 - dperp, c0, 0);
emit(p0 + dperp, c0, 0);
emit(p1 - dperp, c1, 1);
emit(p1 + dperp, c1, 1);
}
)DELIM";
const char FRAGMENT_SOURCE_LINE_WIDTH[]=R"DELIM(
#version 150
in mediump vec4 fColor;
in highp vec4 ls_fP;
out mediump vec4 out_color;
uniform highp vec4 u_ClipPlane;
uniform highp vec4 u_PointPlane;
uniform mediump float u_RenderingMode;
void main(void)
{
// onPlane == 1: inside clipping plane, should be solid;
// onPlane == -1: outside clipping plane, should be transparent;
// onPlane == 0: on clipping plane, whatever;
float onPlane = sign(dot((ls_fP.xyz-u_PointPlane.xyz), u_ClipPlane.xyz));
// rendering_mode == -1: draw both inside and outside;
// rendering_mode == 0: draw inside only;
// rendering_mode == 1: draw outside only;
if (u_RenderingMode == (onPlane+1)/2) {
discard;
}
// Opaque edge: output the solid edge color. The framebuffer multisampling
// smooths the silhouette, so the edge stays fully solid at any width without
// any per-pixel blending.
out_color = fColor;
}
)DELIM";
// Vertex-disk join for the flat edges. Takes each edge (a line) and emits a small
// screen-space disk at both endpoints, in the edge color and at the edge width,
// so the square-cap corners where flat edges meet are filled. It reuses the same
// pixel-to-clip conversion as the wide-edge shader, so the disk radius matches the
// edge half-width. Drawing from the edges (not the point set) skips degenerate
// edges and never touches a non-edge point, so no disk floats where there is no
// edge. The disk is screen-facing (a rounded quad), not a sphere, so it stays flat
// in 3D and scales at any width with no point-size limit.
const char GEOMETRY_SOURCE_EDGE_DISK[]=R"DELIM(
#version 150
layout(lines) in;
layout(triangle_strip, max_vertices = 8) out; // 2 endpoints * 4 corners
in mediump vec4 gColor[];
out mediump vec4 fColor;
out highp vec4 ls_fP;
out mediump vec2 v_coord;
uniform highp mat4 u_Mvp;
uniform mediump float u_PointSize;
uniform mediump vec2 u_Viewport;
// clip space -> viewport pixels
vec2 to_pixel(vec4 clip)
{
return ((clip.xy / clip.w) * 0.5 + 0.5) * u_Viewport;
}
// viewport pixels -> clip space, reusing the endpoint depth (z, w)
vec4 to_clip(vec2 px, vec4 endpoint)
{
vec2 ndc = (px / u_Viewport) * 2.0 - 1.0;
return vec4(ndc * endpoint.w, endpoint.z, endpoint.w);
}
void emitDisk(vec4 clip, vec4 modelPos, vec4 color, float radius)
{
vec2 c = to_pixel(clip);
fColor = color;
ls_fP = modelPos;
v_coord = vec2(-1.0, -1.0); gl_Position = to_clip(c + vec2(-radius, -radius), clip); EmitVertex();
v_coord = vec2(-1.0, 1.0); gl_Position = to_clip(c + vec2(-radius, radius), clip); EmitVertex();
v_coord = vec2( 1.0, -1.0); gl_Position = to_clip(c + vec2( radius, -radius), clip); EmitVertex();
v_coord = vec2( 1.0, 1.0); gl_Position = to_clip(c + vec2( radius, radius), clip); EmitVertex();
EndPrimitive();
}
void main(void)
{
vec4 m0 = gl_in[0].gl_Position; // model-space position (a_Pos), from VERTEX_SOURCE_SHAPE
vec4 m1 = gl_in[1].gl_Position;
// Skip degenerate (zero-length) edges: no flat edge there, so no corner to fill.
if (length(m1.xyz - m0.xyz) < 1e-7)
return;
vec4 clip0 = u_Mvp * m0;
vec4 clip1 = u_Mvp * m1;
if (clip0.w == 0.0 || clip1.w == 0.0)
return;
// Same half-width as the wide edge: half the edge width in pixels, min one pixel.
float radius = max(0.5 * u_PointSize, 1.0);
emitDisk(clip0, m0, gColor[0], radius);
emitDisk(clip1, m1, gColor[1], radius);
}
)DELIM";
const char FRAGMENT_SOURCE_EDGE_DISK[]=R"DELIM(
#version 150
in mediump vec4 fColor;
in highp vec4 ls_fP;
in mediump vec2 v_coord;
out mediump vec4 out_color;
uniform highp vec4 u_ClipPlane;
uniform highp vec4 u_PointPlane;
uniform mediump float u_RenderingMode;
void main(void)
{
// Round the quad into a disk.
if (dot(v_coord, v_coord) > 1.0)
discard;
float onPlane = sign(dot((ls_fP.xyz-u_PointPlane.xyz), u_ClipPlane.xyz));
if (u_RenderingMode == (onPlane+1)/2) {
discard;
}
out_color = fColor;
}
)DELIM";
//------------------------------------------------------------------------------
// compatibility shaders
const char VERTEX_SOURCE_COLOR_COMP[]=R"DELIM(
varying highp vec3 a_Pos;
varying highp vec3 a_Normal;
varying mediump vec3 a_Color;
varying highp vec4 vs_fP; // view space position
varying highp vec3 fN;
varying mediump vec4 fColor;
uniform highp mat4 u_Mvp;
uniform highp mat4 u_Mv;
uniform mediump float u_PointSize;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
vec4 pos = vec4(a_Pos, 1.0);
vs_fP = u_Mv * pos;
highp mat3 mv_matrix_3;
mv_matrix_3[0] = mv_matrix[0].xyz;
mv_matrix_3[1] = mv_matrix[1].xyz;
mv_matrix_3[2] = mv_matrix[2].xyz;
fN = mv_matrix_3* a_Normal;
fColor = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
fColor = vec4(u_DefaultColor, 1.0);
}
gl_PointSize = u_PointSize;
gl_Position = u_Mvp * pos;
}
)DELIM";
const char FRAGMENT_SOURCE_COLOR_COMP[]=R"DELIM(
varying highp vec4 vs_fP;
varying highp vec3 fN;
varying mediump vec4 fColor;
uniform highp vec4 u_LightPos;
uniform mediump vec4 u_LightDiff;
uniform mediump vec4 u_LightSpec;
uniform mediump vec4 u_LightAmb;
uniform mediump float u_SpecPower ;
void main(void)
{
highp vec3 L = u_LightPos.xyz - vs_fP.xyz;
highp vec3 V = -vs_fP.xyz;
highp vec3 a_Normal = normalize(fN);
L = normalize(L);
V = normalize(V);
highp vec3 R = reflect(-L, a_Normal);
highp vec4 diffuse = max(dot(a_Normal,L), 0.0) * u_LightDiff * fColor;
highp vec4 specular = pow(max(dot(R,V), 0.0), u_SpecPower) * u_LightSpec;
gl_FragColor = u_LightAmb*fColor + diffuse;
}
)DELIM";
const char VERTEX_SOURCE_P_L_COMP[]=R"DELIM(
varying highp vec3 a_Pos;
varying mediump vec3 a_Color;
varying mediump vec4 fColor;
uniform highp mat4 u_Mvp;
uniform mediump float u_PointSize;
uniform mediump vec3 u_DefaultColor;
uniform bool u_UseDefaultColor;
void main(void)
{
fColor = vec4(a_Color, 1.0);
if (u_UseDefaultColor)
{
fColor = vec4(u_DefaultColor, 1.0);
}
gl_PointSize = u_PointSize;
gl_Position = u_Mvp * vec4(a_Pos, 1.0);
}
)DELIM";
const char FRAGMENT_SOURCE_P_L_COMP[]=R"DELIM(
varying mediump vec4 fColor;
void main(void)
{
gl_FragColor = fColor;
}
)DELIM";
/* const char vertex_source_clipping_plane_comp[]=R"DELIM(
attribute highp vec4 vertex;
uniform highp mat4 vp_matrix;
uniform highp mat4 m_matrix;
void main(void)
{
gl_Position = vp_matrix * m_matrix * vertex;
}
)DELIM";
const char fragment_source_clipping_plane_comp[]=R"DELIM(
out highp vec4 out_color;
void main(void)
{
out_color = vec4(0.0, 0.0, 0.0, 1.0);
}
)DELIM";
*/
}
#endif // CGAL_BASIC_SHADERS_H