GLSL中的圆柱体冒充者

9
我正在开发一个用于分子三维可视化的小工具。为了实现这个项目,我选择采用 "Brad Larson" 先生的苹果软件 "Molecules" 所使用的方法。您可以在此链接中找到有关该技术的简要介绍:Brad Larsson 软件介绍
为了完成我的工作,我必须计算 球体冒充物圆柱体冒充物
目前,我已经成功地完成了 "球体冒充物",并在另一个教程Lies and Impostors 的帮助下完成了它。
总结一下球体冒充物的计算过程:首先,我们将 "球体位置" 和 "球体半径" 发送到 "顶点着色器" 中,在相机空间中创建一个始终面向相机的正方形,然后我们将正方形发送到片段着色器中,在那里我们使用简单的光线追踪来查找包含在球体中的正方形的哪个片段,并最终计算出片段的法线和位置以计算照明。(另外我们还编写了 gl_fragdepth,以为我们的冒充球提供良好的深度!)
但现在我在计算圆柱体冒充物时遇到了阻碍。我试图在球体冒充物和圆柱体冒充物之间进行对比,但是我没有找到任何线索。我的问题是,对于球体来说很容易,因为无论如何我们看它,我们总是会看到同样的东西:“一个圆”。并且球体完全由数学定义,因此我们可以轻松地找到位置和法线以计算照明并创建我们的冒充物。
对于圆柱体而言,情况不同,我无法找到一个适用于 "圆柱体冒充物" 的模型,因为圆柱体根据我们看它的角度而显示许多不同的形式!
所以我的请求是,请您给出关于我的 "圆柱体冒充物" 问题的解决方案或指示。

3
为什么你要使用一个冒牌货来完成这个任务?为什么不直接画一个圆柱体呢?此外,我在写那篇教程时选择球体而不是圆柱体是有原因的。球体是对称的;它们由位置和半径定义。光线追踪数学很简单。圆柱体则*复杂得多。你最好只需从教程中提取我使用的圆柱模型并进行渲染。 - Nicol Bolas
正如我所说,我为学校项目开发了一个小型的三维分子可视化工具,因此我决定首先执行3D球体冒充和圆柱冒充,这是根据Brad Larson在他的应用程序中使用的技术。另一个原因是,冒充比绘制由数百个多边形组成的真实圆柱更轻,而所有这些对于分子的三维可视化非常重要,因为将计算大量分子!但是,如果你告诉我这太难了,我开始有点害怕了? - nadir
我无法回答你的问题,但是Larsson所链接的论文非常有趣,感谢你提供这个链接。如果我要给出建议,我会说只需使用球体并忽略圆柱形:p。 - Robinson
谢谢,是的,如果我得不到我的问题的答案,我将不得不使用基本的OpenGL圆柱体,但我真的想要实现圆柱体冒牌货,然而目前我的知识不足以找到解决问题的思路! - nadir
毫无疑问,您已经开始了,但是一个很好的起点是首先使用gl_LINES绘制链接,然后像您一样绘制球体。一旦工作正常,请重新访问使用圆柱拟像渲染链接。 - Will
显示剩余2条评论
4个回答

3

我知道这个问题已经超过一年了,但我仍然想提供我的意见。

我能够使用另一种技术来生成圆柱体冒充者,我从 pymol 的代码中获得了灵感。以下是基本策略:

1)您想要为圆柱体绘制一个边界框(一个cuboid)。为此,您需要6个面,即18个三角形,即36个三角形顶点。假设您无法访问几何着色器,则需要将圆柱体的起始点传递给顶点着色器36次,将圆柱体的方向传递给顶点着色器36次,并且对于每个顶点,您将传递相应的边界框点。例如,与点(0,0,0)相关联的顶点意味着它将被转换为边界框的左下后角,(1,1,1)表示对角线相反的点等。

2)在顶点着色器中,您可以通过根据传递的相应点位移每个顶点(您传递了36个相等的顶点)来构建圆柱体的点。在此步骤结束时,您应该有一个圆柱体的边界框。

3) 在此,您需要重构边界框上可见表面的点。从获得的点开始,必须执行射线-圆柱相交。

4) 从相交点可以重建深度和法线。您还必须丢弃在边界框外找到的相交点(当您沿着圆柱轴查看圆柱时,相交点将无限远)。

顺便说一下,这是一个非常困难的任务,如果有人感兴趣,这里是源代码:

https://github.com/chemlab/chemlab/blob/master/chemlab/graphics/renderers/shaders/cylinderimp.frag

https://github.com/chemlab/chemlab/blob/master/chemlab/graphics/renderers/shaders/cylinderimp.vert


3
除了pygabriel的答案之外,我想分享一个使用Blaine Bell的着色器代码(来自PyMOL,Schrödinger,Inc.)的独立实现。
正如pygabriel所解释的那样,这种方法也可以改进。边界框可以对齐,以便它始终面向观察者。最多只有两个面是可见的。因此,只需要6个顶点(即由4个三角形组成的两个面)。
请参见此处的图片,盒子(其方向矢量)始终面向观察者:
Image: Aligned bounding box 要获取源代码,请下载:圆柱冒牌源代码 该代码不包括圆形端盖和正交投影。它使用几何着色器进行顶点生成。您可以根据PyMOL许可协议使用着色器代码。

我想分享一下,Inigo Quilez提供了很好的相交例程,非常适合与您建议的边界框一起使用。将视图空间的开始和结束位置(统一)以及视图空间的顶点位置(在内部)传递到片段着色器中,并使用插值的顶点位置来计算当前片段的射线方向。‍♂️ - coastwise

3
一个圆柱体的模拟可以像球一样做,就像Nicol Bolas在他的教程中所做的那样。你可以制作一个面对相机的正方形并将其着色,使其看起来像一个圆柱体,就像Nicol为球所做的那样。而且这并不难。
它的实现方式当然是光线追踪。注意,在相机空间中朝上的圆柱体有点容易实现。例如,侧面的交点可以投影到xz平面上,这是一个直线与圆相交的二维问题。获取顶部和底部也不难,交点的z坐标已知,因此你实际上知道了射线和圆的平面的交点,你需要做的就是检查它是否在圆内。基本上,你会得到两个点,并返回更近的一个(法线也很简单)。
当涉及到任意轴时,它几乎是相同的问题。当你在固定轴圆柱上解方程时,你是在为描述从给定点沿给定方向到达圆柱所需的时间参数解方程。从它的“定义”中,你应该注意到,如果旋转世界,这个参数不会改变。因此,你可以将任意轴旋转成为y轴,在更容易解方程的空间中解决问题,获得该空间中线性方程的参数,但在摄像机空间返回结果。
你可以从这里下载着色器文件。只是一个它在运行中的图像: screenshot 魔法发生的代码(它很长只是因为它充满了注释,但代码本身最多只有50行):
void CylinderImpostor(out vec3 cameraPos, out vec3 cameraNormal)
{
    // First get the camera space direction of the ray.
    vec3 cameraPlanePos = vec3(mapping * max(cylRadius, cylHeight), 0.0) + cameraCylCenter;
    vec3 cameraRayDirection = normalize(cameraPlanePos);

    // Now transform data into Cylinder space wherethe cyl's symetry axis is up.
    vec3 cylCenter = cameraToCylinder * cameraCylCenter;
    vec3 rayDirection = normalize(cameraToCylinder * cameraPlanePos);


    // We will have to return the one from the intersection of the ray and circles,
    // and the ray and the side, that is closer to the camera. For that, we need to
    // store the results of the computations.
    vec3 circlePos, sidePos;
    vec3 circleNormal, sideNormal;
    bool circleIntersection = false, sideIntersection = false;

    // First check if the ray intersects with the top or bottom circle
    // Note that if the ray is parallel with the circles then we
    // definitely won't get any intersection (but we would divide with 0).
    if(rayDirection.y != 0.0){
        // What we know here is that the distance of the point's y coord
        // and the cylCenter is cylHeight, and the distance from the
        // y axis is less than cylRadius. So we have to find a point
        // which is on the line, and match these conditions.

        // The equation for the y axis distances:
        // rayDirection.y * t - cylCenter.y = +- cylHeight
        // So t = (+-cylHeight + cylCenter.y) / rayDirection.y
        // About selecting the one we need:
        //  - Both has to be positive, or no intersection is visible.
        //  - If both are positive, we need the smaller one.
        float topT = (+cylHeight + cylCenter.y) / rayDirection.y;
        float bottomT = (-cylHeight + cylCenter.y) / rayDirection.y;
        if(topT > 0.0 && bottomT > 0.0){
            float t = min(topT,bottomT);

            // Now check for the x and z axis:
            // If the intersection is inside the circle (so the distance on the xz plain of the point,
            // and the center of circle is less than the radius), then its a point of the cylinder.
            // But we can't yet return because we might get a point from the the cylinder side
            // intersection that is closer to the camera.
            vec3 intersection = rayDirection * t;
            if( length(intersection.xz - cylCenter.xz) <= cylRadius ) {
                // The value we will (optianally) return is in camera space.
                circlePos = cameraRayDirection * t;
                // This one is ugly, but i didn't have better idea.
                circleNormal = length(circlePos - cameraCylCenter) <
                               length((circlePos - cameraCylCenter) + cylAxis) ? cylAxis : -cylAxis;
                circleIntersection = true;
            }
        }
    }


    // Find the intersection of the ray and the cylinder's side
    // The distance of the point and the y axis is sqrt(x^2 + z^2), which has to be equal to cylradius
    // (rayDirection.x*t - cylCenter.x)^2 + (rayDirection.z*t - cylCenter.z)^2 = cylRadius^2
    // So its a quadratic for t (A*t^2 + B*t + C = 0) where:
    // A = rayDirection.x^2 + rayDirection.z^2 - if this is 0, we won't get any intersection
    // B = -2*rayDirection.x*cylCenter.x - 2*rayDirection.z*cylCenter.z
    // C = cylCenter.x^2 + cylCenter.z^2 - cylRadius^2
    // It will give two results, we need the smaller one

    float A = rayDirection.x*rayDirection.x + rayDirection.z*rayDirection.z;
    if(A != 0.0) {
        float B = -2*(rayDirection.x*cylCenter.x + rayDirection.z*cylCenter.z);
        float C = cylCenter.x*cylCenter.x + cylCenter.z*cylCenter.z - cylRadius*cylRadius;

        float det = (B * B) - (4 * A * C);
        if(det >= 0.0){
            float sqrtDet = sqrt(det);
            float posT = (-B + sqrtDet)/(2*A);
            float negT = (-B - sqrtDet)/(2*A);

            float IntersectionT = min(posT, negT);
            vec3 Intersect = rayDirection * IntersectionT;

            if(abs(Intersect.y - cylCenter.y) < cylHeight){
                // Again it's in camera space
                sidePos = cameraRayDirection * IntersectionT;
                sideNormal = normalize(sidePos - cameraCylCenter);
                sideIntersection = true;
            }
        }
    }

    // Now get the results together:
    if(sideIntersection && circleIntersection){
        bool circle = length(circlePos) < length(sidePos);
        cameraPos = circle ? circlePos : sidePos;
        cameraNormal = circle ? circleNormal : sideNormal;
    } else if(sideIntersection){
        cameraPos = sidePos;
        cameraNormal = sideNormal;
    } else if(circleIntersection){
        cameraPos = circlePos;
        cameraNormal = circleNormal;
    } else
        discard;
}   

2
据我理解这篇论文,我可以这样解释:
一个假想的圆柱体,从任何角度看都具备以下特点:
1. 从上方看是一个圆形。所以考虑到你永远不需要从上方观察圆柱体,你不需要渲染任何东西。
2. 从侧面看是一个矩形。像素着色器只需要像往常一样计算照明。
3. 从其他任何角度看都是一个曲折的矩形(在步骤2中计算出的相同矩形)。它的曲率可以在像素着色器内被建模为顶部椭圆的曲率。这个曲率可以被认为是每个“列”在纹理空间中的偏移量,取决于观察角度。该椭圆的次轴可以通过将主轴(圆柱体的厚度)乘以当前视角的因子(角度/90),假设0表示您侧视圆柱体来计算。
查看角度。我只考虑了数学公式中的0-90情况,但其他情况本质上是不同的。
给定观察角(φ)和圆柱体直径(a),下面是着色器如何扭曲纹理空间Y轴的方式:Y = b' sin(φ)。b' = a *(φ/90)。 φ = 0和φ = 90的情况不应该被渲染。
当然,我没有考虑这个圆柱体的长度 - 这将取决于你特定的投影,并且不是一个图像空间问题。

感谢您的解释,我对我的问题有了更好的理解,但是我仍然不知道如何将您所说的所有内容联系起来并实现它,因此如果您有时间再详细解释一下,那对我来说会非常有帮助。 无论如何,感谢您以前的回答! - nadir
非常感谢您清晰的解释,我只有几个问题,首先,“纹理空间”是什么意思? 为了研究这些情况并找到观察角度,我认为我需要“法线”,但我的圆柱体只是虚拟物体,因此法线是在片段着色器中为每个片段计算的,这就是球体虚拟物体发生的情况, 如何在没有法线的情况下找到这个观察角度! - nadir
简单来说,纹理空间就是(u,v)空间 - 在片段着色器内部处理的空间。在上面的解释中,我根本没有考虑光照 - 我所解释的都是如何随着视角变化(围绕X轴)创建曲率。我建议您先获得一个基本的圆柱体冒牌渲染而不带光照,然后再稍后添加光照效果。 - Ani

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