Можно ли сгущать квадратичную кривую Безье с использованием только GPU?

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

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

B (t) = (1 - t) 2 P 0 + 2 (1 - t) tP 1 + t 2 P <суб > 2суб >

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

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

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

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

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

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

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

Ответы

Ответ 1

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

Чтобы позволить шейдеру фрагмента только оттенок между двумя кривыми, два набора "текстурных" координат подаются как переменные, к которым применяется техника 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, либо ее необходимо закодировать вручную. Было бы лучше решить уравнение для перевода без вычисления обратного. В любом случае, я не ожидаю, что эта часть будет особенно быстрой в геометрическом шейдере.

Ответ 2

Вы должны использовать технику Loop и Blinn в упомянутой вами статье.

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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


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