Threejs:计算片段着色器中的投影坐标

Threejs: compute projected coordinate in fragment shader

我正在努力处理片段着色器中的坐标。 简而言之,我只想使用世界 space 的 (x,y,z) 使用片段着色器绘制圆。但是由于相机位置和圆心位置的 z,我无法获得实际正确投影的 xy 坐标。

假设我的相机位于 (0, 0, 1000) 并且视角为

相机注视(0,0)。在这种情况下,使用 three.js,我可以获得相机的 projectionMatrixModelViewMatrix(例如 PerspectiveCamera.projectionMatrix),而且在默认情况下,我可以在 [ 的 fragmentShader 中使用 viewMatrix =24=] 在 three.js 中。

所以在 fragmentShader 中,为了计算放置 (300, 300, -1000) 的圆的投影坐标,我像下面这样写 VertexShaderFragmentShader

我的顶点着色器仅用于获取 projectionMatrixmodelViewMatrix 作为 PMV.

// vertexShader
varying mat4 P;
varying mat4 MV;
void main(){
    P = projectionMatrix;
    MV = modelViewMatrix;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

然后,我只使用 PMV 计算 x 和 y,如下所示。

// fragmentShader
varying mat4 P;
varying mat4 MV;
uniform float x;
uniform float y;
uniform float z;
uniform float r;
uniform vec2 u_resolution;

float circle(vec2 _st, vec2 _center, float _radius){
    vec2 dist = _st - _center + u_resolution;
    return 1.-smoothstep(_radius-(_radius*0.01),
                     _radius+(_radius*0.01),
                     length(dist));
}

void main(){
    vec2 coord = (P * MV * vec4(x, y, z, 1.0)).xy;
    float point = circle(gl_FragCoord.xy, coord, r); // ignore r scaling.
    gl_FragColor = vec4(vec4(point), point);
}

但结果与我的预期不符。并且还发现了一些奇怪的行为。

我犯了什么错误吗?或者有什么误导? (不知何故 circle 函数可能有错误,但我认为它不会造成严重问题..)

让我们假设 xyz,定义世界 space 中的圆心。您想在屏幕 space 通道中平行于视口的平面上画一个圆,在整个视口上画一个四边形。

您必须将圆心从世界 space 坐标转换为规范化设备坐标。最好的解决方案是在 CPU 上执行此操作并使用结果设置统一。

根据你问题的代码,这也可以在顶点着色器中完成。但是你必须做一个Perspective divide,在模型视图矩阵和投影矩阵变换之后,将点形式变换剪辑space以查看归一化设备space:

uniform mat4 P;
uniform mat4 MV;
uniform float x;
uniform float y;
uniform float z;

varying vec3 cpt; 

void main(){
    vec4 cpt_h  =  projectionMatrix * modelViewMatrix * vec4(x, y, z, 1.0);
    vec3 cpt    =  cpt_h.xyz / cpt_h.w;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

如果u_resolution是视口的宽度和高度,则片段在标准化设备space中的x和y坐标可以通过以下方式计算:

vec2 coord = gl_FragCoord.xy / u_resolution.xy * 2.0 - 1.0;

但我建议将圆心转换成window(像素)坐标,那么半径也可以设置为像素:

vec2 cpt_p = (cpt.xy * 0.5 + 0.5) * u_resolution.xy;

要计算向量的长度,您可以使用 GLSL 函数 length

最终片段着色器可能如下所示:

varying vec3 cpt; 

uniform vec2 u_resolution;

uniform float u_pixel_ratio; // device pixel ratio

uniform float r; // e.g. 100.0 means a radius of 100 pixel

float circle( vec2 _st, vec2 _center, float _radius )
{
    // thickness of the circle in pixel
    const float thickness = 20.0;

    // distance to the center  point in pixel
    float dist = length(_st - _center);

    return 1.0 - smoothstep(0.0, thickness/2.0, abs(_radius-dist));
}

void main(){
    vec2  cpt_p  = (cpt.xy * 0.5 + 0.5) * u_resolution.xy * u_pixel_ratio;
    float point  = circle(gl_FragCoord.xy, cpt_p, r);
    gl_FragColor = vec4(point);
}  

例如半径为 50.0,厚度为 20.0 的圆:


如果你想对圆应用透视变形,这意味着圆的大小随着距离的增加而减小,那么你必须在世界坐标中设置半径r。 在normalized device的vertex shader中计算圆上的一个点,计算该点到圆心的距离space。 这是您必须从顶点着色器传递到圆心之外的片段着色器的半径。

uniform mat4 P;
uniform mat4 MV;
uniform float x;
uniform float y;
uniform float z;
uniform float r; // e.g. radius in world space

varying vec3  cpt;
varying float radius;

void main(){
    vec4 cpt_v  = modelViewMatrix * vec4(x, y, z, 1.0);
    vec4 rpt_v  = vec4(cpt_v.x, cpt_v.y + r, cpt_v.zw);

    vec4 cpt_h  = projectionMatrix * cpt_v;
    vec4 rpt_h  = projectionMatrix * rpt_v;

    cpt         =  cpt_h.xyz / cpt_h.w;
    vec3 rpt    =  rpt_v.xyz / rpt_v.w;
    radius      =  length(rpt-cpt);

    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

varying vec3  cpt;
varying float radius;

uniform vec2 u_resolution;
uniform float u_pixel_ratio; // device pixel ratio

uniform float r; // e.g. 100.0 means a radius of 100 pixel

float circle( vec2 _st, vec2 _center, float _radius )
{
    const float thickness = 20.0;
    float dist = length(_st - _center);
    return 1.0 - smoothstep(0.0, thickness/2.0, abs(_radius-dist));
}

void main()
{
    vec2  cpt_p    = (cpt.xy * 0.5 + 0.5) * u_resolution.xy * u_pixel_ratio;
    float radius_p = radius * 0.5 * u_resolution.y * u_pixel_ratio.y;

    float point  = circle(gl_FragCoord.xy, cpt_p, radius_p);
    gl_FragColor = vec4(point);
}