优化 OpenGL 渲染
Optimizing OpenGL rendering
我 运行 在使用 Assimp 渲染到 OpenGL 时遇到性能不佳的问题。场景有 367727 个三角形。同时,298084是国际象棋的典范。我不认为问题出在着色器中,因为:
128x128 window:44 (43.7657) FPS,22.849 毫秒
256x256 window:42 (40.9563) FPS,24.4162 毫秒
512x512 window:35 (34.8007) FPS,28.7351 毫秒
1024x1024 window:22 (21.084) FPS,47.4293 毫秒
但如果我不下棋,那么在 window 1366x763: 55 (54.8424) FPS, 18.2341 ms
此外,更改阴影贴图的分辨率不会对 FPS 产生太大影响。
场景中有 2 个点光源,在为每个通道绘制此模型时 FPS 损失 ~10 FPS(从 23 到 55)。即,我在何处绘制此模型没有区别:在深度图中还是在 "color texture" 中。亏~都一样。我使用以下参数加载模型:aiProcess_Triangulate | aiProcess_CalcTangentSpace | aiProcess_JoinIdenticalVertices
并渲染如下:
inline void pointer(GLint location, int count, GLuint buffer) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glVertexAttribPointer(
location, // attribute location
count, // count (1, 2, 3 or 4)
GL_FLOAT, // type
GL_FALSE, // is normalized?
0, // step
nullptr // offset
);
}
inline void pointerui(GLint location, int count, GLuint buffer) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glVertexAttribIPointer(
location, // attribute location
count, // count (1, 2, 3 or 4)
GL_UNSIGNED_INT, // type
0, // step
nullptr // offset
);
}
...
pointer(cs.inPosition, 3, model.shape->meshes[i].getVerticesBuffer());
pointer(cs.inNormal, 3, model.shape->meshes[i].getNormalsBuffer());
pointer(cs.inTangent, 3, model.shape->meshes[i].getTangentsBuffer());
pointer(cs.inBitangent, 3, model.shape->meshes[i].getBitangentsBuffer());
pointer(cs.inTexCoord, 2, model.shape->meshes[i].getTexCoordsBuffer());
if (model.shape->bonesPerVertex != 0) {
pointer(cs.inBoneWeights, 4, model.shape->meshes[i].getBoneWeightsBuffer());
pointerui(cs.inBoneIds, 4, model.shape->meshes[i].getBoneIdsBuffer());
}
modelMatrix = &model.transformation;
updateMatrices();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.shape->meshes[i].getIndicesBuffer());
glDrawElements(GL_TRIANGLES, model.shape->meshes[i].indices.size(), GL_UNSIGNED_INT, nullptr)
这是场景本身:
编辑:vertex_shader.glsl, fragment_shader.glsl
很抱歉片段着色器难以阅读,我还没有完全完成它的工作
我的 GPU 是 NVGF 920mx
编辑:这里是 capture 来自 renderdoc
我知道这个答案不是您问题的直接答案,根据您提供的详细信息,您可能至少已经知道其中的大部分内容,但无论如何我还是会把它放在这里以防其他人发现您的问题正在寻找可能的解决方案。
在大多数此类情况下,很难给出准确的答案,但可以提供一些寻找解决方案的一般思路。
你知道,无论如何使用着色器都会产生一种真实的幻觉。如果您追求准确性,您无论如何都会将目光转向光线追踪和其他更逼真的图像渲染方式。只要你在实时渲染中工作,并且使用着色器,这就是欺骗眼睛让场景看起来逼真,即使它不是。这就是为什么你应该寻找廉价的技巧来让你的场景看起来比实际情况更真实。
提高成绩最有效的方法就是少画画。如果可以,请尝试将 high-poly 网格烘焙为具有法线贴图的 low-poly 网格。在几乎所有旨在 real-time 性能(即更高的 FPS 评级)的项目中,这是非常常见的方式。一般来说,如果你追求高细节和准确性,你将需要大量的处理能力,但如果你能做出一些新颖的妥协来保留 sort-of-feel-of-details,你可以提高性能。以 50 FPS(即每秒 2500 万个顶点)绘制大约 50 万个顶点对您的 GPU 来说太多了。
这同样适用于您的着色器。您确定您正在使用配置着色器的所有灯光吗?如果你有三盏灯,你可以大大提高性能,如果你的着色器可以只管理三盏灯。请记住,灯光的数量是 fragment-specific 常量 (*):您无需考虑您的场景有多少灯光,这完全取决于每个片段像素考虑了多少灯光(即是,灯光在片段着色器中处理)。
(*) 好吧,这可能是一个特定于模型的常量,因为即使重要的是每个片段使用了多少光,也可能很难为每个片段发送光 - 每个片段更容易发送光渲染模型。
作为一般规则,将计算移至顶点着色器总是好的,让它为您的片段着色器预先计算值。例如,您可以考虑将片段着色器切换为使用相机 space,在这种情况下,您可以完全在顶点着色器中进行 TBN 计算。
问题的评论字段中已经有一些不错的想法,您可以考虑获得更好的性能。
如果您发现是内存瓶颈限制了您的性能,那么关于这个问题有一篇旧的但仍然不错的文章:https://www.khronos.org/opengl/wiki/Vertex_Specification_Best_Practices
在这种情况下,您可以做的是首先压缩您的顶点属性。例如,您可以将 TBN 矩阵转换为 GL_INT_2_10_10_10 类型(如果可用),这会将矩阵从 9 x float 压缩到 3 x float。它会降低精度,但大多数情况下不会造成任何可见的影响。你甚至可以走那么远,你将 TBN 作为四元数发送,如果精度足够的话,它将把 9 x 浮点数压缩到一个浮点数。
此外,您可以尝试交错缓冲区(从结构数组形成 VBO)。不确定有没有效果,就算有,也非常GPU-related。对于某些 GPU,它可能会提高顶点缓存的性能。
但是无论如何,如果您需要深入了解这些细节,充其量性能优势通常很小(至少在考虑您正在使用的一些简单的顶点属性方案时),并且在某些时候您只需要接受您的 GPU 无法在您想要的时间内处理您拥有的数据。您可以获得的最大性能存在与硬件相关的限制。
我知道这对你帮助不大,但我仍然希望你能得到一些想法,从哪里寻找可能的解决方案来解决你的问题。
我 运行 在使用 Assimp 渲染到 OpenGL 时遇到性能不佳的问题。场景有 367727 个三角形。同时,298084是国际象棋的典范。我不认为问题出在着色器中,因为:
128x128 window:44 (43.7657) FPS,22.849 毫秒
256x256 window:42 (40.9563) FPS,24.4162 毫秒
512x512 window:35 (34.8007) FPS,28.7351 毫秒
1024x1024 window:22 (21.084) FPS,47.4293 毫秒
但如果我不下棋,那么在 window 1366x763: 55 (54.8424) FPS, 18.2341 ms
此外,更改阴影贴图的分辨率不会对 FPS 产生太大影响。
场景中有 2 个点光源,在为每个通道绘制此模型时 FPS 损失 ~10 FPS(从 23 到 55)。即,我在何处绘制此模型没有区别:在深度图中还是在 "color texture" 中。亏~都一样。我使用以下参数加载模型:aiProcess_Triangulate | aiProcess_CalcTangentSpace | aiProcess_JoinIdenticalVertices
并渲染如下:
inline void pointer(GLint location, int count, GLuint buffer) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glVertexAttribPointer(
location, // attribute location
count, // count (1, 2, 3 or 4)
GL_FLOAT, // type
GL_FALSE, // is normalized?
0, // step
nullptr // offset
);
}
inline void pointerui(GLint location, int count, GLuint buffer) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glVertexAttribIPointer(
location, // attribute location
count, // count (1, 2, 3 or 4)
GL_UNSIGNED_INT, // type
0, // step
nullptr // offset
);
}
...
pointer(cs.inPosition, 3, model.shape->meshes[i].getVerticesBuffer());
pointer(cs.inNormal, 3, model.shape->meshes[i].getNormalsBuffer());
pointer(cs.inTangent, 3, model.shape->meshes[i].getTangentsBuffer());
pointer(cs.inBitangent, 3, model.shape->meshes[i].getBitangentsBuffer());
pointer(cs.inTexCoord, 2, model.shape->meshes[i].getTexCoordsBuffer());
if (model.shape->bonesPerVertex != 0) {
pointer(cs.inBoneWeights, 4, model.shape->meshes[i].getBoneWeightsBuffer());
pointerui(cs.inBoneIds, 4, model.shape->meshes[i].getBoneIdsBuffer());
}
modelMatrix = &model.transformation;
updateMatrices();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.shape->meshes[i].getIndicesBuffer());
glDrawElements(GL_TRIANGLES, model.shape->meshes[i].indices.size(), GL_UNSIGNED_INT, nullptr)
这是场景本身:
编辑:vertex_shader.glsl, fragment_shader.glsl
很抱歉片段着色器难以阅读,我还没有完全完成它的工作
我的 GPU 是 NVGF 920mx
编辑:这里是 capture 来自 renderdoc
我知道这个答案不是您问题的直接答案,根据您提供的详细信息,您可能至少已经知道其中的大部分内容,但无论如何我还是会把它放在这里以防其他人发现您的问题正在寻找可能的解决方案。
在大多数此类情况下,很难给出准确的答案,但可以提供一些寻找解决方案的一般思路。
你知道,无论如何使用着色器都会产生一种真实的幻觉。如果您追求准确性,您无论如何都会将目光转向光线追踪和其他更逼真的图像渲染方式。只要你在实时渲染中工作,并且使用着色器,这就是欺骗眼睛让场景看起来逼真,即使它不是。这就是为什么你应该寻找廉价的技巧来让你的场景看起来比实际情况更真实。
提高成绩最有效的方法就是少画画。如果可以,请尝试将 high-poly 网格烘焙为具有法线贴图的 low-poly 网格。在几乎所有旨在 real-time 性能(即更高的 FPS 评级)的项目中,这是非常常见的方式。一般来说,如果你追求高细节和准确性,你将需要大量的处理能力,但如果你能做出一些新颖的妥协来保留 sort-of-feel-of-details,你可以提高性能。以 50 FPS(即每秒 2500 万个顶点)绘制大约 50 万个顶点对您的 GPU 来说太多了。
这同样适用于您的着色器。您确定您正在使用配置着色器的所有灯光吗?如果你有三盏灯,你可以大大提高性能,如果你的着色器可以只管理三盏灯。请记住,灯光的数量是 fragment-specific 常量 (*):您无需考虑您的场景有多少灯光,这完全取决于每个片段像素考虑了多少灯光(即是,灯光在片段着色器中处理)。
(*) 好吧,这可能是一个特定于模型的常量,因为即使重要的是每个片段使用了多少光,也可能很难为每个片段发送光 - 每个片段更容易发送光渲染模型。
作为一般规则,将计算移至顶点着色器总是好的,让它为您的片段着色器预先计算值。例如,您可以考虑将片段着色器切换为使用相机 space,在这种情况下,您可以完全在顶点着色器中进行 TBN 计算。
问题的评论字段中已经有一些不错的想法,您可以考虑获得更好的性能。
如果您发现是内存瓶颈限制了您的性能,那么关于这个问题有一篇旧的但仍然不错的文章:https://www.khronos.org/opengl/wiki/Vertex_Specification_Best_Practices
在这种情况下,您可以做的是首先压缩您的顶点属性。例如,您可以将 TBN 矩阵转换为 GL_INT_2_10_10_10 类型(如果可用),这会将矩阵从 9 x float 压缩到 3 x float。它会降低精度,但大多数情况下不会造成任何可见的影响。你甚至可以走那么远,你将 TBN 作为四元数发送,如果精度足够的话,它将把 9 x 浮点数压缩到一个浮点数。
此外,您可以尝试交错缓冲区(从结构数组形成 VBO)。不确定有没有效果,就算有,也非常GPU-related。对于某些 GPU,它可能会提高顶点缓存的性能。
但是无论如何,如果您需要深入了解这些细节,充其量性能优势通常很小(至少在考虑您正在使用的一些简单的顶点属性方案时),并且在某些时候您只需要接受您的 GPU 无法在您想要的时间内处理您拥有的数据。您可以获得的最大性能存在与硬件相关的限制。
我知道这对你帮助不大,但我仍然希望你能得到一些想法,从哪里寻找可能的解决方案来解决你的问题。