Градиент "митра" в OpenGL показывает швы при соединении - PullRequest
7 голосов
/ 30 декабря 2010

Я провожу некоторые базовые эксперименты вокруг 2D-работы в GL.Я пытаюсь нарисовать «рамку» вокруг прямоугольной области.Я бы хотел, чтобы у рамы был постоянный градиент, и поэтому я строю его с геометрией, которая выглядит как четыре квадрата, по одному на каждой стороне рамы, сужающихся, чтобы сделать трапеции, которые эффективно соединяются под углом.,

Координаты vert одинаковы для "внутреннего" и "внешнего" прямоугольников, а цвета одинаковы для всех внутренних и всех внешних элементов, поэтому я ожидаю увидеть идеальное смешивание по краям.

Но обратите внимание на изображение ниже, как будто в углу соединения есть «шов», который легче, чем должен быть.

Мне кажется, что в математике я что-то упускаю концептуально, что объясняет это.Является ли этот артефакт каким-то образом результатом наклона градиента?Если я изменю все цвета на непрозрачный синий (скажем), я получу идеально ровную синюю рамку, как и ожидалось.

Обновление: Код добавлен ниже.Извините, довольно многословно.Использование 2-треугольных вентиляторов для трапеций вместо четырехугольников.

Спасибо!

alt text

glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

// Prep the color array. This is the same for all trapezoids.
// 4 verts * 4 components/color = 16 values. 
GLfloat colors[16];
colors[0] = 0.0;
colors[1] = 0.0;
colors[2] = 1.0;
colors[3] = 1.0;
colors[4] = 0.0;
colors[5] = 0.0;
colors[6] = 1.0;
colors[7] = 1.0;

colors[8] = 1.0;
colors[9] = 1.0;
colors[10] = 1.0;
colors[11] = 1.0;
colors[12] = 1.0;
colors[13] = 1.0;
colors[14] = 1.0;
colors[15] = 1.0;

// Draw the trapezoidal frame areas. Each one is two triangle fans.
// Fan of 2 triangles = 4 verts = 8 values
GLfloat vertices[8];

float insetOffset = 100;
float frameMaxDimension = 1000;

// Bottom 
vertices[0] = 0;
vertices[1] = 0;
vertices[2] = frameMaxDimension;
vertices[3] = 0;
vertices[4] = frameMaxDimension - insetOffset;
vertices[5] = 0 + insetOffset;
vertices[6] = 0 + insetOffset;
vertices[7] = 0 + insetOffset;
glVertexPointer(2, GL_FLOAT , 0, vertices); 
glColorPointer(4, GL_FLOAT, 0, colors);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);    

// Left
vertices[0] = 0;
vertices[1] = frameMaxDimension;
vertices[2] = 0;
vertices[3] = 0;
vertices[4] = 0 + insetOffset;
vertices[5] = 0 + insetOffset;
vertices[6] = 0 + insetOffset;
vertices[7] = frameMaxDimension - inset;    
glVertexPointer(2, GL_FLOAT , 0, vertices); 
glColorPointer(4, GL_FLOAT, 0, colors);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);    

/* top & right would be as expected... */

glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

Ответы [ 3 ]

6 голосов
/ 01 января 2011

Как @Newbie опубликовал в комментариях,

@ quixoto: откройте свое изображение в программе Paint, щелкните инструментом заливки где-нибудь в шве, и вы увидите, что он образует линию угла 90 градусов... означает, что есть только 1 цвет, нигде не ярче в «шве».это просто иллюзия.

Верно.Хотя я не знаком с этой частью математики в OpenGL, я считаю, что это неявный результат того, как выполняется интерполяция цветов между вершинами треугольника ... Я уверен, что это называется "Билинейная интерполяция".

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

Тем не менее, легко создать такую ​​границу в фрагментном шейдере .

Хорошее решение с использованием шейдера GLSL ...


Предположим, вы рисуете прямоугольник с левым нижним углом, координаты текстуры которого равны (0,0), а верхний-правильный угол с (1,1).

Тогда генерация "митры" процедурно в фрагментном шейдере выглядела бы так, если я прав:

varying vec2 coord;

uniform vec2 insetWidth; // width of the border in %, max would be 0.5

void main() {

    vec3 borderColor = vec3(0,0,1);
    vec3 backgroundColor = vec3(1,1,1); 

    // x and y inset, 0..1, 1 means border, 0 means centre

    vec2 insets = max(-coord + insetWidth, vec2(0,0)) / insetWidth;

Если я 'На данный момент значение m корректно, тогда для каждого пикселя значение insets.x имеет значение в диапазоне [0..1]

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

Левая вертикальная полоса имеет insets.y == 0,, нижняя горизонтальная полоса имеет insets.x = 0,, а нижний левый угол имеет пару (insets.x, insets.y), охватывающую весь 2Dдиапазон от (0,0) до (1,1).См. Рис. Для ясности:

insets

Теперь мы хотим преобразование, которое для данной пары (x, y) даст нам ОДНО значение [0..1], определяющее, как смешивать фон ицвет переднего плана.1 означает границу 100%, 0 означает границу 0%.И это можно сделать несколькими способами!

Функция должна соответствовать требованиям:

  • 0, если x == 0, и y == 0
  • 1, еслилибо x == 1, либо y == 1
  • сглаживание значений между ними.

Предположим, что такая функция:

float bias = max(insets.x,insets.y);

Она удовлетворяет этим требованиям.На самом деле, я уверен, что эта функция даст вам тот же «острый» край, что и у вас выше.Попробуйте вычислить его на бумаге для выбора координат внутри этого нижнего левого прямоугольника.

Если мы хотим, чтобы там была гладкая круглая срезка, нам просто нужна другая функция.Я думаю, что что-то вроде этого было бы достаточно:

float bias = min( length(insets) , 1 );

Функция length() здесь просто sqrt(insets.x*insets.x + insets.y*insets.y).Что важно: это означает: «чем дальше (с точки зрения евклидова расстояния) мы от границы, тем более заметной должна быть граница», а min () просто для того, чтобы результат был не больше 1 (=100%).

Обратите внимание, что наша исходная функция придерживается точно такого же определения, но расстояние рассчитывается по метрике Шахматная доска (Чебышев), а не по евклидовой метрике.

Это означает, чтоиспользуя, например, манхэттенскую метрику, вы получите третью возможную форму митры!Это будет определено так:

float bias = min(insets.x+insets.y, 1);

Я предсказываю, что у этого также будет видимая "диагональная линия", но диагональ будет в другом направлении ("\").

ОК, поэтому для остальной части кода, когда у нас есть смещение [0..1], нам просто нужно смешать цвет фона и переднего плана:

    vec3 finalColor = mix(borderColor, backgroundColor, bias);
    gl_FragColor = vec4(finalColor, 1); // return the calculated RGB, and set alpha to 1 
}

И это все!Использование GLSL с OpenGL делает жизнь проще.Надеюсь, это поможет!

3 голосов
/ 03 января 2011

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

1 голос
/ 01 января 2011

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

Я также попробовал нарисовать треугольники отдельно, слегка перекрывая друг друга, с правильным режимом композиции (или смешиванием OpenGL), чтобы вы не получили эффекта.Я работал, но я не стал использовать это, потому что это была лишь крошечная часть конечного продукта, и оно того не стоило.

Извините, я понятия не имею, что является основной причинойэтот эффект, однако: (

...