代码之家  ›  专栏  ›  技术社区  ›  Matt

如何在openGL中处理点球体的闪电(环境光、漫反射、镜面反射)

  •  0
  • Matt  · 技术社区  · 8 年前

    初始情况

    我想在openGL中可视化模拟数据。 我的数据由粒子位置(x、y、z)组成,其中每个粒子都有一些属性(如密度、温度等)这将用于着色。如果你想知道的话,这些粒子(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_位置变量上,这真的发生在所有从顶点传递到片段着色器的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. 现在,这实际上“有点”起作用了,但我有一种感觉,那就是球体的侧面没有面对光源,也被照亮了,这是不应该发生的。我怎样才能解决这个问题?

    enter image description here

    一些奇怪的效果:在这一刻,光源实际上位于左侧行星的后面(它只是在左上角有一点峰值),但仍然存在漫反射和镜面反射效果。这面应该相当暗=(

    enter image description here

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

    1 回复  |  直到 8 年前
        1
  •  2
  •   Nico Schertler    8 年前

    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、黑暗中的光明

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

    推荐文章