通过单位球体中心的平面在 OpenGL ES 2.0 中出现偏移

Plane through centre of unit sphere appears offset in OpenGL ES 2.0

我正在使用 OpenGL ES 2.0 的 vispy 界面在球体上绘制一些海岸线数据。我正在使用纬度和经度值计算球体上数据的 3d 坐标并绘制这些坐标。我能够成功绘制数据,但我只想查看球体侧面的那些从视口可见的数据点。

我尝试了两种截然不同的方法来创建这种效果,但都导致了同样的问题。首先,我计算了视图方向和数据位置的点积,并只绘制那些结果为负的点(即仅那些面向视口的点),其次,我简单地绘制了一个通过球体中心的平面,垂直于查看方向。

在这两种情况下,我观​​察到相同的情况 - 平面似乎稍微偏离视口,在球体中心后面。换句话说,您可以看到数据在被平面遮盖之前稍微环绕球体的背面。

我已经检查过我绘制的点实际上在单位球体上,而且我相信,从 3d 世界的角度来看,一切都是正确的。作为 3d 图形的相对初学者,我不太自信的是我是否对投影矩阵有误解。我读过一些书 - 但我的理解使我认为投影不应该改变 "Z direction"(视口所面对的方向)中的点顺序。

我相信这不是深度测试问题,因为我的第一种方法没有启用深度测试并且遮罩是在顶点着色器中完成的(通过设置片段颜色 alpha 到 0.0)。除此之外,我找不到任何其他解释这个问题的方法。

这是平面进近的代码:

import numpy as np
import cartopy
from vispy import app
from vispy import gloo
import time
from vispy.util.transforms import perspective, translate, rotate

xpts = []
ypts = []

#getting coastlines data

for string in cartopy.feature.NaturalEarthFeature('physical', 'coastline', '10m').geometries():
    for line in string:
        points = list(line.coords)
        for point in points:
            xpts.append(point[0])
            ypts.append(point[1])

coasts = np.array(zip(xpts,ypts), dtype=np.float32)

theta = (np.pi/180)*np.array(xpts, dtype=np.float32)
phi = (np.pi/180)*np.array(ypts, dtype=np.float32)

x3d = np.cos(phi)*np.cos(theta)
y3d = np.sin(theta)*np.cos(phi)
z3d = np.sin(phi)



vertex = """
// Uniforms
uniform mat4 u_model;
uniform mat4 u_view;
uniform mat4 u_projection;
uniform vec3 u_color;

attribute vec3 a_position;
void main (void)
{
    gl_Position = u_projection*u_view*u_model*vec4(a_position, 1.0);
}
"""

fragment = """
// Uniforms
uniform vec3 u_color;

void main()
{
    gl_FragColor = vec4(u_color, 1.0);
}
"""

class Canvas(app.Canvas):
    def __init__(self):
        app.Canvas.__init__(self, keys='interactive')

        gloo.set_state(clear_color = 'red', depth_test=True, blend=True, blend_func=('src_alpha', 'one_minus_src_alpha'))

        self.x = 0

        self.plane = 5*np.array([(0.,-1., -1.,1), (0, -1., +1.,1), (0, +1., -1.,1), (0, +1., +1.,1)], dtype=np.float32)

        self._timer = app.Timer(connect=self.on_timer, start=True)
        self.program = gloo.Program(vertex, fragment)

        self.view = np.dot(rotate(-90, (1, 0, 0)), np.dot(translate((-3, 0, 0)), rotate(-90.0, (0.0,1.0,0.0))))
        self.model = np.eye(4, dtype=np.float32)
        self.projection = perspective(45.0, self.size[0]/float(self.size[1]), 2.0, 10.0)

        self.program['u_projection'] = self.projection
        self.program['u_view'] = self.view
        self.program['u_model'] = self.model
        self.program['u_color'] = np.array([0.0, 0.0, 0.0], dtype=np.float32)

        self.program2 = gloo.Program(vertex, fragment)

        self.program2['u_projection'] = self.projection
        self.program2['u_view'] = self.view
        self.program2['u_model'] = self.model
        self.program2['u_color'] = np.array([1.0, 1.0, 1.0], dtype=np.float32)

        self.program2['a_position'] =  self.plane[:,:3].astype(np.float32)

    def on_timer(self, event):
        self.x += 0.05
        self.model = rotate(self.x, (0.0,0.0,1.0))
        pointys = np.concatenate((x3d,y3d,z3d)).reshape((3, -1)).T
        self.program['a_position'] = pointys
        self.program['u_model'] = self.model

        self.update()


    def on_resize(self, event):
        gloo.set_viewport(0, 0, *event.size)
        self.projection = perspective(45.0, event.size[0]/float(event.size[1]), 2.0, 10.0)
        self.program['u_projection'] = self.projection
        self.program2['u_projection'] = self.projection


    def on_draw(self, event):
        gloo.clear((1,1,1,1))
        self.program2.draw('triangle_strip')
        self.program.draw('points')

Canvas().show()
app.run()

根据我对你的描述的理解,你所看到的是透视投影的结果。我使用我所有的 MS Paint 技能创建了这张从侧面看的非常精细的情况图:

球体的轮廓是用黑色绘制的。红线表示通过球体中心的平面。

蓝线表示来自图表底部视点的两条视线。如果您在应用投影后描绘结果,则在渲染图像中显示为球体正面的部分是绿线下方的所有内容。在生成的渲染中,绿线上方的球体部分构成了球体的背面部分。

或者换句话说,绿线显示了与生成的渲染中的球体轮廓相对应的平面。

从这里可以看出,通过球体中心的平面确实在球体部分后面有一段距离,该部分在渲染图像中显示为球体的正面部分。这只是透视投影的本质。红色平面和绿色平面之间的距离会随着视角变小(即视角变弱)而减小,使用平行投影时两者相同。