OpenGL простой сглаженный шейдер полигональной сетки

#opengl-es #grid #glsl #fragment-shader #antialiasing

#opengl-es #сетка #glsl #фрагмент-шейдер #сглаживание

Вопрос:

Как создать тестовый шаблон сетки со сглаженными линиями в шейдере фрагментов?

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

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

Вот почему базовый цвет здесь равен (0,0,0,0), по сути, ничего. Затем я могу использовать шаблоны смешивания GL, чтобы наложить результат этого шейдера на любую мою текстуру.

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

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

 //java code
shaderCode = shaderCode.replaceFirst("__CONSTANT_SQUARE_SIZE", String.valueOf(GlobalSettings.PLANE_SQUARE_SIZE));
  

Ответ №1:

Если бы я мог поделиться с вами кодом, который я использую для сглаживания сеток, это могло бы облегчить задачу. Все, что я сделал, это использовал координаты текстуры для рисования сетки на плоскости. Я использовал GLSL genType fract(genType x) для повторения текстурного пространства. Затем я использовал функцию абсолютного значения, чтобы по существу вычислить расстояние каждого пикселя до линии сетки. Остальные операции должны интерпретировать это как цвет.

Вы можете поиграть с этим кодом непосредственно на Shadertoy.com вставив его в новый шейдер.

Если вы хотите использовать его в своем коде, единственные строки, которые вам нужны, — это часть, начинающаяся с gridSize переменной и заканчивающаяся grid переменной.

iResolution.y это высота экрана, uv это текстурная координата вашей плоскости.

gridSize и width вероятно, следует предоставить единообразную переменную.

 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // aspect correct pixel coordinates (for shadertoy only)
    vec2 uv = fragCoord / iResolution.xy * vec2(iResolution.x / iResolution.y, 1.0);
    // get some diagonal lines going (for shadertoy only)
    uv.yx  = uv.xy * 0.1;

    // for every unit of texture space, I want 10 grid lines
    float gridSize = 10.0;
    // width of a line on the screen plus a little bit for AA
    float width = (gridSize * 1.2) / iResolution.y;

    // chop up into grid
    uv = fract(uv * gridSize);
    // abs version
    float grid = max(
        1.0 - abs((uv.y - 0.5) / width),
        1.0 - abs((uv.x - 0.5) / width)
    );

    // Output to screen (for shadertoy only)
    fragColor = vec4(grid, grid, grid, 1.0);
}
  

Удачного затенения!

Ответ №2:

Вот мои шейдеры:

Вершина:

 #version 300 es

precision highp float;
precision highp int;

layout (location=0) in vec3 position;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
uniform vec2 coordShift;
uniform mat4 modelMatrix;

out highp vec3 vertexPosition;

const float PLANE_SCALE = __CONSTANT_PLANE_SCALE;   //assigned during shader compillation

void main()
{
    // generate position data for the fragment shader
    // does not take view matrix or projection matrix into account
    // TODO:  3.0 part is contingent on the actual mesh. It is supposed to be it's lowest possible coordinate.
    // TODO: the mesh here is 6x6 with -3..3 coords. I normalize it to 0..6 for correct fragment shader calculations
    vertexPosition = vec3((position.x 3.0)*PLANE_SCALE coordShift.x, position.y, (position.z 3.0)*PLANE_SCALE coordShift.y);

    // position data for the OpenGL vertex drawing
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
  

Обратите внимание, что здесь я вычисляю VertexPosition и передаю его в шейдер фрагмента. Это делается для того, чтобы моя сетка «перемещалась» при перемещении объекта. Дело в том, что в моем приложении у меня основа в основном привязана к основному объекту. Объект (назовите его символом или как угодно) не перемещается по плоскости и не меняет своего положения относительно плоскости. Но чтобы создать иллюзию движения — я вычисляю сдвиг координат (относительно размера квадрата) и использую это для вычисления положения вершины.

Это немного сложно, но я подумал, что включу это. В принципе, если размер квадрата установлен равным 5.0 (т. Е. у нас квадратная сетка размером 5×5 метров), то смещение координат на (0,0) будет означать, что символ находится в нижнем левом углу квадрата; смещение координат на (2.5, 2.5) будет средним, а (5,5) — в правом верхнем углу. После прохождения 5 сдвиг возвращается к 0. Опускаемся ниже 0 — он переходит в 5.

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

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

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

Вот шейдер фрагментов:

 #version 300 es

precision highp float;

in highp vec3 vertexPosition;

out mediump vec4 fragColor;

const float squareSize = __CONSTANT_SQUARE_SIZE;
const vec3 color_l1 = __CONSTANT_COLOR_L1;

void main()
{
    // calculate deriviatives
    // (must be done at the start before conditionals)
    float dXy = abs(dFdx(vertexPosition.z)) / 2.0;
    float dYy = abs(dFdy(vertexPosition.z)) / 2.0;
    float dXx = abs(dFdx(vertexPosition.x)) / 2.0;
    float dYx = abs(dFdy(vertexPosition.x)) / 2.0;

    // find and fill horizontal lines
    int roundPos = int(vertexPosition.z / squareSize);
    float remainder = vertexPosition.z - float(roundPos)*squareSize;
    float width = max(dYy, dXy) * 2.0;

    if (remainder <= width)
    {
        float diff = (width - remainder) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    if (remainder >= (squareSize - width))
    {
        float diff = (remainder - squareSize   width) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    // find and fill vertical lines
    roundPos = int(vertexPosition.x / squareSize);
    remainder = vertexPosition.x - float(roundPos)*squareSize;
    width = max(dYx, dXx) * 2.0;

    if (remainder <= width)
    {
        float diff = (width - remainder) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    if (remainder >= (squareSize - width))
    {
        float diff = (remainder - squareSize   width) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    // fill base color
    fragColor = vec4(0,0,0, 0);
    return;
}
  

В настоящее время он рассчитан только на линии толщиной в 1 пиксель, но вы можете контролировать толщину, управляя «шириной»

Здесь первая важная часть — это функции dfdx / dfdy. Это функции GLSL, и я просто скажу, что они позволяют вам определять, сколько места в МИРОВЫХ координатах занимает ваш фрагмент на экране, на основе Z-расстояния до этой точки на вашей плоскости. Что ж, это был полный рот. Я уверен, что вы сможете разобраться в этом, если почитаете документы для них.

Затем я принимаю максимум этих выходных данных за ширину. В принципе, в зависимости от того, как выглядит ваша камера, вы хотите немного «растянуть» ширину вашей линии.

остаток — это, в основном, то, как далеко этот фрагмент находится от линии, которую мы хотим нарисовать в мировых координатах. Если это слишком далеко — нам не нужно его заполнять.

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

Вы можете видеть, что здесь я сравниваю остаток с шириной линии. Чем больше ширина — тем больше остаток может быть, чтобы «попасть» в него. Я должен сравнить это с обеих сторон, потому что в противном случае вы смотрите только на пиксели, которые находятся близко к линии со стороны отрицательной координаты, и сбрасываете со счетов положительные, которые все еще могут попасть в нее.

Теперь, для простого эффекта сглаживания, нам нужно заставить эти перекрывающиеся сегменты «исчезать» по мере приближения к их концам. Для этой цели я вычисляю дробь, чтобы увидеть, насколько глубоко остаток находится внутри линии. Когда дробь равна 1, это означает, что наша линия, которую мы хотим нарисовать, в основном проходит прямо через середину фрагмента, который мы в данный момент рисуем. Когда доля приближается к 0, это означает, что фрагмент все дальше и дальше удаляется от линии, и, следовательно, его следует делать все более прозрачным.

Наконец, мы делаем это с обеих сторон для горизонтальных и вертикальных линий отдельно. Мы должны выполнять их по отдельности, потому что dFdx / dFdy должны отличаться для вертикальных и горизонтальных линий, поэтому мы не можем использовать их в одной формуле.

И, наконец, если мы недостаточно близко подошли к какой-либо из линий — мы заливаем фрагмент прозрачным цветом.

Я не уверен, что это лучший код для задачи, но он работает. Если у вас есть предложения, дайте мне знать!

p.s. шейдеры написаны для Opengl-ES, но они должны работать и для OpenGL.