如何处理 openGL 中点球体的闪电(环境光、漫反射、镜面反射)
How to handle lightning (ambient, diffuse, specular) for point spheres in openGL
初始情况
我想在 openGL 中可视化模拟数据。
我的数据由粒子位置 (x, y, z) 组成,其中每个粒子都有一些属性(如密度、温度等),这些属性将用于着色。如果您想知道的话,那些 (SPH) 粒子(10 万到数百万)组合在一起,实际上代表行星。我想将这些粒子渲染为小型 3D 球体并添加环境光、漫反射光和镜面光。
现状与问题
- 以我为例:我在哪个坐标系中进行闪电计算? "best"通过管道传递各种组件的方式是什么?
我在视图space中看到很常见,也很直观。但是:不同片段位置的法线是在剪辑 space 坐标中的片段着色器中计算的(请参阅附加的片段着色器)。我真的可以将它们 "back" 转换为视图 space 以在视图 space 中对所有片段进行闪电计算吗?与剪辑 space 相比有什么优势吗?
- 如果我为每个球体使用网格,那么在视图中获取法线会更容易 space,但我认为使用几百万个粒子会大大降低性能,所以最好使用球体相交来实现,会你同意吗?
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;
}
- 我知道 gl_Position 变量会发生投影划分,这是否真的发生在从顶点传递到片段着色器的所有 vec4 上?如果不是,也许片段着色器中的计算是错误的?
片段着色器,其中法线和 diffuse/specular 剪辑 space 中的闪电计算:
//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);
}
- 现在这实际上 "kind of" 起作用了,但我感觉球体不面向光源的侧面也被照亮了,这不应该发生。我怎样才能解决这个问题?
一些奇怪的效果:此时光源实际上在左侧行星的后面(它只是在左上角达到峰值),仍然存在漫反射和镜面反射效果。这边其实应该挺黑的! =(
同时我在片段着色器中得到一些 glError: 1282 错误,我不知道它来自哪里,因为着色器程序实际编译和运行,有什么建议吗? :)
你画的东西实际上不是球体。他们只是从远处看起来像他们。如果您对此表示满意,那绝对没问题。如果您需要几何正确的球体(具有正确的尺寸和正确的投影),您需要进行适当的光线投射。 This 似乎是关于该主题的综合指南。
1。什么坐标系?
最后,由你决定。坐标系只需要满足一些要求。它必须保持角度(因为照明完全与角度有关)。如果你需要基于距离的衰减,它也应该是距离保持的。世界和视图坐标系通常满足这些要求。剪辑 space 不适合光照计算,因为角度和距离都没有保留。此外,gl_PointCoord
在通常的坐标系中 none。它有自己的坐标系,如果您知道它们之间的关系,您应该只将它与其他坐标系一起使用。
2。网格还是什么?
网格绝对不适合渲染球体。如上所述,光线投射或一些 screen-space 近似是更好的选择。这是我在我的项目中使用的示例着色器:
#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
传递的数据进行透视除法,然后将它们转换为屏幕 space。但是除非你自己动手,否则你永远不会真正看到这种透视分割的位置。
4。黑暗中的光明
这可能是您在剪辑 space 中混合使用不同坐标系或进行光照计算的结果。顺便说一句,镜面反射部分通常不会乘以 material 颜色。这是直接在表面反射的光。它不会穿透表面(根据 material 会吸收一些颜色)。这就是为什么即使在黑色物体上,这些高光通常也是白色(或您拥有的任何浅色)。
初始情况
我想在 openGL 中可视化模拟数据。 我的数据由粒子位置 (x, y, z) 组成,其中每个粒子都有一些属性(如密度、温度等),这些属性将用于着色。如果您想知道的话,那些 (SPH) 粒子(10 万到数百万)组合在一起,实际上代表行星。我想将这些粒子渲染为小型 3D 球体并添加环境光、漫反射光和镜面光。
现状与问题
- 以我为例:我在哪个坐标系中进行闪电计算? "best"通过管道传递各种组件的方式是什么?
我在视图space中看到很常见,也很直观。但是:不同片段位置的法线是在剪辑 space 坐标中的片段着色器中计算的(请参阅附加的片段着色器)。我真的可以将它们 "back" 转换为视图 space 以在视图 space 中对所有片段进行闪电计算吗?与剪辑 space 相比有什么优势吗?
- 如果我为每个球体使用网格,那么在视图中获取法线会更容易 space,但我认为使用几百万个粒子会大大降低性能,所以最好使用球体相交来实现,会你同意吗?
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;
}
- 我知道 gl_Position 变量会发生投影划分,这是否真的发生在从顶点传递到片段着色器的所有 vec4 上?如果不是,也许片段着色器中的计算是错误的?
片段着色器,其中法线和 diffuse/specular 剪辑 space 中的闪电计算:
//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);
}
- 现在这实际上 "kind of" 起作用了,但我感觉球体不面向光源的侧面也被照亮了,这不应该发生。我怎样才能解决这个问题?
一些奇怪的效果:此时光源实际上在左侧行星的后面(它只是在左上角达到峰值),仍然存在漫反射和镜面反射效果。这边其实应该挺黑的! =(
同时我在片段着色器中得到一些 glError: 1282 错误,我不知道它来自哪里,因为着色器程序实际编译和运行,有什么建议吗? :)
你画的东西实际上不是球体。他们只是从远处看起来像他们。如果您对此表示满意,那绝对没问题。如果您需要几何正确的球体(具有正确的尺寸和正确的投影),您需要进行适当的光线投射。 This 似乎是关于该主题的综合指南。
1。什么坐标系?
最后,由你决定。坐标系只需要满足一些要求。它必须保持角度(因为照明完全与角度有关)。如果你需要基于距离的衰减,它也应该是距离保持的。世界和视图坐标系通常满足这些要求。剪辑 space 不适合光照计算,因为角度和距离都没有保留。此外,gl_PointCoord
在通常的坐标系中 none。它有自己的坐标系,如果您知道它们之间的关系,您应该只将它与其他坐标系一起使用。
2。网格还是什么?
网格绝对不适合渲染球体。如上所述,光线投射或一些 screen-space 近似是更好的选择。这是我在我的项目中使用的示例着色器:
#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
传递的数据进行透视除法,然后将它们转换为屏幕 space。但是除非你自己动手,否则你永远不会真正看到这种透视分割的位置。
4。黑暗中的光明
这可能是您在剪辑 space 中混合使用不同坐标系或进行光照计算的结果。顺便说一句,镜面反射部分通常不会乘以 material 颜色。这是直接在表面反射的光。它不会穿透表面(根据 material 会吸收一些颜色)。这就是为什么即使在黑色物体上,这些高光通常也是白色(或您拥有的任何浅色)。