Получите код OpenGL старого стиля, работающий в GLSL

Я пытаюсь нарисовать этот шаблон в OpenGL:

Чтобы получить это, я создал шаблон как:

vector< vector<DataPoint> > datas;
float Intensitytemp=0;
float xPos=0, yPos=0, angleInRadians=0;
for (float theta = 0.0f; theta < 4096; theta += 1.f)
{
    vector<DataPoint> temp;
    angleInRadians = 2 * M_PI*theta / 4096;
    for (float r = 0; r < 4096; r += 1.f)
    {
        xPos = cos(angleInRadians)*r / 4096;
        yPos = sin(angleInRadians)*r / 4096;
        Intensitytemp = ((float)((int)r % 256)) / 255;
        DataPoint dt;
        dt.x = xPos;
        dt.y = yPos;
        dt.Int = Intensitytemp;
        temp.push_back(dt);
    }
    datas.push_back(temp);
}

и я рисую шаблон как:

glBegin(GL_POINTS);
    for (int x = 0; x < 4096; x++)
        for (int y = 0; y < 4096; y++)
        {
            xPos = datas[x][y].x;
            yPos = datas[x][y].y;
            Intensitytemp = datas[x][y].Int;
            glColor4f(0.0f, Intensitytemp, 0.0f, 1.0f);
            glVertex3f(xPos, yPos, 0.0f);
        }
glEnd();

Если я создаю данные в glBegin()-glEnd() блок, он работает быстрее. Но в обоих случаях я считаю, что в GLSL все лучше. Я не очень хорошо понимаю логику современного OpenGL.

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

2 ответа

Решение
  1. рендеринг четырехугольника, охватывающего экран

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    GLint id;
    glUseProgram(prog_id);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);
    
    glBegin(GL_QUADS);
    glColor3f(1,1,1);
    glVertex2f(-1.0,-1.0);
    glVertex2f(-1.0,+1.0);
    glVertex2f(+1.0,+1.0);
    glVertex2f(+1.0,-1.0);
    glEnd();
    
    glUseProgram(0);
    glFlush();
    SwapBuffers(hdc);
    

    См. Также См. Полный пример GL+GLSL+VAO/VBO C++ о том, как заставить работать GLSL (даже новые вещи)

    Не забудьте установить вид GL на квадратную область!

  2. в вершинном шейдере передайте координаты вершины фрагменту

    нет необходимости в матрицах... pos находится в диапазоне <-1.0,1.0> что хорошо для фрагмента.

    // Vertex
    varying vec2 pos;
    void main()
        {
        pos=gl_Vertex.xy;
        gl_Position=gl_Vertex;
        }
    
  3. во фрагменте вычислить расстояние от середины (0,0) и вычислить окончательный цвет из него

    // Fragment
    varying vec2 pos;
    void main()
        {
        vec4 c=vec4(0.0,0.0,0.0,1.0);
        float r=length(pos);    // radius = distance to (0,0)
        if (r<=1.0)             // inside disc?
            {
            r=16.0*r;           // your range 16=4096/256
            c.g=r-floor(r);     // use only the fractional part ... %256
            }
        gl_FragColor=c;
        }
    

    Вот результат:

    пример вывода

  4. Как работает GLSL

    Вы можете использовать фрагментный шейдер в качестве механизма вычисления цвета для заливки полигонов. Это работает так:

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

    Если поддерживается примитив (устанавливается glBegin в старом стиле GL) (как TRIANGLE,QUAD,...) полностью передается растеризация карты gfx. Это делается с помощью HW- интерполяторов, вызывающих фрагментный шейдер для каждого "пикселя" для заполнения. Поскольку "пиксель" содержит гораздо больше данных, чем просто цвет, его также можно отбросить... вместо этого он называется фрагментом. Его единственная цель - вычислить целевой цвет пикселя на экране, который он представляет. Вы не можете изменить свою позицию только цветом. Это самая большая разница между старым подходом GL и GLSL. Вы не можете изменить форму или положение объектов только тем, как они окрашены / затенены, отсюда и название шейдеров. Поэтому, если вам нужно сгенерировать конкретный паттерн или эффект, вы обычно визуализируете некоторый примитив, покрывающий область, занимаемую GL, и перекрашивает его с помощью вычислений в основном фрагментном шейдере.

    Очевидно, что шейдер Vertex в большинстве случаев вызывается не так часто, как шейдер Fragment, поэтому перенесите как можно больше вычислений в шейдер Vertex для повышения производительности.

    Более новые версии GLSL поддерживают также геометрические и тесселяционные шейдеры, но эта глава сама по себе и не важна для вас сейчас. (сначала нужно привыкнуть к вершине / фрагменту).

[Заметки]

не замужем if в таких простых шейдерах это не большая проблема. Основное увеличение скорости заключается в том, что вы проходите одиночный квад вместо 4096x4096 точки. Код шейдера полностью распараллелен непосредственно GWX. Вот почему архитектура такова... ограничивает некоторые возможности того, что может быть эффективно сделано внутри шейдера, по сравнению со стандартными архитектурами CPU/MEM.

[Edit1]

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

// Fragment
varying vec2 pos;
void main()
    {
    vec4 c=vec4(0.0,0.0,0.0,1.0);
    float r=length(pos);            // radius = distance to (0,0)
    r*=max(1.0+floor(1.0-r),0.0);   // if (r>1.0) r=0.0;
    r*=16.0;                        // your range 16=4096/256
    c.g=r-floor(r);                 // use only the fractional part ... %256
    gl_FragColor=c;
    }

Чтобы прямо ответить на ваш вопрос

Нет, теперь так работают шейдеры. Шейдеры переопределяют части конвейера рендеринга. В древнем OpenGL, где конвейер фиксирован, графический процессор использует встроенные процедуры шейдера для рендеринга примитивов, которые вы загружаете в него через glBegin/glEnd связанные звонки. Однако в более поздних версиях OpenGL вы можете написать собственные подпрограммы для использования вашим графическим процессором. В обоих случаяхвам необходимо отправить данные для работы с шейдерами.

Чтобы дать вам лучший подход о том, как это сделать

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

Теперь, поскольку вы пытаетесь нарисовать шаблон с формулой, вы можете закрасить квадрат 4096x4096, отправив буквально 4 вершины, так как вы хотите получить тот же результат.

Вершинный шейдер:

#version 150

in vec2 vertexPos;
out vec2 interpolatedVertexPos;

void main()
{
  interpolatedVertexPos = vertexPos;
}

Фрагмент шейдера:

#version 1.5

in vec2 interpolatedVertexPos;
out vec4 glFragColor;

void main()
{
  const vec2 center = vec2(2048, 2048);
  float distanceFromCenter = sqrt(pow((interpolatedVertexPos.x-center.x), 2) + pow((interpolatedVertexPos.y-center.y), 2));
  if (distanceFromCenter > 2048){
    discard;
  }
  float Intensitytemp = ((float)((int)distanceFromCenter % 256)) / 255;
  glFragColor = vec4(0, Intensitytemp , 0, 1);
}

Изменить: Я подумал, что вы также можете найти этот ответ полезным: большие проекты OpenGL, VAO-ы и многое другое

Другие вопросы по тегам