【问题标题】:GLSL shader generation of normalsGLSL 着色器生成法线
【发布时间】:2009-12-26 20:38:44
【问题描述】:

您好,我正在编写 3D 建模应用程序,我想加快 OpenGL 中的渲染速度。目前我使用 glBegin/glEnd 这真的很慢而且不推荐使用。我需要绘制非常快速的平面阴影模型。我每帧都在 CPU 上生成法线。这是非常缓慢的。我尝试使用带有索引几何的 glDrawElements,但在法线生成中存在问题,因为法线是在顶点而不是三角形级别指定的。
另一个想法是在几何着色器中使用 GLSL 在 GPU 上生成法线。我为正常生成编写了这段代码:

#version 120 
#extension GL_EXT_geometry_shader4 : enable

vec3 NormalFromTriangleVertices(vec3 triangleVertices[3])
{
    // now is same as RedBook (OpenGL Programming Guide)
    vec3 u = triangleVertices[0] - triangleVertices[1];
    vec3 v = triangleVertices[1] - triangleVertices[2];
    return cross(v, u);
}

void main()
{
    // no change of position
    // computes normal from input triangle and front color for that triangle

    vec3 triangleVertices[3];
    vec3 computedNormal;

    vec3 normal, lightDir;
    vec4 diffuse;
    float NdotL;

    vec4 finalColor;

    for(int i = 0; i < gl_VerticesIn; i += 3)
    {
        for (int j = 0; j < 3; j++)
        {
            triangleVertices[j] = gl_PositionIn[i + j].xyz;
        }
        computedNormal = NormalFromTriangleVertices(triangleVertices);
        normal = normalize(gl_NormalMatrix * computedNormal);

        // hardcoded light direction 
        vec4 light = gl_ModelViewMatrix * vec4(0.0, 0.0, 1.0, 0.0);
        lightDir = normalize(light.xyz);

        NdotL = max(dot(normal, lightDir), 0.0);

        // hardcoded
        diffuse = vec4(0.5, 0.5, 0.9, 1.0);

        finalColor = NdotL * diffuse; 
        finalColor.a = 1.0; // final color ignores everything, except lighting

        for (int j = 0; j < 3; j++)
        { 
            gl_FrontColor = finalColor;
            gl_Position = gl_PositionIn[i + j];
            EmitVertex();
        }
    }
    EndPrimitive();
}

当我将着色器集成到我的应用程序中时,速度并没有提高。这比以前更糟。我是 GLSL 和着色器的新手,所以我不知道我做错了什么。 我在配备 Geforce 9400M 的 MacBook 上尝试了此代码。

为了更清楚,这是我要替换的代码:

- (void)drawAsCommandsWithScale:(Vector3D)scale
{
    float frontDiffuse[4] = { 0.4, 0.4, 0.4, 1 };
    CGFloat components[4];
    [color getComponents:components];
    float backDiffuse[4];
    float selectedDiffuse[4] = { 1.0f, 0.0f, 0.0f, 1 };

    for (uint i = 0; i < 4; i++)
        backDiffuse[i] = components[i];

    glMaterialfv(GL_BACK, GL_DIFFUSE, backDiffuse);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, frontDiffuse);

    Vector3D triangleVertices[3];

    float *lastDiffuse = frontDiffuse; 

    BOOL flip = scale.x < 0.0f || scale.y < 0.0f || scale.z < 0.0f;

    glBegin(GL_TRIANGLES);

    for (uint i = 0; i < triangles->size(); i++)
    {
        if (selectionMode == MeshSelectionModeTriangles) 
        {
            if (selected->at(i))
            {
                if (lastDiffuse == frontDiffuse)
                {
                    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, selectedDiffuse);
                    lastDiffuse = selectedDiffuse;
                }
            }
            else if (lastDiffuse == selectedDiffuse)
            {
                glMaterialfv(GL_BACK, GL_DIFFUSE, backDiffuse);
                glMaterialfv(GL_FRONT, GL_DIFFUSE, frontDiffuse);
                lastDiffuse = frontDiffuse;
            }
        }    
        Triangle currentTriangle = [self triangleAtIndex:i];
        if (flip)
            currentTriangle = FlipTriangle(currentTriangle);

        [self getTriangleVertices:triangleVertices fromTriangle:currentTriangle];
        for (uint j = 0; j < 3; j++)
        {
            for (uint k = 0; k < 3; k++)
            {
                triangleVertices[j][k] *= scale[k];
            }
        }
        Vector3D n = NormalFromTriangleVertices(triangleVertices);
        n.Normalize();
        for (uint j = 0; j < 3; j++)
        {
            glNormal3f(n.x, n.y, n.z);
            glVertex3f(triangleVertices[j].x, triangleVertices[j].y, triangleVertices[j].z);            
        }
    }

    glEnd();    
}

如您所见,它的效率非常低,但可以正常工作。trianglesvertices 数组的索引数组。

我尝试使用此代码进行绘图,但我不能只有一个索引数组而不是两个(一个用于顶点,第二个用于法线)。

glEnableClientState(GL_VERTEX_ARRAY);

uint *trianglePtr = (uint *)(&(*triangles)[0]); 
float *vertexPtr = (float *)(&(*vertices)[0]);

glVertexPointer(3, GL_FLOAT, 0, vertexPtr);
glDrawElements(GL_TRIANGLES, triangles->size() * 3, GL_UNSIGNED_INT, trianglePtr);
glDisableClientState(GL_VERTEX_ARRAY);

现在,当某些顶点由不同的三角形共享时,我如何指定指向法线的指针,因此它们的法线不同?

【问题讨论】:

  • glVertexPointer配合glDrawElements有什么问题?您可以使用任何您想计算每个顶点的法线的顶点法线算法。
  • btw 您的着色器现在用于每个三角形。这将是主要的瓶颈。你的出路是 glDrawElements 或 VBOs。
  • 棘手的是我不想为每个顶点生成法线,而是为每个三角形生成法线。
  • 这并不奇怪,因为点没有“本机”正常。顶点的法线通常是“附着”到它的所有表面的法线的平均值。这种正态近似值的准确性将决定您的照明质量。

标签: optimization glsl


【解决方案1】:

所以我终于设法提高了渲染速度。我只在顶点或三角形发生变化时重新计算 CPU 上的法线,这仅在一个网格中而不是在整个场景中工作时才会发生。 这不是我想要的解决方案,但在现实世界中它比以前的方法更好。
我将整个几何图形缓存到单独的法线和顶点数组中,不能使用索引绘图,因为我想要平面阴影(类似于 3ds max 中的平滑组问题)。
我使用简单的glDrawArrays 和照明顶点着色器,这是因为我希望在三角形模式下为选定的三角形使用不同的颜色,为未选择的三角形使用另一种颜色,并且没有材料数组(我没有找到任何材料)。

【讨论】:

    【解决方案2】:

    您通常不会在每帧计算法线,只有在几何形状发生变化时才计算。 并且每个三角形都有一个法线,只需为三角形中的每个顶点设置相同的法线。这确实意味着您不能在网格中的相邻三角形之间共享顶点,但这在这类事情中并不罕见。

    【讨论】:

    • 我看不懂,例如立方体有 12 个三角形和 8 个顶点。所以我需要 12 个法线、12 个索引和 8 个顶点。我不知道如何将其调用到 OpenGL。我想到的唯一方法是做 12 * 3 顶点,将共享顶点复制到此缓冲区并做 12 * 3 法线并牺牲索引绘图,这将起作用,但很难看。
    • 你是对的,例如,法线不会改变游戏中的每一帧,但我正在编写应用程序,它允许选择三角形网格的顶点并对其进行缩放、旋转等。所以法线改变了很多,在每一帧。我可以通过只更新已更改的三角形来加快速度,但这在某些情况下无济于事,所以我希望它通常快。
    【解决方案3】:

    你的问题让我想起了这篇 Normals without Normals 博文。

    【讨论】:

    • 不错的文章,但对我来说太多了。
    猜你喜欢
    • 2012-01-09
    • 2012-01-12
    • 1970-01-01
    • 2013-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多