【问题标题】:GLSL - computing normals of fragmentsGLSL - 计算片段的法线
【发布时间】:2011-05-23 09:03:02
【问题描述】:

我正在尝试创建一个程序来显示带有地形的地球,并在 GPU 上执行所有纬度/经度/Alt 到 XYZ (ECEF)。

我已经编写了一个有效的顶点着色器,它将表示经度、纬度和高度(分别)的 XYZ 转换为正确的 XYZ(使用 ECEF)。

我做不到的是灯光。

只要没有地形数据,我就可以使用定向灯正确地照亮每个顶点。地形数据未正确点亮,我无法获得不同的坡度以获得正确的阴影。

这是我正在使用的顶点着色器:

const float a = 6378137.0;
const float f = 0.003352810664747480719845528618;

varying vec3 Normal;
varying vec3 ecPos;

vec3 LatLonAltToEcef(in vec3 latLonAlt)
{
    vec3 v = latLonAlt;

    float cosLat = cos(radians(v.y));
    float sinLat = sin(radians(v.y));
    float nfs = (1.0 - f) * (1.0 - f);

    float C = 1.0 / (sqrt(cosLat * cosLat + nfs * sinLat * sinLat));
    float S = nfs * C;

    float lon = radians(v.x);
    float h = v.z;

    v.x = (a * C + h) * cosLat * cos(lon) / a;
    v.y = (a * C + h) * cosLat * sin(lon) / a;
    v.z = (a * S + h) * sinLat / a;

    return v;
}

vec4 LatLonAltToEcef(in vec4 latLonAlt)
{
    vec3 ecef = LatLonAltToEcef(latLonAlt.xyz);
    return vec4(ecef.x, ecef.y, ecef.z, latLonAlt.w);
}

void main(void)
{
    vec4 v = LatLonAltToEcef(gl_Vertex); //x=lon, y=lat, z=alt
    ecPos = vec3(gl_ModelViewMatrix * v);

    Normal = normalize(gl_NormalMatrix * v.xyz);
    vec4 lightPos = LatLonAltToEcef(gl_LightSource[0].position);
    vec3 lightDir = normalize(gl_NormalMatrix * lightPos.xyz);
    float NdotL = max(dot(Normal, lightDir), 0.0);
    vec4 diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;

    gl_FrontColor =  NdotL * diffuse;
    gl_Position = gl_ModelViewProjectionMatrix * v;
}

为了绘制地球网格,需要这样做:

for (float lat = -90; lat < 90; lat += 5)
{
    glBegin(GL_LINE_LOOP);
    for (float lon = -180; lon < 180; lon += 5)
        glVertex3f(lon, lat, 0);
    glEnd();
}

for (float lon = -180; lat < 180; lon += 5)
{
    glBegin(GL_LINE_LOOP);
    for (float lat = -90; lon < 90; lon += 5)
        glVertex3f(lon, lat, 0);
    glEnd();
}

谁能指导我如何正确地为地形着色?

【问题讨论】:

    标签: opengl glsl shader terrain


    【解决方案1】:

    我觉得你需要学习更多的计算机图形学,因为你对计算机渲染几何的基础知识似乎有些差距。

    无论如何:

    如果您从一开始就没有坡度信息,那么在任何点获取法线(表面方向)通常都需要查看周围环境。

    其次,您可能想要绘制三角形,而不是线条。

    三角形有一个曲面。曲面有法线。法线是向量,可以与其他向量成角度。光的方向是一个向量。点积是你的朋友。 :)

    所以,用一组三角形连接你的顶点将帮助你到达某个地方。

    另外,避免在着色器中计算可以在任何渲染之前一劳永逸地计算的东西。

    【讨论】:

    • +1 表示“避免在着色器中计算可以在任何渲染之前一劳永逸地计算的东西”
    • 我知道。这些线条只是我编写的着色器使用的一个示例。我在它上面有另一个模型,它表示使用在网格中对齐的三角形构建的地形(简单的高度图模型)。该模型没有正确点亮,因此我的问题是 - 因为我没有找到一种方法来计算着色器中每个片段或像素的正确法线......
    • “避免在着色器中计算可以在任何渲染之前一劳永逸地计算的东西” - 我计划动态改变地形(替换瓷砖)。使用显示列表算不算重新计算?
    • @Itai:显示列表还可以,但由于固有的低效率,不再推荐使用。但是在其中存储“仅几何”是可以的(尽量避免做任何其他事情)。大多数现代 OpenGL 代码都使用顶点缓冲区对象。此外,对于平铺,有很多很好的文章描述了如何在书籍和互联网上进行有效的平铺细节级别地形。
    • @Itai: W.r.t.你的第一条评论。您应该在渲染之前预先计算每个顶点的法线。然后,您可以在着色器中直接使用该法线进行逐顶点光照,或者通过在三角形上插值间接使用该法线进行逐片段光照。为了获得更好的照明,您可能希望使用法线贴图,但这是高级图形。 :) 为了查看您的法线是否正确,请学习将 RGB 解释为 XYZ 和/或制作一个小例程,将模型中的法线绘制为线条。 (因此,在将它们用于照明之前,请预先计算您的法线和真实性)
    【解决方案2】:

    你的问题在这里:

    Normal = normalize(gl_NormalMatrix * v.xyz);
    

    幸运的是,您发现了一种罕见的情况,即使用顶点的位置也会为您提供法线:球体(但由于地球不是球体,因此您的照明不是 100% 正确)

    当你添加山脉时,这不再是真的了。法线垂直于地面,所以在山上,它是弯曲的。

    你需要这样的一行:

    Normal = normalize(gl_NormalMatrix * gl_Normal.xyz);
    

    gl_Normal 由调用 glNormal3f 给出,就在 glVertex3f 之前。但是您没有说明如何提供地形数据,因此您必须自己找到 glNormal3f 的参数。它们必须在模型空间中表示(即不是 lon/lat,而是相对于地球中心的 XYZ,在 笛卡尔 空间中。同样:笛卡尔空间。)

    【讨论】:

    • 我明白了。然而,我想要实现的是避免在代码中将 Lat/Long 转换为 XYZ。假设我将我的几何图形提供为三角形带(或三角形,就此而言) - 着色器将每个顶点从 Lat/Long/Alt 转换为 XYZ。我知道(也许是错误的)OpenGL 会自动计算三角形的法线(如果我错了,请纠正我)。如何在着色器中给出每个三角形的顶点,以便计算其法线,以及如何将计算的法线传递给计算照明?
    • @Itai:OpenGL 不会自动执行任何操作。法线只是您必须提供的另一个顶点属性。此外,您应该将 Lat/Lon 预先计算到 XYZ 中:在着色器中执行它会强制 GPU 为每一帧一次又一次地执行任务,而这本来可以一劳永逸地完成。可以使用片段着色器使用偏导数函数opengl.org/sdk/docs/manglsl/xhtml/dFdx.xml 以非常复杂的方式计算法线,但是离线计算一次更加容易和简单。
    • "如何在着色器中给出每个三角形的顶点" -> 在几何着色器中;但这有点矫枉过正恕我直言。我真的建议您学习如何传递法线,然后,如果性能有问题,请考虑即时计算它们。
    • 转换法线(在着色器中)比从顶点位置导出法线要容易得多。
    • @datenwolf :嗯,是的,我的意思差不多就是这个意思,抱歉我不清楚...
    猜你喜欢
    • 1970-01-01
    • 2021-12-19
    • 2015-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-08
    相关资源
    最近更新 更多