为何我的OpenGL Compute Shader光线追踪仅30fps?求优化方案
瓶颈原因
线程粒度严重不匹配GPU架构
当前设置local_size_x = 1, local_size_y = 1,意味着每个工作组仅包含1个线程。GPU是基于SIMD(单指令多数据)的并行架构,通常以32/64线程为一个执行单元(Wavefront/Warp),这种单线程工作组的设置会导致硬件利用率极低,大部分计算单元处于闲置状态,这是最核心的性能瓶颈。暴力遍历无空间加速
每个像素光线都遍历场景中所有三角形,即使仅20个三角形,若按1080p分辨率计算,总求交次数也达到1920*1080*20 ≈ 4000万次,且随着三角形数量增加,计算量会线性增长。没有任何空间剔除逻辑,大量时间浪费在与光线不相交的三角形求交上。冗余计算开销
getBarycentricCoords函数中重复计算v0、v1,而Triangle结构体已经存储了edge1(vertex2-vertex1)和edge2(vertex3-vertex1),属于完全不必要的重复运算。- 使用
distance(intersectionPoint, rayOrigin)计算交点距离,而由于rayDirection已经归一化,求交得到的t值本身就是光线原点到交点的距离,无需额外计算距离(包含开方操作)。 unProject函数声明了model、proj参数,但实际未使用,属于代码冗余,虽不直接影响性能,但增加了维护成本。
内存访问效率低下
Triangle结构体采用std430布局,vec3类型会被自动对齐到vec4的字节大小,导致结构体内部存在大量内存填充(Padding),浪费内存带宽。同时,线程随机访问不同Triangle元素,无法利用GPU缓存的空间局部性,缓存命中率低。
优化措施
调整工作组大小适配GPU架构
修改工作组尺寸为符合GPU硬件的并行单元大小,例如:layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;16x16的工作组包含256个线程,适配绝大多数GPU的Wavefront/Warp规模,能充分利用GPU的SIMD并行能力,直接提升硬件利用率。
引入空间加速结构
即使三角形数量少,也能通过简单的加速结构减少无效求交:- BVH(包围盒层级结构):在CPU端提前构建BVH,将三角形按空间位置分组,光线先与BVH节点的包围盒求交,仅遍历可能相交的三角形组,大幅减少求交次数。
- 网格划分(Grid):将场景划分为均匀网格,光线仅与经过的网格内的三角形进行求交,避免遍历所有三角形。
消除冗余计算
- 替换
getBarycentricCoords中的重复计算,直接使用结构体已有的edge1和edge2:vec3 getBarycentricCoords(int triangleIndex, vec3 closestIntersectionPoint) { vec3 v0 = triangles[triangleIndex].edge1; vec3 v1 = triangles[triangleIndex].edge2; vec3 v2 = closestIntersectionPoint - triangles[triangleIndex].vertex1; float d00 = dot(v0, v0); float d01 = dot(v0, v1); float d11 = dot(v1, v1); float d20 = dot(v2, v0); float d21 = dot(v2, v1); float denom = d00 * d11 - d01 * d01; float b1 = (d11 * d20 - d01 * d21) / denom; float b2 = (d00 * d21 - d01 * d20) / denom; float b0 = 1.0f - b1 - b2; return vec3(b0, b1, b2); } - 用求交得到的
t值替代distance计算,需要先修改rayTriangleIntersection函数返回t值:
之后在主逻辑中直接比较bool rayTriangleIntersection(vec3 rayOrigin, vec3 rayDirection, int triangleIndex, out vec3 intersectionPoint, out float t) { vec3 h = cross(rayDirection, triangles[triangleIndex].edge2); float a = dot(triangles[triangleIndex].edge1, h); if (a > -0.00001 && a < 0.00001) { return false; } float f = 1.0 / a; vec3 s = rayOrigin - triangles[triangleIndex].vertex1; float u = f * dot(s, h); if (u < 0.0 || u > 1.0) { return false; } vec3 q = cross(s, triangles[triangleIndex].edge1); float v = f * dot(rayDirection, q); if (v < 0.0 || u + v > 1.0) { return false; } t = f * dot(triangles[triangleIndex].edge2, q); if (t > 0.00001) { intersectionPoint = rayOrigin + rayDirection * t; return true; } return false; }t:float t; if (rayTriangleIntersection(rayOrigin, rayDirection, triangleIndex, intersectionPoint, t)) { if (t < closestIntersectionDistance) { closestIntersectionDistance = t; closestIntersectionPoint = intersectionPoint; finalColor = getTriangleColor(triangleIndex, closestIntersectionPoint); intersectionFound = true; } } - 清理
unProject函数的无效参数:vec3 unProject(vec3 win, vec4 viewport) { vec4 tmp = vec4(win, 1); tmp.x = (tmp.x - viewport[0]) / viewport[2]; tmp.y = (tmp.y - viewport[1]) / viewport[3]; tmp.x = tmp.x * 2 - 1; tmp.y = tmp.y * 2 - 1; vec4 obj = inverseViewProjection * tmp; obj /= obj.w; return obj.xyz; }
- 替换
优化内存布局
- 调整Triangle结构体的成员顺序,减少std430布局的内存填充,将同类型成员连续排列:
struct Triangle { vec3 vertex1; vec3 vertex2; vec3 vertex3; vec3 edge1; vec3 edge2; vec3 color1; vec3 color2; vec3 color3; vec3 normal1; vec3 normal2; vec3 normal3; }; - 拆分结构体为多个独立的缓冲区(如顶点缓冲区、边缓冲区、颜色缓冲区),让连续线程访问连续的内存地址,提升缓存命中率。
- 调整Triangle结构体的成员顺序,减少std430布局的内存填充,将同类型成员连续排列:
优化光线生成逻辑
简化光线生成的计算,无需两次调用unProject:void main() { ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); vec2 imgSize = vec2(imageSize(outputImage)); // 计算NDC坐标(加0.5是为了取像素中心) vec2 ndc = (vec2(pixelCoord) + 0.5) / imgSize * 2.0 - 1.0; vec4 clipPos = vec4(ndc.x, ndc.y, -1.0, 1.0); vec4 worldPos = inverseViewProjection * clipPos; worldPos /= worldPos.w; vec3 rayOrigin = cameraPosition; vec3 rayDirection = normalize(worldPos.xyz - rayOrigin); vec3 intersectionPoint; float closestIntersectionDistance = 999999999.0f; vec3 finalColor = vec3(0,0,0); bool intersectionFound = false; float t; for (int triangleIndex = 0; triangleIndex < numTriangles; triangleIndex++) { if (rayTriangleIntersection(rayOrigin, rayDirection, triangleIndex, intersectionPoint, t)) { if (t < closestIntersectionDistance) { closestIntersectionDistance = t; closestIntersectionPoint = intersectionPoint; finalColor = getTriangleColor(triangleIndex, closestIntersectionPoint); intersectionFound = true; } } } imageStore(outputImage, pixelCoord, intersectionFound ? vec4(finalColor, 1.0f) : vec4(0)); }这种方式减少了一次矩阵乘法,同时避免了viewport相关的冗余计算。
内容的提问来源于stack exchange,提问作者ZeunO8

