Можно ли express "t" переменная из уравнения Куба c Кривая Безье? - PullRequest
3 голосов
/ 05 февраля 2020

Я хочу нарисовать кривую Безье только фрагментным шейдером, чтобы соединить узлы в моем редакторе. Я знаю все 4 точки, которые определяют кривую Безье. И Fragment Shader вызывается для каждого пикселя, так что я могу просто проверить: если «t» для gl_Coord.x находится между 0 и 1, тогда установите для frag_color значение Red, например. Я хочу избежать шейдеров в шейдерах, которые неэффективны. Лучший способ, я думаю, это проверить точки, которые лежат на кривой. Но как это сделать для Кривых Безье?

Возможно ли express "t" переменная из кубического c уравнения Безье?

x = ((1-t)^3 * p0.x) + (3 * (1-t)^2 * t * p1.x) + (3 * (1 - t) * t^2 * p2.x) + (t^3 * p3.x);

t = ?

Сайт Wolfram Aplha дает мне эту формулу (в функции GetBezierT). Но формула дает мне неправильные значения "t", и вместо кривой у меня есть половина параболы:

#version 150
.....
layout (origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord;
out vec4 frag_color;
.....
vec4 BackgroundColor = vec4(0.15, 0.15, 0.15, 1.0);
vec2 p0 = vec2(61.0f,87.0f);
vec2 p1 = vec2(181.0f, 39.0f);
vec2 p2 = vec2(283.0f, 178.0f);
vec2 p3 = vec2(416.0f, 132.0f);

float getBezierT(float x, float a, float b, float c, float d)
{
      return  float(sqrt(3) * 
          sqrt(-4 * b * d + 4 * b * x + 3 * c * c + 2 * c * d - 8 * c * x - d * d + 4 * d * x) 
            + 6 * b - 9 * c + 3 * d) 
            / (6 * (b - 2 * c + d));
}

void main() {  
    .....
    frag_color = BackgroundColor; 
    .....
    float tx = getBezierT(gl_FragCoord.x, p0.x, p1.x, p2.x, p3.x);
    float ty = getBezierT(gl_FragCoord.y, p0.y, p1.y, p2.y, p3.y);

    if (tx >= 0.0f && tx <= 1.0f && ty >= 0.0f && ty <= 1.0f)
    {
        if(abs(tx-ty) <  0.01f) // simple check is that one point with little bias
        frag_color = vec4(1.0f, 0.0f, 0.0f, 1.0f);
    }
}

ОБНОВЛЕНИЕ

Ошибка. Я думал, что нет смысла искать t. Я думал, что смирюсь с этим. Но после ответа, данного Salix alba и Stratubas, я понял, что если tX равно tY, это означает, что эта точка будет l ie на кривой, потому что в формуле для каждой точки один значение t заменяется на x и y. Возможно, есть случаи, когда разные tX и tY также могут дать точку на этой кривой, но мы можем просто игнорировать это. Алгоритм построения кривой Безье подразумевает, что мы линейно увеличиваем t и подставляем его в формулу, и не имеет значения, насколько искривлена ​​кривая, алгоритм возвращает координаты каждой следующей точки последовательно вдоль кривой.

Поэтому, прежде всего, я снова открываю этот вопрос: как express переменная t из уравнения Куби c Безье?

Пытался до express t, но это безумно сложно для меня. Необходимо оценить эффективность этого подхода для «научных целей» (1063 *) =). Прежде чем задать вопрос, я много искал, но так и не нашел, что кто-то попытается использовать этот метод. Мне нужно понять, почему.

ОБНОВЛЕНИЕ 2

Вы проделали отличную работу! Я не ожидал получить такие подробные ответы. Именно то, что мне было нужно. Дайте мне время проверить все =)

ОБНОВЛЕНИЕ 3

Выводы: Точное выражение t из уравнения Куби c Безье. Трудоемкая задача, но приблизительные значения не имеют практического применения. Чтобы решить эту проблему, необходимо проанализировать данные уравнения, найти закономерности и разработать новую формулу для построения кривых Безье. С новыми отношениями переменных между собой, тогда станет возможным express t по-другому. Если представить формулу Куби c Безье в виде суммы произведений x координат контрольных точек на четыре коэффициента (v0 - v3), сгенерированных функциями в четырех частях уравнение в зависимости от значения t. Это дает формулу x = ax * v0 + bx * v1 + c .x * v2 + dx * v3. И если вы посмотрите на таблицу ниже, вы можете понять, что выражение для переменной t является уравнением с четырьмя неизвестными. Потому что и значения, и отношения некоторых коэффициентов V между собой изменяются непредсказуемым образом от итерации к итерации. Нахождение этой новой абстрактной формулы выходит за рамки этого вопроса и моей компетенции.

Table of V0-V3 Coefficients

Большое спасибо всем за вашу работу, особенно Spektre за уникальные разработки и усилия по оптимизации алгоритма рендеринга. Ваш подход - лучший выбор для меня =)

Ответы [ 3 ]

4 голосов
/ 05 февраля 2020

См. Эту хитрую кривую Безье:

tricky bezier curve

Нет единого решения для t, есть (до) 3 решений.

(edit1: как указано в ответе Salix alba, это не значит, что вы не можете их найти. Когда вы думали, что был только один tx и один ty, вы проверили, они (почти) равны. Переходя к 3 решениям, вы можете найти tx и ty и проверить, существует ли (почти) общее реальное значение, но я думаю, что этого должно быть достаточно ( и быстрее ), чтобы проверить, является ли bezierY(tx) (почти) равным glFragCoord.y для любого tx, без вычисления ty. Также, поскольку tx одинаковы для каждого пикселя, который имеет то же x, посмотрите, можете ли вы рассчитать их только один раз для каждого уникального x. )

Я не много работал с кривыми Безье и никогда с glsl, так что вот Идея, которая может быть плохой:

Каждый раз, когда ваши контрольные точки меняются, выполните t l oop, чтобы сгенерировать список {x,y} точек, и, возможно, руда их в какой-то неупорядоченной карте. Затем, в вашем шейдере, для каждого пикселя, если этот пиксель существует на этой карте, примените нужный эффект.

Вы также можете добавить близлежащие точки и сохранить расстояние от кривой в качестве значения на карте, так что вы можете сделать какое-то сглаживание, если хотите.

Размер шага в t l oop должен быть достаточно маленьким, чтобы не пропустить ни одной точки, но большим достаточно, чтобы было быстро. Вы можете реализовать динамический шаг c t, проверив, насколько близка следующая точка к предыдущей точке. Если это слишком близко, увеличьте шаг. Если это слишком далеко, уменьшите шаг.

Вы также можете попробовать использовать 2d массив вместо карты, что-то вроде 512x512 логических значений. Инициализируйте каждый элемент с помощью false и измените значения на true для точек, сгенерированных в вашем t l oop. Между тем, сохраните список индексов массива, которые в настоящее время true, так что вы можете инициализировать 2d массив только один раз, и когда ваша кривая изменится, переверните каждые true обратно на false, очистите список индексов и повторите t l oop et c.


(edit2, после вашего обновления)

вместо поиска "как express переменная t из cubi c уравнение Безье ", вы можете искать" решение уравнения cubi c "вообще. Если я не ошибаюсь, уравнения Безье (x или y) можно записать в виде

(-a + 3b - 3c + d) t^3 + (3a - 6b + 3c) t^2 + (-3a + 3b) t + (a - x) = 0

, где a, b, c и d - x (или y) компоненты контрольных точек, а x - это x (или y) компонент кривой, поэтому они являются просто кубическими c уравнениями. Обратите внимание, что x появляется только в последнем коэффициенте, что может упростить ситуацию, когда вам нужно решить множество из них, и их единственное отличие - это значение x.

Должны быть более простые решения, но если у вас есть доступ к комплексной арифметике c (или вы хотите написать ее самостоятельно, используя vec2, см. Ответ Спектре «Как вычислить дискретное число Фурье» Преобразование ") , вы можете попробовать эти 3 решения для t Я получил от Mathematica (I - мнимая единица):

(-2*(a - 2*b + c) + (2*2^(1/3)*(b^2 + c^2 + a*(-c + d) - b*(c + d)))/(-2*b^3 + 3*a*b*c + 3*b^2*c - 6*a*c^2 + 3*b*c^2 - 2*c^3 - a^2*d + 3*a*b*d - 6*b^2*d + 3*a*c*d + 3*b*c*d - a*d^2 + a^2*x - 6*a*b*x + 9*b^2*x + 6*a*c*x - 18*b*c*x + 9*c^2*x - 2*a*d*x + 6*b*d*x - 6*c*d*x + d^2*x + Sqrt[(a - 3*b + 3*c - d)^2*(4*b^3*(d - x) + a^2*(d - x)^2 + x*(-4*c^3 + 9*c^2*x - 6*c*d*x + d^2*x) - 3*b^2*(c^2 - 2*c*x + (4*d - 3*x)*x) + 2*a*(2*c^3 - 6*c^2*x + 3*c*x*(d + x) - d*x*(d + x)) + 6*b*(a*(c - x)*(-d + x) + x*(c^2 + c*d - 3*c*x + d*x)))])^(1/3) + 2^(2/3)*(-2*b^3 + 3*a*b*c + 3*b^2*c - 6*a*c^2 + 3*b*c^2 - 2*c^3 - a^2*d + 3*a*b*d - 6*b^2*d + 3*a*c*d + 3*b*c*d - a*d^2 + a^2*x - 6*a*b*x + 9*b^2*x + 6*a*c*x - 18*b*c*x + 9*c^2*x - 2*a*d*x + 6*b*d*x - 6*c*d*x + d^2*x + Sqrt[(a - 3*b + 3*c - d)^2*(4*b^3*(d - x) + a^2*(d - x)^2 + x*(-4*c^3 + 9*c^2*x - 6*c*d*x + d^2*x) - 3*b^2*(c^2 - 2*c*x + (4*d - 3*x)*x) + 2*a*(2*c^3 - 6*c^2*x + 3*c*x*(d + x) - d*x*(d + x)) + 6*b*(a*(c - x)*(-d + x) + x*(c^2 + c*d - 3*c*x + d*x)))])^(1/3))/(2*(-a + 3*b - 3*c + d))
(-36*(a - 2*b + c) - ((18*I)*2^(1/3)*(-I + Sqrt[3])*(b^2 + c^2 + a*(-c + d) - b*(c + d)))/(-2*b^3 + 3*a*b*c + 3*b^2*c - 6*a*c^2 + 3*b*c^2 - 2*c^3 - a^2*d + 3*a*b*d - 6*b^2*d + 3*a*c*d + 3*b*c*d - a*d^2 + a^2*x - 6*a*b*x + 9*b^2*x + 6*a*c*x - 18*b*c*x + 9*c^2*x - 2*a*d*x + 6*b*d*x - 6*c*d*x + d^2*x + Sqrt[(a - 3*b + 3*c - d)^2*(4*b^3*(d - x) + a^2*(d - x)^2 + x*(-4*c^3 + 9*c^2*x - 6*c*d*x + d^2*x) - 3*b^2*(c^2 - 2*c*x + (4*d - 3*x)*x) + 2*a*(2*c^3 - 6*c^2*x + 3*c*x*(d + x) - d*x*(d + x)) + 6*b*(a*(c - x)*(-d + x) + x*(c^2 + c*d - 3*c*x + d*x)))])^(1/3) + (9*I)*2^(2/3)*(I + Sqrt[3])*(-2*b^3 + 3*a*b*c + 3*b^2*c - 6*a*c^2 + 3*b*c^2 - 2*c^3 - a^2*d + 3*a*b*d - 6*b^2*d + 3*a*c*d + 3*b*c*d - a*d^2 + a^2*x - 6*a*b*x + 9*b^2*x + 6*a*c*x - 18*b*c*x + 9*c^2*x - 2*a*d*x + 6*b*d*x - 6*c*d*x + d^2*x + Sqrt[(a - 3*b + 3*c - d)^2*(4*b^3*(d - x) + a^2*(d - x)^2 + x*(-4*c^3 + 9*c^2*x - 6*c*d*x + d^2*x) - 3*b^2*(c^2 - 2*c*x + (4*d - 3*x)*x) + 2*a*(2*c^3 - 6*c^2*x + 3*c*x*(d + x) - d*x*(d + x)) + 6*b*(a*(c - x)*(-d + x) + x*(c^2 + c*d - 3*c*x + d*x)))])^(1/3))/(36*(-a + 3*b - 3*c + d))
(-36*(a - 2*b + c) + ((18*I)*2^(1/3)*(I + Sqrt[3])*(b^2 + c^2 + a*(-c + d) - b*(c + d)))/(-2*b^3 + 3*a*b*c + 3*b^2*c - 6*a*c^2 + 3*b*c^2 - 2*c^3 - a^2*d + 3*a*b*d - 6*b^2*d + 3*a*c*d + 3*b*c*d - a*d^2 + a^2*x - 6*a*b*x + 9*b^2*x + 6*a*c*x - 18*b*c*x + 9*c^2*x - 2*a*d*x + 6*b*d*x - 6*c*d*x + d^2*x + Sqrt[(a - 3*b + 3*c - d)^2*(4*b^3*(d - x) + a^2*(d - x)^2 + x*(-4*c^3 + 9*c^2*x - 6*c*d*x + d^2*x) - 3*b^2*(c^2 - 2*c*x + (4*d - 3*x)*x) + 2*a*(2*c^3 - 6*c^2*x + 3*c*x*(d + x) - d*x*(d + x)) + 6*b*(a*(c - x)*(-d + x) + x*(c^2 + c*d - 3*c*x + d*x)))])^(1/3) - 9*2^(2/3)*(1 + I*Sqrt[3])*(-2*b^3 + 3*a*b*c + 3*b^2*c - 6*a*c^2 + 3*b*c^2 - 2*c^3 - a^2*d + 3*a*b*d - 6*b^2*d + 3*a*c*d + 3*b*c*d - a*d^2 + a^2*x - 6*a*b*x + 9*b^2*x + 6*a*c*x - 18*b*c*x + 9*c^2*x - 2*a*d*x + 6*b*d*x - 6*c*d*x + d^2*x + Sqrt[(a - 3*b + 3*c - d)^2*(4*b^3*(d - x) + a^2*(d - x)^2 + x*(-4*c^3 + 9*c^2*x - 6*c*d*x + d^2*x) - 3*b^2*(c^2 - 2*c*x + (4*d - 3*x)*x) + 2*a*(2*c^3 - 6*c^2*x + 3*c*x*(d + x) - d*x*(d + x)) + 6*b*(a*(c - x)*(-d + x) + x*(c^2 + c*d - 3*c*x + d*x)))])^(1/3))/(36*(-a + 3*b - 3*c + d))

Они они большие, но они содержат много общих подвыражений (например, (a - 2*b + c)), которые вы можете оценить один раз и использовать повторно, чтобы повысить производительность (если все это работает вообще).

Для хитрого Безье, который я опубликовал, Вот 3 решения:

red = (6 + (4*2^(1/3))/(-9 + 49*x + 7*Sqrt[1 + x*(-18 + 49*x)])^(1/3) + 2^(2/3)*(-9 + 49*x + 7*Sqrt[1 + x*(-18 + 49*x)])^(1/3))/14
green = (12 - ((4*I)*2^(1/3)*(-I + Sqrt[3]))/(-9 + 49*x + 7*Sqrt[1 - 18*x + 49*x^2])^(1/3) + I*2^(2/3)*(I + Sqrt[3])*(-9 + 49*x + 7*Sqrt[1 - 18*x + 49*x^2])^(1/3))/28
blue = (12 + ((4*I)*2^(1/3)*(I + Sqrt[3]))/(-9 + 49*x + 7*Sqrt[1 - 18*x + 49*x^2])^(1/3) - 2^(2/3)*(1 + I*Sqrt[3])*(-9 + 49*x + 7*Sqrt[1 - 18*x + 49*x^2])^(1/3))/28

t-vs-x for the tricky bezier


(edit3) Следуя предложению Spektre, используя коэффициенты куби c напрямую

x = a*t^3 + b*t^2 + c*t + d

(вместо использования координат контрольных точек) дает более чистые выражения:

1st(red) = (-2*b + (2*2^(1/3)*(b^2 - 3*a*c))/(-2*b^3 + 9*a*b*c - 27*a^2*d + Sqrt[-4*(b^2 - 3*a*c)^3 + (2*b^3 - 9*a*b*c + 27*a^2*(d - x))^2] + 27*a^2*x)^(1/3) + 2^(2/3)*(-2*b^3 + 9*a*b*c - 27*a^2*d + Sqrt[-4*(b^2 - 3*a*c)^3 + (2*b^3 - 9*a*b*c + 27*a^2*(d - x))^2] + 27*a^2*x)^(1/3))/(6*a)
2nd(green) = (-4*b + (2*2^(1/3)*(1 + I*Sqrt[3])*(-b^2 + 3*a*c))/(-2*b^3 + 9*a*b*c - 27*a^2*d + Sqrt[-4*(b^2 - 3*a*c)^3 + (2*b^3 - 9*a*b*c + 27*a^2*(d - x))^2] + 27*a^2*x)^(1/3) + I*2^(2/3)*(I + Sqrt[3])*(-2*b^3 + 9*a*b*c - 27*a^2*d + Sqrt[-4*(b^2 - 3*a*c)^3 + (2*b^3 - 9*a*b*c + 27*a^2*(d - x))^2] + 27*a^2*x)^(1/3))/(12*a)
3rd(blue) = -(4*b - ((2*I)*2^(1/3)*(I + Sqrt[3])*(b^2 - 3*a*c))/(-2*b^3 + 9*a*b*c - 27*a^2*d + Sqrt[-4*(b^2 - 3*a*c)^3 + (2*b^3 - 9*a*b*c + 27*a^2*(d - x))^2] + 27*a^2*x)^(1/3) + 2^(2/3)*(1 + I*Sqrt[3])*(-2*b^3 + 9*a*b*c - 27*a^2*d + Sqrt[-4*(b^2 - 3*a*c)^3 + (2*b^3 - 9*a*b*c + 27*a^2*(d - x))^2] + 27*a^2*x)^(1/3))/(12*a)

Вы можете легко преобразовать координаты контрольной точки в следующие координаты:

direct a = control (-a + 3 b - 3 c + d)
direct b = control (3 a - 6 b + 3 c)
direct c = control (-3 a + 3 b)
direct d = control a
3 голосов
/ 07 февраля 2020

То, что вам нужно, это найти ваш кубический c путь и запомнить ближайшую точку. Это может быть сделано рекурсивно с увеличением точности здесь маленький C ++ GL пример:

//---------------------------------------------------------------------------
double pnt[]=                   // cubic curve control points
    {
    -0.9,-0.8,0.0,
    -0.6,+0.8,0.0,
    +0.6,+0.8,0.0,
    +0.9,-0.8,0.0,
    };
const int pnts3=sizeof(pnt)/sizeof(pnt[0]);
const int pnts=pnts3/3;
//---------------------------------------------------------------------------
double cubic_a[4][3];           // cubic coefficients
void cubic_init(double *pnt)    // compute cubic coefficients
    {
    int i;
    double *p0=pnt,*p1=p0+3,*p2=p1+3,*p3=p2+3;
    for (i=0;i<3;i++)           // cubic BEZIER coefficients
        {
        cubic_a[0][i]=                                    (    p0[i]);
        cubic_a[1][i]=                        (3.0*p1[i])-(3.0*p0[i]);
        cubic_a[2][i]=            (3.0*p2[i])-(6.0*p1[i])+(3.0*p0[i]);
        cubic_a[3][i]=(    p3[i])-(3.0*p2[i])+(3.0*p1[i])-(    p0[i]);
        }
    }
//---------------------------------------------------------------------------
double* cubic(double t)         // return point on cubic from parameter
    {
    int i;
    static double p[3];
    double tt=t*t,ttt=tt*t;
    for (i=0;i<3;i++)
     p[i]=cubic_a[0][i]
        +(cubic_a[1][i]*t)
        +(cubic_a[2][i]*tt)
        +(cubic_a[3][i]*ttt);
    return p;
    }
//---------------------------------------------------------------------------
double cubic_d(double *p)       // return closest distance from point to cubic
    {
    int i,j;
    double t,tt,t0,t1,dt,
           l,ll,a,*q;
    tt=-1.0; ll=-1.0; t0=0.0; t1=1.001; dt=0.05;
    for (j=0;j<3;j++)
        {
        for (t=t0;t<=t1;t+=dt)
            {
            q=cubic(t);
            for (l=0.0,i=0;i<3;i++) l+=(p[i]-q[i])*(p[i]-q[i]);
            if ((ll<0.0)||(ll>l)){ ll=l; tt=t; }
            }
        t0=tt-dt; if (t0<0.0) t0=0.0;
        t1=tt+dt; if (t1>1.0) t1=1.0;
        dt*=0.2;
        }
    return sqrt(ll);
    }
//---------------------------------------------------------------------------
void gl_draw()
    {
    int i;
    double t,p[3],dp;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_CULL_FACE);

    // GL render
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);

                    glColor3f(0.2,0.2,0.2); glBegin(GL_LINE_STRIP); for (i=0;i<pnts3;i+=3) glVertex3dv(pnt+i); glEnd();
    glPointSize(5); glColor3f(0.0,0.0,0.7); glBegin(GL_POINTS); for (i=0;i<pnts3;i+=3) glVertex3dv(pnt+i); glEnd(); glPointSize(1);
    cubic_init(pnt);glColor3f(0.2,0.7,0.7); glBegin(GL_LINE_STRIP); for (t=0.0;t<1.001;t+=0.025) glVertex3dv(cubic(t)); glEnd();

    glColor3f(0.0,0.7,0.0); glBegin(GL_POINTS);
    p[2]=0.0; dp=0.01;
    for (p[0]=-1.0;p[0]<1.001;p[0]+=dp)
     for (p[1]=-1.0;p[1]<1.001;p[1]+=dp)
      if (cubic_d(p)<0.05)
       glVertex3dv(p);
    glEnd();

    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------

, поэтому сначала вы вызываете cubic_init один раз, чтобы вычислить коэффициенты, а затем получить точку на кривой как функцию использования параметра:

double pnt[3] = cubic(double t);

Теперь наоборот (я возвращаю ближайшее расстояние ll, но вы можете легко изменить его, чтобы вернуть tt)

double dist = cubic_d(double pnt[3]);

Теперь вы просто портировать это для шейдера и определения того, достаточно ли фрагмента достаточно близко к кривой для его рендеринга (отсюда вместо t также для скорости вы можете избавиться от последнего sqrt и использовать последние приведенные значения).

Функция gl_draw визуализирует контрольные точки (синие) / линии (серые) кривой Безье (аква) с помощью GL, а затем эмулирует фрагментный шейдер для визуализации кривой с толщиной 2*0.05 в (зеленом) ...

Предварительный просмотр:

preview

Теперь это просто вопрос переноса этого в GLSL. Чтобы использовать GLSL-способ прохождения вершин, вам нужно немного увеличить площадь, как здесь:

Но вам нужно немного изменить геометрию, чтобы учесть 4 контрольных точки, а не только 3. Эти вещи должны быть в геометрическом шейдере ...

Так что в геометрическом шейдере вы должны сделать cubic_init, и в фрагментном шейдере discard, если расстояние cubic_d больше толщины.

Поиск основан на:

, которую я разрабатываю для подобных задач. Сам поиск l oop может быть немного изменен для повышения производительности / точности ... но будьте осторожны, при первоначальном поиске выборка кривой должна быть не менее 4-5 кусков, иначе она может перестать работать должным образом для некоторых фигур.

[Edit1] после некоторого размышления здесь версия GLSL

Vertex

// Vertex
#version 400 core
layout(location = 0) in vec2 pos;   // control points (QUADS)
layout(location = 3) in vec3 col;   // color

out vec2 vpos;
out vec3 vcol;

void main()
    {
    vpos=pos;
    vcol=col;
    gl_Position=vec4(pos,0.0,1.0);
    }

Геометрия:

//------------------------------------------------------------------------------
// Geometry
//------------------------------------------------------------------------------
#version 400 core
layout(lines_adjacency) in;
layout(triangle_strip, max_vertices = 4) out;

uniform float d=0.05;   // half thickness

in vec2 vpos[];
in vec3 vcol[];

out vec2 a0,a1,a2,a3;   // cubic coefficients
out vec3 fcol;          // color
out vec2 fpos;          // position
//------------------------------------------------------------------------------
void main()
    {
    vec4 p0,p1,p2,p3,a,b;
    p0=gl_in[0].gl_Position;
    p1=gl_in[1].gl_Position;
    p2=gl_in[2].gl_Position;
    p3=gl_in[3].gl_Position;
    // compute BEZIER coefficients
    a0.x=                             (    p0.x);
    a1.x=                  (3.0*p1.x)-(3.0*p0.x);
    a2.x=       (3.0*p2.x)-(6.0*p1.x)+(3.0*p0.x);
    a3.x=(p3.x)-(3.0*p2.x)+(3.0*p1.x)-(    p0.x);
    a0.y=                             (    p0.y);
    a1.y=                  (3.0*p1.y)-(3.0*p0.y);
    a2.y=       (3.0*p2.y)-(6.0*p1.y)+(3.0*p0.y);
    a3.y=(p3.y)-(3.0*p2.y)+(3.0*p1.y)-(    p0.y);
    // compute BBOX
    a=p0;                     b=p0;
    if (a.x > p1.x) a.x=p1.x; if (b.x < p1.x) b.x=p1.x;
    if (a.x > p2.x) a.x=p2.x; if (b.x < p2.x) b.x=p2.x;
    if (a.x > p3.x) a.x=p3.x; if (b.x < p3.x) b.x=p3.x;
    if (a.y > p1.y) a.y=p1.y; if (b.y < p1.y) b.y=p1.y;
    if (a.y > p2.y) a.y=p2.y; if (b.y < p2.y) b.y=p2.y;
    if (a.y > p3.y) a.y=p3.y; if (b.y < p3.y) b.y=p3.y;
    // enlarge by d
    a.x-=d; a.y-=d;
    b.x+=d; b.y+=d;
    // pass it as QUAD
    fcol=vcol[0];
    fpos=vec2(a.x,a.y); gl_Position=vec4(a.x,a.y,0.0,1.0); EmitVertex();
    fpos=vec2(a.x,b.y); gl_Position=vec4(a.x,b.y,0.0,1.0); EmitVertex();
    fpos=vec2(b.x,a.y); gl_Position=vec4(b.x,a.y,0.0,1.0); EmitVertex();
    fpos=vec2(b.x,b.y); gl_Position=vec4(b.x,b.y,0.0,1.0); EmitVertex();
    EndPrimitive();
    }

//------------------------------------------------------------------------------

Фрагмент:

// Fragment
#version 400 core
uniform float d=0.05;   // half thickness

in vec2 fpos;           // fragment position
in vec3 fcol;           // fragment color
in vec2 a0,a1,a2,a3;    // cubic coefficients

out vec4 col;

vec2 cubic(float t)     // return point on cubic from parameter
    {
    float tt=t*t,ttt=tt*t;
    return a0+(a1*t)+(a2*tt)+(a3*ttt);
    }

void main()
    {
    vec2 p;
    int i;
    float t,tt,t0,t1,dt,l,ll;
    tt=-1.0; ll=-1.0; dt=0.05; t0=0.0; t1=1.0; l=0.0;
    for (i=0;i<3;i++)
        {
        for (t=t0;t<=t1;t+=dt)
            {
            p=cubic(t)-fpos;
            l=length(p);
            if ((ll<0.0)||(ll>l)){ ll=l; tt=t; }
            }
        t0=tt-dt; if (t0<0.0) t0=0.0;
        t1=tt+dt; if (t1>1.0) t1=1.0;
        dt*=0.2;
        }
    if (ll>d) discard;
    col=vec4(fcol,1.0); // ll,tt can be used for coloring or texturing
    }

Ожидается 4 контрольных точки BEZIER на CUBI C в виде GL_LINES_ADJACENCY, поскольку GL_QUADS больше нет :( Когда я использую это так (внутри gl_draw):

glUseProgram(prog_id);               // use our shaders
i=glGetUniformLocation(prog_id,"d"); // set line half thickness
glUniform1f(i,0.02);
glColor3f(0.2,0.7,0.2);              // color
glBegin(GL_LINES_ADJACENCY); 
for (i=0;i<pnts3;i+=3)
 glVertex3dv(pnt+i);
glEnd();
glUseProgram(0);

Результат выглядит так:

shader preview

и грубый намного быстрее , чем эмуляция старого точечного шейдера API :). Я знаю, что шейдеры старого API и GLSL нового стиля не следует смешивать, поэтому вы должны создать VAO / VBO вместо использования glBegin/glEnd ... Я слишком ленив, чтобы сделать это только для ответа на этот вопрос. ..

Здесь пример не функции (больше y на один x) (по сравнению с точками на стороне процессора) :

double pnt[]=                   // cubic curve control points
    {
    +0.9,-0.8,0.0,
    -2.5,+0.8,0.0,
    +2.5,+0.8,0.0,
    -0.9,-0.8,0.0,
    };

non function

Как видите, оба подхода соответствуют форме (точки использовали большую толщину). Чтобы это работало, необходимо правильно установить коэффициенты поиска (dt), чтобы не пропустить решение ...

PS решение куба c Ваш путь приводит к двум наборам из них:

analytical solution

Что, я сильно сомневаюсь, может быть вычислено намного быстрее, чем простой поиск.

[Edit2] дальнейшие улучшения

Я просто изменил геометрический шейдер так, чтобы он сэмплировал кривую на 10 сегментов и генерировал BBOX для каждого отдельно, исключая много пустого пространства, которое нужно было обработать раньше. Я немного изменил цветовую разметку и порядок рендеринга.

Это новый результат (такой же, как предыдущий, но в несколько раз быстрее из-за более низкого отношения пустого пространства):

preview

Вот так выглядит покрытие сейчас:

coverage

До того, как покрытие было BBOX контрольных точек + увеличение на d который в этом случае был намного больше, чем сама кривая (2 контрольные точки находятся снаружи).

Здесь обновлен шейдер Geometry :

//------------------------------------------------------------------------------
// Geometry
//------------------------------------------------------------------------------
#version 400 core
layout(lines_adjacency) in;
layout(triangle_strip, max_vertices = 40) out;  // 4*n <= 60

uniform float d=0.05;   // half thickness

in vec2 vpos[];
in vec3 vcol[];

out vec2 a0,a1,a2,a3;   // cubic coefficients
out vec3 fcol;          // color
out vec2 fpos;          // position
//------------------------------------------------------------------------------
vec2 cubic(float t)     // return point on cubic from parameter
    {
    float tt=t*t,ttt=tt*t;
    return a0+(a1*t)+(a2*tt)+(a3*ttt);
    }
//------------------------------------------------------------------------------
void main()
    {
    float t,dt=1.0/10.0;    // 1/n
    vec2 p0,p1,p2,p3,a,b;
    p0=gl_in[0].gl_Position.xy;
    p1=gl_in[1].gl_Position.xy;
    p2=gl_in[2].gl_Position.xy;
    p3=gl_in[3].gl_Position.xy;
    // compute BEZIER coefficients
    a0.x=                             (    p0.x);
    a1.x=                  (3.0*p1.x)-(3.0*p0.x);
    a2.x=       (3.0*p2.x)-(6.0*p1.x)+(3.0*p0.x);
    a3.x=(p3.x)-(3.0*p2.x)+(3.0*p1.x)-(    p0.x);
    a0.y=                             (    p0.y);
    a1.y=                  (3.0*p1.y)-(3.0*p0.y);
    a2.y=       (3.0*p2.y)-(6.0*p1.y)+(3.0*p0.y);
    a3.y=(p3.y)-(3.0*p2.y)+(3.0*p1.y)-(    p0.y);
    p1=cubic(0.0);
    for (t=dt;t < 1.001;t+=dt)
        {
        p0=p1; p1=cubic(t);
        // compute BBOX
        a=p0;                     b=p0;
        if (a.x > p1.x) a.x=p1.x; if (b.x < p1.x) b.x=p1.x;
        if (a.y > p1.y) a.y=p1.y; if (b.y < p1.y) b.y=p1.y;
        // enlarge by d
        a.x-=d; a.y-=d;
        b.x+=d; b.y+=d;
        // pass it as QUAD
        fcol=vcol[0];
        fpos=vec2(a.x,a.y); gl_Position=vec4(a.x,a.y,0.0,1.0); EmitVertex();
        fpos=vec2(a.x,b.y); gl_Position=vec4(a.x,b.y,0.0,1.0); EmitVertex();
        fpos=vec2(b.x,a.y); gl_Position=vec4(b.x,a.y,0.0,1.0); EmitVertex();
        fpos=vec2(b.x,b.y); gl_Position=vec4(b.x,b.y,0.0,1.0); EmitVertex();
        EndPrimitive();
        }
    }
//------------------------------------------------------------------------------

Моя карта gfx имеет ограничение 60 вершин, поэтому при выводе полос треугольника, эмулирующих QUAD, ограничение по сегментам составляет 60/4 = 15 Я использовал n=10 только для того, чтобы убедиться, что он работает на более низких HW. Чтобы изменить количество сегментов, просмотрите две строки с комментарием, содержащим n

[Edit3], еще лучшее соотношение полезного / пустого пространства

Я изменил Покрытие AABB BBOX до ~ OOB BBOX без перекрытий. Это также позволяет передать фактический диапазон t во фрагмент, ускоряя поиск ~ в 10 раз. Обновлены шейдеры:

Вершина:

// Vertex
#version 400 core
layout(location = 0) in vec2 pos;   // control points (QUADS)
layout(location = 3) in vec3 col;   // color

out vec2 vpos;
out vec3 vcol;

void main()
    {
    vpos=pos;
    vcol=col;
    gl_Position=vec4(pos,0.0,1.0);
    }

Геометрия:

//------------------------------------------------------------------------------
// Geometry
//------------------------------------------------------------------------------
#version 400 core
layout(lines_adjacency) in;
layout(triangle_strip, max_vertices = 40) out;  // 4*n <= 60

uniform float d=0.05;   // half thickness

in vec2 vpos[];
in vec3 vcol[];

out vec2 a0,a1,a2,a3;   // cubic coefficients
out vec3 fcol;          // color
out vec2 fpos;          // position
out vec2 trange;        // t range of chunk
//------------------------------------------------------------------------------
vec2 cubic(float t)     // return point on cubic from parameter
    {
    float tt=t*t,ttt=tt*t;
    return a0+(a1*t)+(a2*tt)+(a3*ttt);
    }
//------------------------------------------------------------------------------
void main()
    {
    int i,j,n=10,m=10;              // n,m
    float t,dd,d0,d1,dt=1.0/10.0;   // 1/n
    float tt,dtt=1.0/100.0;         // 1/(n*m)
    vec2 p0,p1,p2,p3,u,v;
    vec2 q0,q1,q2,q3;
    p0=gl_in[0].gl_Position.xy;
    p1=gl_in[1].gl_Position.xy;
    p2=gl_in[2].gl_Position.xy;
    p3=gl_in[3].gl_Position.xy;
    // compute BEZIER coefficients
    a0.x=                             (    p0.x);
    a1.x=                  (3.0*p1.x)-(3.0*p0.x);
    a2.x=       (3.0*p2.x)-(6.0*p1.x)+(3.0*p0.x);
    a3.x=(p3.x)-(3.0*p2.x)+(3.0*p1.x)-(    p0.x);
    a0.y=                             (    p0.y);
    a1.y=                  (3.0*p1.y)-(3.0*p0.y);
    a2.y=       (3.0*p2.y)-(6.0*p1.y)+(3.0*p0.y);
    a3.y=(p3.y)-(3.0*p2.y)+(3.0*p1.y)-(    p0.y);
    q2=vec2(0.0,0.0);
    q3=vec2(0.0,0.0);
    // sample curve by chunks
    for (p1=cubic(0.0),i=0,t=dt;i<n;i++,t+=dt)
        {
        // sample point
        p0=p1; p1=cubic(t); q0=q2; q1=q3;
        // compute ~OBB enlarged by D
        u=normalize(p1-p0);
        v=vec2(u.y,-u.x);
        // resample chunk to compute enlargement
        for (d0=0.0,d1=0.0,tt=t-dtt,j=2;j<m;j++,tt-=dtt)
            {
            dd=dot(cubic(tt)-p0,v);
            d0=max(-dd,d0);
            d1=max(+dd,d1);
            }
        d0+=d; d1+=d; u*=d;
        d0*=1.25; d1*=1.25; // just to be sure
        // enlarge radial
        q2=p1+(v*d1);
        q3=p1-(v*d0);
        // enlarge axial
        if (i==0)
            {
            q0=p0+(v*d1)-u;
            q1=p0-(v*d0)-u;
            }
        if (i==n-1)
            {
            q2+=u;
            q3+=u;
            }
        // pass it as QUAD
        fcol=vcol[0]; trange=vec2(t-dt,t);
        fpos=q0; gl_Position=vec4(q0,0.0,1.0); EmitVertex();
        fpos=q1; gl_Position=vec4(q1,0.0,1.0); EmitVertex();
        fpos=q2; gl_Position=vec4(q2,0.0,1.0); EmitVertex();
        fpos=q3; gl_Position=vec4(q3,0.0,1.0); EmitVertex();
        EndPrimitive();
        }
    }
//------------------------------------------------------------------------------*

Фрагмент:

// Fragment
#version 400 core

//#define show_coverage

uniform float d=0.05;   // half thickness

in vec2 fpos;           // fragment position
in vec3 fcol;           // fragment color
in vec2 a0,a1,a2,a3;    // cubic coefficients
in vec2 trange;         // t range of chunk

out vec4 col;

vec2 cubic(float t)     // return point on cubic from parameter
    {
    float tt=t*t,ttt=tt*t;
    return a0+(a1*t)+(a2*tt)+(a3*ttt);
    }

void main()
    {
    vec2 p;
    int i,n;
    float t,tt,t0,t1,dt,l,ll;
    tt=-1.0; ll=-1.0; l=0.0;
    #ifdef show_coverage
    t0=0.0; t1=1.0; dt=0.05; n=3;
    #else
    t0=trange.x; n=2;
    t1=trange.y;
    dt=(t1-t0)*0.1;
    #endif
    for (i=0;i<n;i++)
        {
        for (t=t0;t<=t1;t+=dt)
            {
            p=cubic(t)-fpos;
            l=length(p);
            if ((ll<0.0)||(ll>l)){ ll=l; tt=t; }
            }
        t0=tt-dt; if (t0<0.0) t0=0.0;
        t1=tt+dt; if (t1>1.0) t1=1.0;
        dt*=0.2;
        }
    #ifdef show_coverage
    if (ll>d) col=vec4(0.1,0.1,0.1,1.0); else
    #else
    if (ll>d) discard;
    #endif
    col=vec4(fcol,1.0);
    }

И предварительный просмотр (кривая + покрытие):

better coverage

И просто кривая:

curve

as Вы можете видеть шов на пересечении с покрытием, которое связано с визуализацией покрытия без наложения. Сама кривая в порядке.

Параметры d0,d1 - это максимальные перпендикулярные расстояния до фактической осевой оси (U) блока OBB, увеличенные на d и увеличенные на 25%, чтобы быть уверенными. Похоже, это подходит очень хорошо. Я сомневаюсь, что дальнейшая оптимизация принесет много пользы, поскольку этот результат очень близок к идеальному соответствию покрытия ...

#define show_coverage просто позволяет увидеть, какая геометрия передается фрагментному шейдеру ...

2 голосов
/ 06 февраля 2020

Кривые Безье в основном являются кубиками, и есть формула, получающая результаты кубик, которые вы можете увидеть, посмотрев Cubi c уравнение в Википедии. Это довольно сложно, но вы можете следовать по методу. Вместо того, чтобы использовать формулу, легче следовать шагам методов. Этот вопрос Quora Как я могу решить уравнение третьей степени? содержит ответы, в которых подробно обсуждаются различные методы.

В другом ответе упоминается, что решение не всегда уникально для При заданном значении x возможны одно, два или три возможных значения t. Когда вы работаете с алгоритмом, вам нужно пару раз вычислить квадратные корни числа, у него будет два решения: + sqrt (...) или -sqrt (...). Выполнение алгоритма для каждого значения даст вам решения.

Следует также упомянуть, что промежуточная часть алгоритма будет включать комплексные числа всякий раз, когда вычисляется квадрат root отрицательного числа. Опять же, вам нужно рассмотреть пару решений, которые будут комплексными сопряженными.

...