OpenGL技术问题:如何在GL_TRIANGLE_STRIP中隔n个四边形跳过渲染
嘿,针对你用glDrawArrays(GL_TRIANGLE_STRIP, 0, 2000)渲染路径,需要每隔n个四边形跳过渲染的需求,我整理了几个实用的方案,适配不同的场景:
方案1:改用索引缓冲对象(EBO)—— 灵活且高效
既然你现在用的是无索引的glDrawArrays,换成EBO可以让你精确控制哪些四边形被渲染。
原理
GL_TRIANGLE_STRIP中,每4个连续顶点(比如索引0,1,2,3)会组成一个四边形(两个三角形)。我们可以在生成索引数组时,直接跳过那些不需要渲染的四边形对应的索引段。
示例代码
假设你要每渲染k个四边形,就跳过n个:
std::vector<GLuint> indices; int totalQuads = 2000 / 4; // 总四边形数(按你的2000个顶点计算) int renderPerBlock = k; int skipPerBlock = n; int currentQuad = 0; while (currentQuad < totalQuads) { // 渲染renderPerBlock个四边形,连续生成索引 for (int i = 0; i < renderPerBlock && currentQuad < totalQuads; ++i, ++currentQuad) { GLuint base = currentQuad * 4; indices.push_back(base); indices.push_back(base + 1); indices.push_back(base + 2); indices.push_back(base + 3); } // 跳过skipPerBlock个四边形 currentQuad += skipPerBlock; } // 绑定EBO并上传数据 GLuint ebo; glGenBuffers(1, &ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW); // 渲染时用glDrawElements glDrawElements(GL_TRIANGLE_STRIP, indices.size(), GL_UNSIGNED_INT, 0);
适用场景
需要频繁调整跳过规则,或者希望精确控制渲染范围的场景,性能开销极低。
方案2:使用glMultiDrawArrays—— 无需修改缓冲
如果你不想改动现有VBO的结构,glMultiDrawArrays可以让你一次性提交多个连续的渲染段,跳过不需要的部分。
原理
把整个顶点序列拆分成多个需要渲染的连续块,每块对应k个四边形的顶点数,块之间跳过n个四边形的顶点数,然后把这些块的起始位置和长度传给glMultiDrawArrays。
示例代码
std::vector<GLint> firsts; std::vector<GLsizei> counts; int quadPerRender = k; // 每次渲染k个四边形 int quadToSkip = n; // 跳过n个四边形 int verticesPerQuad = 4; // 每个四边形对应4个顶点 int totalVertices = 2000; int totalQuads = totalVertices / verticesPerQuad; int currentQuad = 0; while (currentQuad < totalQuads) { // 计算当前要渲染的四边形数量,避免超过总数 int renderCount = std::min(quadPerRender, totalQuads - currentQuad); // 转换为顶点的起始位置和长度 firsts.push_back(currentQuad * verticesPerQuad); counts.push_back(renderCount * verticesPerQuad); // 跳过n个四边形 currentQuad += renderCount + quadToSkip; } // 执行多段渲染 glMultiDrawArrays(GL_TRIANGLE_STRIP, firsts.data(), counts.data(), firsts.size());
适用场景
临时调整跳过规则,或者不想修改现有缓冲结构的场景,实现成本最低。
方案3:着色器中丢弃图元—— 动态调整最灵活
如果需要动态调整跳过的数量,甚至实时修改规则,可以在着色器中判断并丢弃不需要的四边形。
原理
给每个顶点添加一个额外的属性(比如quadIndex),标记它属于第几个四边形。然后在几何着色器中,判断当前四边形是否在跳过范围内,如果是,就不输出任何图元。
步骤
- 修改VBO,每个顶点追加
quadIndex属性:
// 在添加顶点数据时,计算当前顶点所属的四边形索引 int quadIndex = i / 4; // 每4个顶点对应一个四边形 vbo.AddData(&quadIndex, sizeof(int));
- 几何着色器实现跳过逻辑:
#version 450 core layout(triangle_strip, max_vertices = 4) out; in int v_quadIndex[]; in vec2 v_texCoord[]; out vec2 f_texCoord; uniform int u_skipN; // 要跳过的四边形数量 uniform int u_everyK; // 每渲染K个后跳过 void main() { // 取第一个顶点的quadIndex即可(同一个四边形的顶点索引相同) int currentQuad = v_quadIndex[0]; // 判断是否需要跳过:每K+N个四边形中,跳过后面的N个 if (currentQuad % (u_everyK + u_skipN) >= u_everyK) { return; // 不输出任何顶点,跳过这个四边形 } // 正常输出四边形的4个顶点 for (int i = 0; i < 4; ++i) { gl_Position = gl_in[i].gl_Position; f_texCoord = v_texCoord[i]; EmitVertex(); } EndPrimitive(); }
适用场景
需要实时调整跳过规则,或者不想修改CPU端渲染逻辑的场景,唯一的小缺点是会有少量着色器性能开销。
方案4:预处理VBO数据—— 性能最优
如果跳过规则是固定不变的,你可以在生成VBO时直接剔除不需要的四边形数据,后续渲染完全不用额外处理。
示例代码
int quadPerRender = k; int quadToSkip = n; int currentQuad = 0; for (unsigned int i = 0; i < lPoints.size() && i < rPoints.size(); ) { // 判断当前四边形是否需要渲染 if (currentQuad % (quadPerRender + quadToSkip) < quadPerRender) { // 添加当前四边形的4个顶点数据 for (int j = 0; j < 4 && i < lPoints.size() && i < rPoints.size(); ++j, ++i) { if (i % 2 != 0) { vbo.AddData(&rPoints[i], sizeof(glm::vec3)); vbo.AddData(&m_textCoords[i], sizeof(glm::vec2)); vbo.AddData(&normal, sizeof(glm::vec3)); } else { vbo.AddData(&lPoints[i], sizeof(glm::vec3)); vbo.AddData(&m_textCoords[i], sizeof(glm::vec2)); vbo.AddData(&normal, sizeof(glm::vec3)); } } } else { // 跳过n个四边形,直接移动顶点索引 i += quadToSkip * 4; } currentQuad++; } // 后续直接用glDrawArrays渲染即可 glDrawArrays(GL_TRIANGLE_STRIP, 0, vbo.GetTotalVertices());
适用场景
跳过规则固定,追求最高渲染性能的场景,一旦VBO生成好,后续渲染没有额外开销。
内容的提问来源于stack exchange,提问作者dlp_dev

