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

时间:2018-09-29 11:50:22

标签: three.js glsl fragment-shader perspectivecamera

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

让我们假设我的相机放在(0, 0, 1000)处,并以

进行透视
  • 视角:45deg
  • 尊重screen_width/screen_height
  • nearZ:1
  • farZ:10000

相机查看(0,0)。在这种情况下,使用three.js,我可以获取projectionMatrixModelViewMatrix的相机(例如PerspectiveCamera.projectionMatrix),并且默认情况下,我可以在{ viewMatrix中的{1}}。

因此,在fragmentShader中,为了计算放置的圆ShaderMaterial的投影坐标,我像下面这样写three.js(300, 300, -1000)

“我的顶点着色器”仅以VertexShaderFragmentShader的身份获得projectionMatrixmodelViewMatrix

P

然后,我仅使用MV// vertexShader varying mat4 P; varying mat4 MV; void main(){ P = projectionMatrix; MV = modelViewMatrix; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } 来计算x和y,如下所示。

P

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

  • 无论MV的制服是什么,都没有任何变化。
  • 像素比率可能是某些原因(例如,视网膜显示器的像素比率为2),但是从我的实验来看,与此无关。

我犯了任何错误吗?还是有误导性? (以某种方式在// 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); } 函数中可能会出错,但我认为这并不是关键问题。)

1 个答案:

答案 0 :(得分:0)

让我们假设xyz定义了世界空间中的圆心。您想要在与屏幕空间通道中的视口平行的平面上绘制一个圆,然后在整个视口上绘制一个四边形。

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

根据您的问题代码,这也可以在顶点着色器中完成。但是,在通过模型视图矩阵和投影矩阵进行转换之后,必须执行Perspective divide,以转换点形式的裁剪空间以查看规范化的设备空间:

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是视口的宽度和高度,则可以通过以下方式计算标准化设备空间中片段的x和y坐标:

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

但是我建议将圆的中心点转换为窗口(像素)坐标,然后也可以以像素为单位设置半径:

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。 计算圆上的一个点,并在归一化设备空间中的顶点着色器中计算该点到圆的中心点的距离。 这是您必须从顶点着色器传递到除圆心之外的片段着色器的半径。

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);
}