Threejs:计算片段着色器中的投影坐标
Threejs: compute projected coordinate in fragment shader
我正在努力处理片段着色器中的坐标。
简而言之,我只想使用世界 space 的 (x,y,z)
使用片段着色器绘制圆。但是由于相机位置和圆心位置的 z
,我无法获得实际正确投影的 x
和 y
坐标。
假设我的相机位于 (0, 0, 1000)
并且视角为
- 视角:45deg
screen_width/screen_height
方面
- nearZ: 1
- farZ: 10000
相机注视(0,0)
。在这种情况下,使用 three.js
,我可以获得相机的 projectionMatrix
和 ModelViewMatrix
(例如 PerspectiveCamera.projectionMatrix
),而且在默认情况下,我可以在 [ 的 fragmentShader 中使用 viewMatrix
=24=] 在 three.js
中。
所以在 fragmentShader 中,为了计算放置 (300, 300, -1000)
的圆的投影坐标,我像下面这样写 VertexShader
和 FragmentShader
。
我的顶点着色器仅用于获取 projectionMatrix
和 modelViewMatrix
作为 P
和 MV
.
// vertexShader
varying mat4 P;
varying mat4 MV;
void main(){
P = projectionMatrix;
MV = modelViewMatrix;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
然后,我只使用 P
和 MV
计算 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);
}
但结果与我的预期不符。并且还发现了一些奇怪的行为。
- 不管是什么
z
制服,都没有任何变化。
- 像素比可能是某种原因(例如视网膜显示器的像素比为2),但从我的实验来看,它与此无关。
我犯了什么错误吗?或者有什么误导? (不知何故 circle
函数可能有错误,但我认为它不会造成严重问题..)
让我们假设 x
、y
和 z
,定义世界 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);
}
我正在努力处理片段着色器中的坐标。
简而言之,我只想使用世界 space 的 (x,y,z)
使用片段着色器绘制圆。但是由于相机位置和圆心位置的 z
,我无法获得实际正确投影的 x
和 y
坐标。
假设我的相机位于 (0, 0, 1000)
并且视角为
- 视角:45deg
screen_width/screen_height
方面- nearZ: 1
- farZ: 10000
相机注视(0,0)
。在这种情况下,使用 three.js
,我可以获得相机的 projectionMatrix
和 ModelViewMatrix
(例如 PerspectiveCamera.projectionMatrix
),而且在默认情况下,我可以在 [ 的 fragmentShader 中使用 viewMatrix
=24=] 在 three.js
中。
所以在 fragmentShader 中,为了计算放置 (300, 300, -1000)
的圆的投影坐标,我像下面这样写 VertexShader
和 FragmentShader
。
我的顶点着色器仅用于获取 projectionMatrix
和 modelViewMatrix
作为 P
和 MV
.
// vertexShader
varying mat4 P;
varying mat4 MV;
void main(){
P = projectionMatrix;
MV = modelViewMatrix;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
然后,我只使用 P
和 MV
计算 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);
}
但结果与我的预期不符。并且还发现了一些奇怪的行为。
- 不管是什么
z
制服,都没有任何变化。 - 像素比可能是某种原因(例如视网膜显示器的像素比为2),但从我的实验来看,它与此无关。
我犯了什么错误吗?或者有什么误导? (不知何故 circle
函数可能有错误,但我认为它不会造成严重问题..)
让我们假设 x
、y
和 z
,定义世界 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);
}