Threejs: вычислить проекционную координату в фрагментном шейдере - PullRequest
0 голосов
/ 29 сентября 2018

Я борюсь с обработкой Coord во фрагменте Shader.Вкратце, я просто хочу нарисовать круг с фрагментным шейдером, используя (x,y,z) мирового пространства.Но из-за положения камеры и z центрального положения круга я не могу получить действительные проекции координат x и y.

Предположим, что моя камера расположена на (0, 0, 1000) и перспектива с

  • fov: 45deg
  • с screen_width/screen_height
  • nearZ:1
  • farZ: 10000

Камера смотрит на (0,0).В этом случае с three.js я могу получить projectionMatrix и ModelViewMatrix камеры (например, PerspectiveCamera.projectionMatrix), а также по умолчанию я могу использовать viewMatrix во фрагменте ShaderMaterial в three.js.

Итак, для того, чтобы при расчете проекции координаты окружности, помещенной в (300, 300, -1000), во FragmentShader я записал VertexShader и FragmentShader, как показано ниже.

Мой Vertex Shader предназначен только для get 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);
}

А затем я просто вычисляю x и y, используя P и MV, как показано ниже.

// 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, но я думаю, что это не создает критических проблем ..)

1 Ответ

0 голосов
/ 29 сентября 2018

Предположим, что x, y и z определяют центр круга в мировом пространстве.Вы хотите нарисовать круг в плоскости, параллельной порту просмотра в проходе пространства экрана, где вы рисуете квад по всему окну просмотра.

Вам необходимо преобразовать центр круга из мирового пространства.координаты к нормализованным координатам устройства.Лучшим решением было бы сделать это на процессоре и установить единообразный результат.

Согласно коду вашего вопроса, это можно сделать и в вершинном шейдере.Но вам нужно сделать Перспективное деление после преобразования с помощью матрицы вида модели и матрицы проекции, чтобы преобразовать пространство клипа в форме точки для просмотра нормализованного пространства устройства:

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