Commit d0d05f47 authored by Matthias Betz's avatar Matthias Betz
Browse files

Render highlight vertex markers as sphere impostors



Add a geometry-shader program that expands each highlight point into a
camera-facing quad shaded as a lit sphere (hemisphere normal per fragment,
discard outside the disc). ShaderProgram gains optional geometry-stage
support; HighlightOverlay splits draw into drawLines/drawPoints so points
use the impostor program while edges keep the flat scene program.

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 60d835f3
...@@ -25,6 +25,7 @@ public class GLViewport { ...@@ -25,6 +25,7 @@ public class GLViewport {
private ShaderProgram sceneProgram; private ShaderProgram sceneProgram;
private ShaderProgram pickProgram; private ShaderProgram pickProgram;
private ShaderProgram pointProgram;
private boolean initialized; private boolean initialized;
// state set off-thread, consumed in render callback // state set off-thread, consumed in render callback
...@@ -114,6 +115,7 @@ public class GLViewport { ...@@ -114,6 +115,7 @@ public class GLViewport {
if (!initialized) { if (!initialized) {
sceneProgram = new ShaderProgram(gl, "scene.vert", "scene.frag"); sceneProgram = new ShaderProgram(gl, "scene.vert", "scene.frag");
pickProgram = new ShaderProgram(gl, "pick.vert", "pick.frag"); pickProgram = new ShaderProgram(gl, "pick.vert", "pick.frag");
pointProgram = new ShaderProgram(gl, "point.vert", "point.geom", "point.frag");
overlay.init(gl); overlay.init(gl);
gl.glEnable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_DEPTH_TEST);
gl.glEnable(GL.GL_MULTISAMPLE); // anti-aliasing for the multisampled framebuffer gl.glEnable(GL.GL_MULTISAMPLE); // anti-aliasing for the multisampled framebuffer
...@@ -160,7 +162,14 @@ public class GLViewport { ...@@ -160,7 +162,14 @@ public class GLViewport {
sceneProgram.use(gl); sceneProgram.use(gl);
gl.glUniformMatrix4fv(sceneProgram.uniform(gl, "uMVP"), 1, false, mvp, 0); gl.glUniformMatrix4fv(sceneProgram.uniform(gl, "uMVP"), 1, false, mvp, 0);
scene.draw(gl); scene.draw(gl);
overlay.draw(gl, pointScale, lineScale); // edge markers reuse the flat scene program (pos + colour)
overlay.drawLines(gl, lineScale);
// vertex markers use the sphere-impostor program (geometry shader -> camera-facing quads)
pointProgram.use(gl);
gl.glUniformMatrix4fv(pointProgram.uniform(gl, "uMVP"), 1, false, mvp, 0);
gl.glUniform2f(pointProgram.uniform(gl, "uViewport"), (float) w, (float) h);
gl.glUniform1f(pointProgram.uniform(gl, "uPointSize"), pointScale);
overlay.drawPoints(gl);
double[] req = pickRequest; double[] req = pickRequest;
if (req != null && currentScene != null) { if (req != null && currentScene != null) {
......
...@@ -74,20 +74,27 @@ public class HighlightOverlay { ...@@ -74,20 +74,27 @@ public class HighlightOverlay {
gl.glVertexAttribPointer(loc, comp, GL.GL_FLOAT, false, 0, 0L); gl.glVertexAttribPointer(loc, comp, GL.GL_FLOAT, false, 0, 0L);
} }
public void draw(GL3 gl, float pointSize, float lineWidth) { /** Draws the edge markers as lines. The caller must bind a program that reads pos+color. */
if (!created) { public void drawLines(GL3 gl, float lineWidth) {
if (!created || lineVertexCount == 0) {
return; return;
} }
if (lineVertexCount > 0) { gl.glLineWidth(lineWidth);
gl.glLineWidth(lineWidth); gl.glBindVertexArray(lineVao);
gl.glBindVertexArray(lineVao); gl.glDrawArrays(GL.GL_LINES, 0, lineVertexCount);
gl.glDrawArrays(GL.GL_LINES, 0, lineVertexCount); gl.glBindVertexArray(0);
} }
if (pointCount > 0) {
gl.glPointSize(pointSize); /**
gl.glBindVertexArray(pointVao); * Draws the vertex markers as GL_POINTS. The caller must bind the sphere-impostor program (a
gl.glDrawArrays(GL.GL_POINTS, 0, pointCount); * geometry shader expands each point into a camera-facing quad shaded as a sphere).
*/
public void drawPoints(GL3 gl) {
if (!created || pointCount == 0) {
return;
} }
gl.glBindVertexArray(pointVao);
gl.glDrawArrays(GL.GL_POINTS, 0, pointCount);
gl.glBindVertexArray(0); gl.glBindVertexArray(0);
} }
......
...@@ -15,14 +15,26 @@ public class ShaderProgram { ...@@ -15,14 +15,26 @@ public class ShaderProgram {
private final int program; private final int program;
public ShaderProgram(GL3 gl, String vertexResource, String fragmentResource) { public ShaderProgram(GL3 gl, String vertexResource, String fragmentResource) {
this(gl, vertexResource, null, fragmentResource);
}
/** Builds a program with an optional geometry stage ({@code geometryResource} may be null). */
public ShaderProgram(GL3 gl, String vertexResource, String geometryResource, String fragmentResource) {
int vs = compile(gl, GL3.GL_VERTEX_SHADER, read(vertexResource)); int vs = compile(gl, GL3.GL_VERTEX_SHADER, read(vertexResource));
int gs = geometryResource != null ? compile(gl, GL3.GL_GEOMETRY_SHADER, read(geometryResource)) : 0;
int fs = compile(gl, GL3.GL_FRAGMENT_SHADER, read(fragmentResource)); int fs = compile(gl, GL3.GL_FRAGMENT_SHADER, read(fragmentResource));
program = gl.glCreateProgram(); program = gl.glCreateProgram();
gl.glAttachShader(program, vs); gl.glAttachShader(program, vs);
if (gs != 0) {
gl.glAttachShader(program, gs);
}
gl.glAttachShader(program, fs); gl.glAttachShader(program, fs);
gl.glLinkProgram(program); gl.glLinkProgram(program);
checkLink(gl, program); checkLink(gl, program);
gl.glDeleteShader(vs); gl.glDeleteShader(vs);
if (gs != 0) {
gl.glDeleteShader(gs);
}
gl.glDeleteShader(fs); gl.glDeleteShader(fs);
} }
......
#version 330 core
in vec2 vQuad;
in vec3 vColor;
out vec4 fragColor;
void main() {
float r2 = dot(vQuad, vQuad);
if (r2 > 1.0) {
discard; // outside the sphere's silhouette
}
float z = sqrt(1.0 - r2);
vec3 normal = normalize(vec3(vQuad.x, vQuad.y, z));
vec3 lightDir = normalize(vec3(0.3, 0.3, 1.0));
float diff = max(dot(normal, lightDir), 0.0);
float ambient = 0.45;
vec3 color = vColor * (ambient + (1.0 - ambient) * diff);
fragColor = vec4(color, 1.0);
}
#version 330 core
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
in vec3 vColorG[];
uniform vec2 uViewport; // framebuffer size in pixels
uniform float uPointSize; // sphere diameter in pixels
out vec2 vQuad; // billboard-local coords in [-1, 1]
out vec3 vColor;
void main() {
vec4 c = gl_in[0].gl_Position;
if (c.w <= 0.0) {
return; // behind the camera
}
// half-extent in clip space for a constant on-screen pixel size
vec2 h = vec2(uPointSize / uViewport.x, uPointSize / uViewport.y) * c.w;
vColor = vColorG[0];
gl_Position = c + vec4(-h.x, -h.y, 0.0, 0.0); vQuad = vec2(-1.0, -1.0); EmitVertex();
gl_Position = c + vec4( h.x, -h.y, 0.0, 0.0); vQuad = vec2( 1.0, -1.0); EmitVertex();
gl_Position = c + vec4(-h.x, h.y, 0.0, 0.0); vQuad = vec2(-1.0, 1.0); EmitVertex();
gl_Position = c + vec4( h.x, h.y, 0.0, 0.0); vQuad = vec2( 1.0, 1.0); EmitVertex();
EndPrimitive();
}
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
uniform mat4 uMVP;
out vec3 vColorG;
void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
vColorG = aColor;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment