OpenGL中如何解决重叠点轮廓被覆盖问题并优化性能
Great question—you're totally right to worry about performance when doing per-point draw calls, especially as your vertex count grows. Let's break down some solid optimizations that keep the correct rendering order (outline first, then inner point per vertex) while cutting down on expensive draw calls:
Option 1: Use glMultiDrawArrays (Minimize Draw Call Overhead)
Instead of calling glDrawArrays for every single point, use glMultiDrawArrays to batch all your per-point draw operations into just two total calls (one for outlines, one for inner points). This drastically reduces the CPU-GPU communication overhead that kills performance with many small draw calls.
Keep your original bulk vertex arrays (points and outlinePoints)—no need to create per-point arrays anymore. Then set up arrays to define each single-point draw operation:
// Prepare draw parameters for multi-draw GLint* firsts = new GLint[graph->vertexCount]; GLsizei* counts = new GLsizei[graph->vertexCount]; for (int i = 0; i < graph->vertexCount; i++) { firsts[i] = i; // Start at the i-th vertex in the array counts[i] = 1; // Draw exactly 1 vertex per operation } // Render all outlines in one multi-draw call glEnable(GL_POINT_SMOOTH); glPointSize(DOT_SIZE*scale*1.2); glVertexPointer(2, GL_FLOAT, 24, outlinePoints); glColorPointer(4, GL_FLOAT, 24, &outlinePoints[2]); glMultiDrawArrays(GL_POINTS, firsts, counts, graph->vertexCount); // Render all inner points in one multi-draw call glPointSize(DOT_SIZE*scale); glVertexPointer(2, GL_FLOAT, 24, points); glColorPointer(4, GL_FLOAT, 24, &points[2]); glMultiDrawArrays(GL_POINTS, firsts, counts, graph->vertexCount); // Clean up (or reuse these arrays if rendering every frame) delete[] firsts; delete[] counts;
This maintains the critical "outline first, inner point second" order for each vertex, but only uses 2 draw calls total instead of 2×vertexCount.
Option 2: Add Vertex Array Objects (VAOs) to Reduce State Switches
If you're rendering this frame after frame, VAOs will save you from re-setting glVertexPointer and glColorPointer every time. VAOs store all your vertex attribute state, so you just bind the VAO and go:
// Initialize once (e.g., at setup time) GLuint outlineVAO, innerVAO; glGenVertexArrays(2, &outlineVAO); // Set up outline VAO glBindVertexArray(outlineVAO); glVertexPointer(2, GL_FLOAT, 24, outlinePoints); glColorPointer(4, GL_FLOAT, 24, &outlinePoints[2]); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); // Set up inner point VAO glBindVertexArray(innerVAO); glVertexPointer(2, GL_FLOAT, 24, points); glColorPointer(4, GL_FLOAT, 24, &points[2]); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); // Render loop (simplified) glEnable(GL_POINT_SMOOTH); // Render outlines glBindVertexArray(outlineVAO); glPointSize(DOT_SIZE*scale*1.2); glMultiDrawArrays(GL_POINTS, firsts, counts, graph->vertexCount); // Render inner points glBindVertexArray(innerVAO); glPointSize(DOT_SIZE*scale); glMultiDrawArrays(GL_POINTS, firsts, counts, graph->vertexCount);
VAOs eliminate redundant state setup calls, which adds another layer of performance improvement, especially in tight render loops.
Option 3: Switch to Programmable Pipeline (Best for Long-Term Performance)
If your target hardware supports OpenGL 2.0+, switching to shaders lets you render everything in a single draw call. You can encode both the outline and inner point data into a single vertex array, then use a vertex shader to set point size based on a vertex attribute.
Step 1: Build a combined vertex array
Each entry will hold position, color, and point size:
struct VertexData { GLfloat x, y; GLfloat r, g, b, a; GLfloat size; }; VertexData* combinedData = new VertexData[graph->vertexCount * 2]; for (int i = 0; i < graph->vertexCount; i++) { // Outline vertex (white, larger size) combinedData[i*2].x = (graph->vertices[i].x / (backingWidth/2)) - 1; combinedData[i*2].y = -(graph->vertices[i].y / (backingHeight/2)) + 1; combinedData[i*2].r = 0.9f; combinedData[i*2].g = 0.9f; combinedData[i*2].b = 0.9f; combinedData[i*2].a = 1.0f; combinedData[i*2].size = DOT_SIZE*scale*1.2; // Inner vertex (red, smaller size) combinedData[i*2+1].x = combinedData[i*2].x; combinedData[i*2+1].y = combinedData[i*2].y; combinedData[i*2+1].r = 1.0f; combinedData[i*2+1].g = 0.0f; combinedData[i*2+1].b = 0.0f; combinedData[i*2+1].a = 1.0f; combinedData[i*2+1].size = DOT_SIZE*scale; }
Step 2: Basic Vertex Shader
attribute vec2 aPosition; attribute vec4 aColor; attribute float aPointSize; varying vec4 vColor; void main() { gl_Position = vec4(aPosition, 0.0, 1.0); gl_PointSize = aPointSize; vColor = aColor; }
Step 3: Basic Fragment Shader
varying vec4 vColor; void main() { gl_FragColor = vColor; }
Step 4: Render in One Call
Bind your shader, set up vertex attributes, and draw:
// (Assume shader is compiled and linked into 'program') glUseProgram(program); // Set up vertex attributes GLint posAttrib = glGetAttribLocation(program, "aPosition"); GLint colorAttrib = glGetAttribLocation(program, "aColor"); GLint sizeAttrib = glGetAttribLocation(program, "aPointSize"); glEnableVertexAttribArray(posAttrib); glEnableVertexAttribArray(colorAttrib); glEnableVertexAttribArray(sizeAttrib); glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, x)); glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, r)); glVertexAttribPointer(sizeAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, size)); // Draw everything in one call! glEnable(GL_POINT_SMOOTH); glDrawArrays(GL_POINTS, 0, graph->vertexCount * 2);
This gives you the absolute best performance, as it's just one draw call with no per-point overhead.
Quick Additional Tip
Don't forget to enable blending if you want smooth anti-aliased edges (you're already using GL_POINT_SMOOTH, so this completes the effect):
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
内容的提问来源于stack exchange,提问作者jdleung

