【问题标题】:How to handle lightning (ambient, diffuse, specular) for point spheres in openGL如何在openGL中处理点球体的闪电(环境、漫反射、镜面反射)
【发布时间】:2018-04-30 01:52:20
【问题描述】:

初始情况

我想在 openGL 中可视化模拟数据。 我的数据由粒子位置(x、y、z)组成,其中每个粒子都有一些属性(如密度、温度……),这些属性将用于着色。这些 (SPH) 粒子(10 万到数百万)组合在一起,实际上代表行星,以防您想知道。我想将这些粒子渲染为小的 3D 球体,并添加环境光、漫反射光和镜面光。

现状与问题

  1. 在我的情况下:我在哪个坐标系中进行闪电计算?哪种方式是通过管道传递各种组件的“最佳”方式?

我看到在视图空间中执行此操作很常见,这也非常直观。但是:不同片段位置的法线是在片段着色器中以剪辑空间坐标计算的(请参阅附加的片段着色器)。我真的可以将它们“返回”到视图空间中,以便在视图空间中为所有片段进行闪电计算吗?与在剪辑空间中做相比有什么优势吗?

  1. 如果我为每个球体使用网格,则在视图空间中获取法线会更容易,但我认为使用数百万个粒子会大大降低性能,所以最好使用球体相交,你同意吗?

PS:我不需要模型矩阵,因为所有粒子都已经到位。

//VERTEX SHADER

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 2) in float density;

uniform float radius;
uniform vec3 lightPos;
uniform vec3 viewPos;

out vec4 lightDir;
out vec4 viewDir;
out vec4 viewPosition;
out vec4 posClip;
out float vertexColor;


// transformation matrices
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    lightDir        = projection * view * vec4(lightPos - position, 1.0f);
    viewDir         = projection * view * vec4(viewPos - position, 1.0f);
    viewPosition    = projection * view * vec4(lightPos, 1.0f);
    posClip         = projection * view * vec4(position, 1.0f);

    gl_Position = posClip;
    gl_PointSize = radius;

    vertexColor = density;

}
  1. 我知道 gl_Position 变量会发生投影除法,这是否真的发生在从顶点传递到片段着色器的所有 vec4 上?如果不是,也许片段着色器中的计算会出错?

以及片段着色器在剪辑空间中计算法线和漫反射/镜面反射闪电:

//FRAGMENT SHADER

#version 330 core

in float vertexColor;
in vec4 lightDir;
in vec4 viewDir;
in vec4 posClip;
in vec4 viewPosition;

uniform vec3 lightColor;

vec4 colormap(float x); // returns vec4(r, g, b, a)

out vec4 vFragColor;


void main(void)
{
    // AMBIENT LIGHT
    float ambientStrength = 0.0;
    vec3 ambient = ambientStrength * lightColor;

    // Normal calculation done in clip space (first from texture (gl_PointCoord 0 to 1) coord to NDC( -1 to 1))
    vec3 normal;
    normal.xy = gl_PointCoord * 2.0 - vec2(1.0);    // transform from 0->1 point primitive coords to NDC -1->1
    float mag = dot(normal.xy, normal.xy);          // sqrt(x=1) = sqrt(x)
    if (mag > 1.0)                                  // discard fragments outside sphere
        discard;            
    normal.z = sqrt(1.0 - mag);                     // because x^2 + y^2 + z^2 = 1

    // DIFFUSE LIGHT
    float diff = max(0.0, dot(vec3(lightDir), normal));
    vec3 diffuse = diff * lightColor;

    // SPECULAR LIGHT
    float specularStrength = 0.1;
    vec3 viewDir = normalize(vec3(viewPosition) - vec3(posClip));
    vec3 reflectDir = reflect(-vec3(lightDir), normal);  
    float shininess = 64;
    float spec = pow(max(dot(vec3(viewDir), vec3(reflectDir)), 0.0), shininess);
    vec3 specular = specularStrength * spec * lightColor;  

     vFragColor = colormap(vertexColor / 8) * vec4(ambient + diffuse + specular, 1);

} 
  1. 现在这实际上“有点”有效,但我感觉球体的不面向光源的侧面也被照亮,这是不应该发生的。我怎样才能解决这个问题?

一些奇怪的效果:此时光源实际上位于左侧行星的后面(它只是在左上角稍微突出一点),仍然存在漫反射和镜面反射效果。这一面其实应该很黑! =(

此时我在片段着色器中得到一些 glError: 1282 错误,我不知道它来自哪里,因为着色器程序实际上编译并运行,有什么建议吗? :)

【问题讨论】:

    标签: c++ opengl rendering specular


    【解决方案1】:

    您所绘制的东西实际上并不是球体。他们只是从远处看就像他们。如果您对此感到满意,那绝对可以。如果您需要几何上正确的球体(具有正确的尺寸和正确的投影),则需要进行适当的光线投射。 This 似乎是关于这个主题的综合指南。

    1。什么坐标系?

    最后,这取决于你。坐标系只需要满足一些要求。它必须保持角度(因为照明都是关于角度的)。如果你需要基于距离的衰减,它也应该是保持距离的。世界和视图坐标系通常满足这些要求。剪辑空间不适合照明计算,因为既不保留角度也不保留距离。此外,gl_PointCoord 不在任何通常的坐标系中。它是它自己的坐标系,如果你知道它们的关系,你应该只将它与其他坐标系一起使用。

    2。网格还是什么?

    网格绝对不适合渲染球体。如上所述,光线投射或一些屏幕空间近似是更好的选择。这是我在项目中使用的示例着色器:

    #version 330
    
    out vec4 result;
    
    in fData
    {
        vec4 toPixel; //fragment coordinate in particle coordinates
        vec4 cam;     //camera position in particle coordinates
        vec4 color;   //sphere color
        float radius; //sphere radius
    } frag;
    
    uniform mat4 p; //projection matrix
    
    void main(void)
    {
        vec3 v = frag.toPixel.xyz - frag.cam.xyz;
        vec3 e = frag.cam.xyz;
        float ev = dot(e, v);
        float vv = dot(v, v);
        float ee = dot(e, e);
        float rr = frag.radius * frag.radius;
    
        float radicand = ev * ev - vv * (ee - rr);
        if(radicand < 0)
            discard;
    
        float rt = sqrt(radicand);
    
    
        float lambda = max(0, (-ev - rt) / vv); //first intersection on the ray
        float lambda2 = (-ev + rt) / vv;  //second intersection on the ray
        if(lambda2 < lambda) //if the first intersection is behind the camera
            discard;
    
        vec3 hit = lambda * v; //intersection point
        vec3 normal = (frag.cam.xyz + hit) / frag.radius;
    
        vec4 proj = p * vec4(hit, 1); //intersection point in clip space
        gl_FragDepth = ((gl_DepthRange.diff * proj.z / proj.w) + gl_DepthRange.near + gl_DepthRange.far) / 2.0;
    
        vec3 vNormalized = -normalize(v);
        float nDotL = dot(vNormalized, normal);
        vec3 c = frag.color.rgb * nDotL + vec3(0.5, 0.5, 0.5) * pow(nDotL, 120);    
    
        result = vec4(c, frag.color.a);
    }
    

    3。透视划分

    透视划分不适用于您的属性。 GPU 对您通过gl_Position 传递的数据进行透视划分,并将它们转换为屏幕空间。但是,除非您自己动手,否则您永远不会真正看到这种视角划分的位置。

    4。黑暗中的光明

    这可能是您混合不同坐标系或在剪辑空间中进行光照计算的结果。顺便说一句,镜面反射部分通常不会乘以材质颜色。这是直接在表面反射的光。它不会穿透表面(根据材料会吸收一些颜色)。这就是为什么这些高光通常是白色的(或任何你有的浅色),即使在黑色物体上也是如此。

    【讨论】:

    • 感谢您的精彩回答!只需 2 个后续问题:1)假设我完全可以接受球体是“假”球体。是否可以使用 gl_FragCoord 去 NDC -> 然后去homog。剪辑坐标->然后返回视图空间(使用逆投影)并在视图空间中计算适当的闪电? 2) 光线投射的表现如何(也与上面的方法相比)?我希望能够在全屏模式下实时渲染时间步长。我建议它是屏幕尺寸而不是限制性能的球体数量(数百万......)?提前致谢
    • 1) 是的,这是可能的(大约)。给你的粒子一个在视图空间中的位置(你可能已经有了)。然后你可以通过将radius*normal添加到这个位置来计算实际的片段位置,其中radius应该在视图空间中(你可以从片段深度近似它)。那么您已经(大约)在视图空间中。无需来回走动。 2) 正如您在示例着色器中看到的,它并不太复杂。所以,我猜它只会稍微慢一点。几百万个粒子应该不是问题(是的,大多数工作都是按像素完成的)。
    猜你喜欢
    • 1970-01-01
    • 2018-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-14
    相关资源
    最近更新 更多