Можно ли сгущать квадратичную кривую Безье, используя только графический процессор? - PullRequest
14 голосов
/ 04 апреля 2011

Я рисую много квадратичных кривых Безье в моей программе OpenGL.Прямо сейчас, кривые имеют толщину в один пиксель и генерируются программно, потому что я на довольно ранней стадии, и достаточно посмотреть, что работает.

Достаточно просто, учитывая 3 контрольных точки ( P 0 до P 2 ), я оцениваю следующее уравнение с t , варьирующимся от 0 до 1 (с шагами1/8) в программном обеспечении и используйте GL_LINE_STRIP, чтобы связать их вместе:

B ( t ) = (1 - t ) 2 P 0 + 2 (1 - t ) t P 1 + t 2 P 2

Где B, очевидно, достаточно, результатыв двумерном векторе.

Этот подход работал «достаточно хорошо», так как даже мои самые большие кривые не требуют намного больше, чем 8 шагов, чтобы выглядеть изогнутыми.Тем не менее, тонкие кривые в один пиксель уродливы.

Я хотел написать шейдер GLSL, который бы принимал контрольные точки и унифицированную переменную thickness, чтобы сделать кривые более толстыми.Сначала я думал о создании только пиксельного шейдера, который бы окрашивал только пиксели на расстоянии thickness / 2 от кривой, но для этого требуется решить полином третьей степени, и выбор между тремя решениями внутри шейдера не выглядит каклучшая идея когда-либо.

Затем я попытался посмотреть, если другие люди уже сделали это.Я наткнулся на белую бумагу Лупа и Блинна из Microsoft Research , где ребята показывают простой способ заполнения области под кривой.Несмотря на то, что он хорошо работает в этой степени, у меня возникают проблемы с адаптацией идеи к рисованию между двумя кривыми роста.

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

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

Существуют ли жизнеспособные алгоритмы для обводки квадратичного Безьекривые с шейдером?

Ответы [ 3 ]

3 голосов
/ 10 апреля 2011

Это отчасти продолжает мой предыдущий ответ, но на самом деле все по-другому, так как в этом ответе я ошибся.

Чтобы разрешить фрагментному шейдеру только затенение между двумя кривыми, в качестве переменных переменных предоставляются два набора «текстурных» координат, к которым применяется метод Loop-Blinn.

varying vec2 texCoord1,texCoord2;
varying float insideOutside;

varying vec4 col;

void main()
{   
    float f1 = texCoord1[0] * texCoord1[0] - texCoord1[1];
    float f2 = texCoord2[0] * texCoord2[0] - texCoord2[1];

    float alpha = (sign(insideOutside*f1) + 1) * (sign(-insideOutside*f2) + 1) * 0.25;
    gl_FragColor = vec4(col.rgb, col.a * alpha);
}

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

Решение этой проблемы - найти линейную функцию, отображающую координаты (x, y) в интерполированные / экстраполированные значения. Затем эти значения могут быть установлены для каждой вершины при рендеринге треугольника. Вот ключевая часть моего кода для этой части.

    vec2[3] tex = vec2[3]( vec2(0,0), vec2(0.5,0), vec2(1,1) );

    mat3 uvmat;
    uvmat[0] = vec3(pos2[0].x, pos2[1].x, pos2[2].x);
    uvmat[1] = vec3(pos2[0].y, pos2[1].y, pos2[2].y);
    uvmat[2] = vec3(1, 1, 1);

    mat3 uvInv = inverse(transpose(uvmat));

    vec3 uCoeffs = vec3(tex[0][0],tex[1][0],tex[2][0]) * uvInv;
    vec3 vCoeffs = vec3(tex[0][1],tex[1][1],tex[2][1]) * uvInv;

    float[3] uOther, vOther;
    for(i=0; i<3; i++) {
        uOther[i] = dot(uCoeffs,vec3(pos1[i].xy,1));
        vOther[i] = dot(vCoeffs,vec3(pos1[i].xy,1));
    }   

    insideOutside = 1;
    for(i=0; i< gl_VerticesIn; i++){
        gl_Position = gl_ModelViewProjectionMatrix * pos1[i];
        texCoord1 = tex[i];
        texCoord2 = vec2(uOther[i], vOther[i]);
        EmitVertex();
    }
    EndPrimitive();

Здесь pos1 и pos2 содержат координаты двух контрольных треугольников. Эта часть визуализирует треугольник, определенный pos1, но с texCoord2, установленным в переведенные значения из треугольника pos2. Затем треугольник pos2 необходимо визуализировать аналогичным образом. Затем необходимо заполнить промежуток между этими двумя треугольниками на каждом конце, и оба набора координат перевести соответствующим образом.

Для вычисления обратной матрицы требуется либо GLSL 1,50, либо кодирование вручную. Было бы лучше решить уравнение для перевода без вычисления обратного. В любом случае, я не ожидаю, что эта часть будет особенно быстрой в геометрическом шейдере.

0 голосов
/ 06 апреля 2011

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

Вершинный шейдер
Бросьте обычную линию точек в ваш вершинный шейдер.Пусть вершинный шейдер сместит точки к безье.

Геометрический шейдер

Пусть ваш геометрический шейдер создаст дополнительную точку для каждой вершины.

foreach (point p in bezierCurve)
    new point(p+(0,thickness,0)) // in tangent with p1-p2        

Фрагмент шейдера
Для поглаживания вашего Безье специальным штрихом вы можете использовать текстуру с альфа-каналом.Вы можете проверить альфа-канал на его значение.Если это ноль, обрезать пиксель.Таким образом, вы все равно можете заставить систему думать, что это сплошная линия, а не полупрозрачная.Вы можете применить некоторые паттерны в своем альфа-канале.

Надеюсь, это поможет вам на вашем пути.Вам придется много разбираться в себе, но я думаю, что затенение геометрии ускорит ваш Безье.


Тем не менее, за поглаживания, я продолжаю свой выбор создания GL_QUAD_STRIP итекстура альфа-канала.

0 голосов
/ 04 апреля 2011

Вы должны быть в состоянии использовать технику Лупа и Блинна в статье, которую вы упомянули.

По сути, вам нужно сместить каждую контрольную точку в нормальном направлении, в обоих направлениях, чтобы получить контрольные точки для двух кривых (внутренней и внешней). Затем следуйте методике, описанной в разделе 3.1 Loop и Blinn - это разбивает участки кривой, чтобы избежать наложения треугольников, а затем триангулирует основную часть внутренней части (обратите внимание, что для этой части требуется процессор). Наконец, эти треугольники заполнены, и маленькие изогнутые части вне их визуализируются на графическом процессоре, используя метод Loop и Blinn (в начале и конце раздела 3).

Альтернативная техника, которая может работать для вас, описана здесь: Толстые кривые Безье в OpenGL

EDIT: Ах, вы хотите избежать даже триангуляции процессора - я должен был прочитать более внимательно.

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

В вашем случае с постоянной толщиной, я думаю, сработает довольно простая триангуляция - используя Loop и Bling для всех «изогнутых битов». Когда два контрольных треугольника не пересекаются, это легко. Когда они это делают, часть вне пересечения легко. Таким образом, единственная сложная часть находится на пересечении (это должен быть треугольник).

Внутри пересечения вы хотите заштриховать пиксель, только если оба контрольных треугольника приводят к его затенению с помощью Loop и Bling. Таким образом, фрагментный шейдер должен уметь выполнять поиск текстуры для обоих треугольников. Один из них может быть стандартным, и вам нужно будет добавить переменную переменную vec2 для второго набора координат текстуры, который вам нужно будет установить соответствующим образом для каждой вершины треугольника. Также вам понадобится единая переменная «sampler2D» для текстуры, которую вы можете затем сэмплировать с помощью texture2D. Затем вы просто закрашиваете фрагменты, которые удовлетворяют проверкам для обоих контрольных треугольников (в пределах пересечения).

Я думаю, что это работает в каждом случае, но, возможно, я что-то упустил.

...