OpenGL虚拟球体:计算深度值时遇到的问题

5
我正在尝试将一个球渲染成冒牌者,但是在计算球面上点的深度值时遇到了问题。
当我围绕冒牌者球和与球相交的“真实”立方体移动相机时,您可以看到发生了什么。

IMG

你可以看到,当我移动相机时,深度值不一致,有些立方体的部分应该在球体内,但根据相机位置的不同,会弹出和弹入球体。
这是伪装球体的顶点着色器和片段着色器。 顶点着色器:
#version 450 core

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec2 coords;

void main()
{
    switch (gl_VertexID)
    {
    case 0:
        coords = vec2(-1.0f, -1.0f);
        break;
    case 1:
        coords = vec2(-1.0f, +1.0f);
        break;
    case 2:
        coords = vec2(+1.0f, -1.0f);
        break;
    case 3:
        coords = vec2(+1.0f, +1.0f);
        break;
    }

    // right & up axes camera space
    vec3 right = vec3(view[0][0], view[1][0], view[2][0]);
    vec3 up = vec3(view[0][1], view[1][1], view[2][1]);

    vec3 center = vec3(0.0f);
    float radius = 1.0f;

    // vertex position
    vec3 position = radius * vec3(coords, 0.0f);
    position = right * position.x + up * position.y;

    mat4 MVP = projection * view * model;
    gl_Position = MVP * vec4(center + position, 1.0f);
}

片元着色器 :

#version 450 core

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

in vec2 coords;
out vec4 pixel;

void main()
{
    float d = dot(coords, coords);

    if (d > 1.0f)
    {
        discard;
    }

    float z = sqrt(1.0f - d);

    vec3 normal = vec3(coords, z);
    normal = mat3(transpose((view * model))) * normal;

    vec3 center = vec3(0.0f);
    float radius = 1.0f;

    vec3 position = center + radius * normal;

    mat4 MVP = projection * view * model;

    // gl_DepthRange.diff value is the far value minus the near value

    vec4 clipPos = MVP * vec4(position, 1.0f);
    float ndcDepth = clipPos.z / clipPos.w;
    gl_FragDepth = ((gl_DepthRange.diff * ndcDepth) + gl_DepthRange.near + gl_DepthRange.far) * 0.5f;

    pixel = vec4((normal + 1.0f) * 0.5f, 1.0f);
}

我按照 这个例子 进行了深度值的计算。
我传递给着色器的模型矩阵是一个单位矩阵,因此它不会影响操作。
非常感谢您的帮助!!

嗯,这很奇怪。初看起来,所有的一切对我来说似乎都很正常。只是为了确保:您没有在程序中调用 glClipControl() 吗? - Michael Kenzel
谢谢你的回答!我从来没有调用过那个函数。我也没有接触到near和far值(它们应该分别为0和1),因此深度计算中的最后一行应该等同于 gl_FragDepth = (ndcDepth + 1.0f) * 0.5f;,它仅将剪裁空间中的深度值从范围[-1, +1]映射到范围[0, 1]。我认为代码几乎是正确的,但缺少了一些东西,特别是当物体之间的深度值很接近时。如果您有任何其他想法或认为检查代码的其他部分可能有用,请告诉我!非常感谢! - Arctic Pi
@ArcticPi:"我按照这个例子计算深度值。" 但是...你没有使用教程的其余部分。就像这一页所解释的那样,它解释了你正在尝试使用的方法不起作用的原因,并且你应该对球的位置进行光线追踪。 - Nicol Bolas
@NicolBolas 感谢您的回答!是的,我阅读了教程的所有页面。我解决了问题,即通过视图模型矩阵的转置来乘以法向量。这样,我可以在相机移动时更新法向量,然后将其用于光处理。教程中解释的射线投射方法存在一个问题,即必须扩展四边形的大小,而作者仅建议使用大约2的近似比例因子,而没有显示此值适用于所有可能的情况。 - Arctic Pi
2
@ArcticPi:“我解决了提到的问题,即将法向量乘以视图模型矩阵的转置。”这实际上并没有解决您的深度问题;它仍然存在,但在当前环境中不明显。教程之所以必须扩展四边形的大小是因为这就是现实的工作方式。在透视投影下,一个球体不占用正方形大小的区域。因此,如果要呈现一个冒牌球体,您必须扩展该区域。 - Nicol Bolas
2个回答

5
你的深度计算没问题。你遇到的问题本质上与你从中获取深度计算的教程中概述的问题相同。它是该问题的不同"表现形式",但它们都源于同一个问题:与现实有关的位置计算不正确。因此,错误的值被提供给深度计算;难怪你没有得到正确的结果。
这是你基本问题的夸张的二维表示:

A picture of a circle impostor, with a close-up viewer

我们正在查看Point,根据你的impostor代码,我们将通过垂直投影到平面上,直到它碰到球体来计算Point的位置。该计算得出点Impostor。
然而,正如您所见,这并不是现实所显示的。如果我们从Point到View画一条线(表示我们应该在该方向从View看到的内容),我们看到的球体上的第一个位置是Real。而那离Impostor非常远。
由于根本问题在于你的impostor位置计算是错误的,唯一的解决方法是使用正确的impostor计算方法。而正确的方法是(以某种方式)通过从View对球体进行光线追踪。这就是教程所做的事情

非常感谢您的回答!!我已经尝试使用光线追踪,但是遇到了一些问题。明天早上我会再次尝试,并告诉您结果。再次感谢! - Arctic Pi

1

好的,我认为我最终解决了这个问题。

我按照@NicolBolas建议使用光线追踪,并跟随教程进行操作,这是结果。

enter image description here

这是伪装球体的顶点着色器和片段着色器。
顶点着色器:
#version 330

out vec2 mapping;

uniform mat4 view;
uniform mat4 cameraToClipMatrix;

const float sphereRadius = 1.0f;
const vec3 worldSpherePos = vec3(0.0f);

const float g_boxCorrection = 1.5;

void main()
{
    vec2 offset;
    switch(gl_VertexID)
    {
    case 0:
        //Bottom-left
        mapping = vec2(-1.0, -1.0) * g_boxCorrection;
        offset = vec2(-sphereRadius, -sphereRadius);
        break;
    case 1:
        //Top-left
        mapping = vec2(-1.0, 1.0) * g_boxCorrection;
        offset = vec2(-sphereRadius, sphereRadius);
        break;
    case 2:
        //Bottom-right
        mapping = vec2(1.0, -1.0) * g_boxCorrection;
        offset = vec2(sphereRadius, -sphereRadius);
        break;
    case 3:
        //Top-right
        mapping = vec2(1.0, 1.0) * g_boxCorrection;
        offset = vec2(sphereRadius, sphereRadius);
        break;
    }

    vec3 cameraSpherePos = vec3(view * vec4(worldSpherePos, 1.0));

    vec4 cameraCornerPos = vec4(cameraSpherePos, 1.0);
    cameraCornerPos.xy += offset * g_boxCorrection;

    gl_Position = cameraToClipMatrix * cameraCornerPos;
}

片段着色器 :

#version 330

in vec2 mapping;

out vec4 outputColor;

uniform mat4 view;
uniform mat4 cameraToClipMatrix;

const float sphereRadius = 1.0f;
const vec3 worldSpherePos = vec3(0.0f);

uniform vec3 eye;

void Impostor(out vec3 cameraPos, out vec3 cameraNormal)
{
    vec3 cameraSpherePos = vec3(view * vec4(worldSpherePos, 1.0));

    vec3 cameraPlanePos = vec3(mapping * sphereRadius, 0.0) + cameraSpherePos;
    vec3 rayDirection = normalize(cameraPlanePos);

    float B = 2.0 * dot(rayDirection, -cameraSpherePos);
    float C = dot(cameraSpherePos, cameraSpherePos) - (sphereRadius * sphereRadius);

    float det = (B * B) - (4 * C);
    if(det < 0.0)
        discard;

    float sqrtDet = sqrt(det);
    float posT = (-B + sqrtDet)/2;
    float negT = (-B - sqrtDet)/2;

    float intersectT = min(posT, negT);
    cameraPos = rayDirection * intersectT;
    cameraNormal = normalize(cameraPos - cameraSpherePos);
}

void main()
{
    vec3 cameraPos;
    vec3 cameraNormal;

    Impostor(cameraPos, cameraNormal);

    //Set the depth based on the new cameraPos.
    vec4 clipPos = cameraToClipMatrix * vec4(cameraPos, 1.0);
    float ndcDepth = clipPos.z / clipPos.w;
    gl_FragDepth = ((gl_DepthRange.diff * ndcDepth) + gl_DepthRange.near + gl_DepthRange.far) / 2.0;

    cameraNormal = mat3(transpose(view)) * cameraNormal;

    outputColor = vec4((cameraNormal + 1.0f) * 0.5f, 1.0f);
}

我只是稍微修改了教程中的着色器来管理相机运动。

我想问你一些问题。

  • 正如您所看到的,我仍然将每个片段的法线乘以视图矩阵的转置。这样做是否正确?通过这种方式,当我围绕它移动相机时,我可以确保看到球体“定向”在正确的方向上吗?

  • 关于盒子大小的校正,如何确保将四边形的大小增加50%适用于每种情况?在教程中提到,为了找出四边形的确切大小,需要考虑视口和透视矩阵。您知道如何执行此计算吗?

  • 在这篇著名的论文中,似乎他们没有使用光线追踪来获得相同的结果。但不幸的是,我不知道他们是如何做到的。

enter image description here

现在我需要在同一个场景中渲染数万甚至数十万个冒牌球。我想知道射线追踪计算是否对这么多球太昂贵了。此外,我还将添加光照的计算成本。我知道还有一种叫做射线行进的技术,应该更便宜,但目前我不知道它是否适用于我的情况。谢谢你的帮助! :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接