gl_PointSize Corresponding to World Space Size

别说谁变了你拦得住时间么 提交于 2019-11-28 09:15:16
jozxyqk

A straight forward idea is to transform a point at the top and bottom of the particle into screen space and find the distance. This cancels very nicely and it's pretty simple to work with just the y coordinate.

The billboard is screen aligned, and view matrices generally don't scale, so the particle size in world space is the same as eye space. That just leaves the projection to get to NDC, the divide by w and scaling by the viewport size.

A typical projection matrix, P, might look something like this...

[ +1.2990 +0.0000 +0.0000 +0.0000 ]
[ +0.0000 +1.7321 +0.0000 +0.0000 ]
[ +0.0000 +0.0000 -1.0002 -0.0020 ]
[ +0.0000 +0.0000 -1.0000 +0.0000 ]

Starting with y_eye, a y coordinate in eye space, the image space coordinate y_image is obtained in pixels...

Plugging in the radius above/below the billboard and subtracting cancels to...

Or, in text, pixelSize = vpHeight * P[1][1] * radius / w_clip

For a perspective projection, P[1][1] = 1 / tan(fov_y / 2). w_clip is gl_Position.w, which is also -z_eye (from the -1 in the perspective matrix). To guarantee your point covers every pixel you want, this may need an additional small constant.


Side note: A sphere on a billboard will look OK in the middle of the screen. If you have a large field of view perspective projection, a true sphere should warp as it approaches the edges of the screen. You could implicitly raycast the virtual sphere for each pixel in the billboard to get a correct result, but the billboard boundary will need to be adjusted accordingly. Quick google results: 1 2 3 4


[EDIT]
Well, since I bothered to test this I'll throw my shaders here too...

Vertex:

#version 150

in vec4 osVert;

uniform mat4 projectionMat;
uniform mat4 modelviewMat;
uniform vec2 vpSize;

flat out vec2 centre;
flat out float radiusPixels;

const float radius = 1.0;

void main()
{
    gl_Position = projectionMat * modelviewMat * osVert;
    centre = (0.5 * gl_Position.xy/gl_Position.w + 0.5) * vpSize;
    gl_PointSize = vpSize.y * projectionMat[1][5] * radius / gl_Position.w;
    radiusPixels = gl_PointSize / 2.0;
}

Fragment:

#version 150

flat in vec2 centre;
flat in float radiusPixels;

out vec4 fragColour;

void main()
{
    vec2 coord = (gl_FragCoord.xy - centre) / radiusPixels;
    float l = length(coord);
    if (l > 1.0)
        discard;
    vec3 pos = vec3(coord, sqrt(1.0-l*l));
    fragColour = vec4(vec3(pos.z), 1.0);
}

(Note the visible gap at the bottom right is incorrect as described above)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!