8

我正在开发一个用于分子 3D 可视化的小工具。对于我的项目,我选择像“Brad Larson”先生用他的苹果软件“ Molecules ”所做的那样做一件事。一个链接,您可以在其中找到所用技术的小型演示:Brad Larsson 软件演示

为了完成我的工作,我必须计算球体冒名顶替者圆柱冒名顶替者

目前,我已经在另一个教程Lies and Impostors的帮助下成功完成了“Sphere Impostor”

总结一下球体冒名顶替的计算:首先我们将“球体位置”和“球体半径”发送到“顶点着色器”,这将在相机空间中创建一个始终面向相机的正方形,然后我们发送我们的正方形到片段着色器,我们使用简单的光线追踪来查找正方形的哪个片段包含在球体中,最后我们计算片段的法线和位置来计算光照。(另一件事我们还编写了 gl_fragdepth 来为我们的冒名顶替球体提供良好的深度!)

但是现在我在计算圆柱体冒名顶替者时受阻,我尝试在球体冒名顶替者和圆柱体冒名顶替者之间做一个平行,但我什么也没找到,我的问题是对于球体来说这很容易,因为球体是不管我们怎么看总是一样的,我们总是看到同样的东西:“一个圆”,另一件事是球体是由数学完美定义的,然后我们可以很容易地找到计算照明的位置和法线,并创建我们的骗子。

对于圆柱体,它不是一回事,我没有找到可以用作“圆柱体冒名顶替者”的形式建模的提示,因为圆柱体根据我们看到的角度显示出许多不同的形式!

所以我的要求是向您询问我的“冒名顶替者”问题的解决方案或指示。

4

4 回答 4

3

圆柱体冒名顶替者实际上可以像球体一样完成,就像Nicol Bolas在他的教程中所做的那样。您可以制作一个面向相机的正方形并将其着色,使其看起来像一个圆柱体,就像 Nicol 为球体所做的那样。这并不

它的完成方式当然是光线追踪。请注意,在相机空间中朝上的圆柱体很容易实现。例如,与边的交点可以投影到 xz 平原,这是一条线与圆相交的二维问题。得到顶部和底部也不难,交点的z坐标给出,所以你实际上知道射线和圆的平面的交点,你所要做的就是检查它是否在圆内。基本上,就是这样,你得到两分,然后返回更接近的一分(法线也很微不足道)。

当涉及到任意轴时,结果几乎是相同的问题。当您在固定轴圆柱体上求解方程时,您正在求解一个参数,该参数描述了从给定点沿给定方向到达圆柱体需要多长时间。从它的“定义”来看,你应该注意到,如果你旋转世界,这个参数不会改变。因此,您可以将任意轴旋转为 y 轴,在方程更容易的空间中解决问题,在该空间中获取线方程的参数,但在相机空间中返回结果。

您可以从这里下载着色器文件。只是它在行动的形象: 截屏

魔术发生的代码(它只是很长,因为它充满了注释,但代码本身最多 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;
}   
于 2013-05-28T00:48:03.183 回答
3

我知道这个问题已经有一年多了,但我仍然想给我的 2 美分。

我能够用另一种技术制作圆柱体冒名顶替者,我从 pymol 的代码中获得灵感。以下是基本策略:

1)您想为圆柱体绘制一个边界框(一个长方体)。要做到这一点,您需要 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

于 2013-05-08T19:51:55.033 回答
2

除了 pygabriels 回答之外,我还想分享一个使用来自 Blaine Bell(PyMOL,Schrödinger,Inc.)的上述着色器代码的独立实现。

pygabriel 解释的方法也可以改进。边界框可以以这样的方式对齐,它总是面向观察者。最多只能看到两张脸。因此,只需要 6 个顶点(即,由 4 个三角形组成的两个面)。

看这里的图片,盒子(它的方向向量)总是面向观察者:
图片:对齐的边界框

源码下载:圆柱冒名顶替源代码

该代码不包括圆帽和正交投影。它使用几何着色器进行顶点生成。您可以根据 PyMOL 许可协议使用着色器代码。

于 2013-05-24T19:55:56.020 回答
2

根据我对论文的理解,我将其解释如下。

从任何角度看,冒名顶替圆柱体具有以下特征。

  1. 从顶部看,它是一个圆圈。所以考虑到你永远不需要从上到下查看圆柱体,你不需要渲染任何东西。
  2. 从侧面看,它是一个矩形。像素着色器只需要照常计算照明。
  3. 从任何其他角度看,它是一个弯曲的矩形(与步骤 2 中计算的相同)。它的曲率可以在像素着色器内部建模为顶部椭圆的曲率。这个曲率可以被认为是纹理空间中每个“列”的简单偏移,具体取决于视角。这个椭圆的短轴可以通过将长轴(圆柱体的厚度)乘以当前视角的因子(角度/90)来计算,假设 0 表示您正在查看圆柱体的侧面。

图。1。 视角。在下面的数学中,我只考虑了 0-90 的情况,但其他情况略有不同。

图 2。 给定视角 (phi) 和圆柱体的直径 (a),这里是着色器需要在纹理空间 Y = b' sin(phi) 中扭曲 Y 轴的方式。b' = a * (phi / 90)。永远不应该渲染 phi = 0 和 phi = 90 的情况。

当然,我没有考虑这个圆柱体的长度——这取决于你的特定投影,而不是图像空间问题。

于 2012-03-07T14:43:31.723 回答