resizeGL QOpenGLWidget НЕ является местом для вызова glViewport?

Я экспериментирую с новым классом QOpenGLWidget (обратите внимание, что это не класс QGLWidget).

Я рисую треугольник. У меня есть тривиальный вершинный шейдер, который получает координаты в пространстве отсечения, поэтому никакие матрицы или проекции не задействованы. Одна из вершин имеет координаты -1, -1, 0, 1, а другая имеет координаты 1, 1, 0, 1.

Когда у меня вообще нет вызова glViewport, программа отрисовывается так, как если бы я вызывал glViewport(0, 0, w, h); в моей функции resizeGL, что на самом деле не так. А именно, две вершины треугольника прикреплены к нижнему левому и верхнему правому углам окна независимо от того, как я изменяю размер окна.

Когда я на самом деле добавляю вызов glViewport в моей функции resizeGL, он явно игнорируется - не имеет значения, передаю ли я w/2, h/2 или любое другое значение, рендеринг точно такой же как это было бы, если бы я вызвал glViewport(0, 0, w, h); (например, я ожидал бы, что треугольник появится в нижней левой четверти окна в случае glViewport(0, 0, w/2, h/2);)

Когда я вызываю glViewport(0, 0, width()/2, height()/2) в функции paingGL, рендеринг соответствует ожиданиям — все рисуется в нижней левой четверти окна.

Таким образом, кажется, что glViewport переопределен где-то между resizeGL и paintGL. Что происходит и как это исправить? Должен ли я прибегать к преобразованиям окна просмотра в моей функции paintGL?

Одно из различий между QGLWidget и QOpenGLWidget, перечисленных в документации, заключается в том, что последний выполняет рендеринг в фреймбуфер, а не прямо на экран. Может ли это быть ключом к объяснению?

На всякий случай прилагаю полный код для справки.

//треугольник.h

#ifndef TRIANGLE_H
#define TRIANGLE_H

#include <QOpenGLBuffer>
#include <QOpenGLFunctions>

class Triangle
{
public:
    Triangle();
    void render();
    void create();

private:
    QOpenGLBuffer position_vbo;
    QOpenGLFunctions *glFuncs;
};

#endif // TRIANGLE_H

//треугольник.cpp

#include "triangle.h"

Triangle::Triangle()
    :position_vbo(QOpenGLBuffer::VertexBuffer)
{    

}

void Triangle::create()
{
    glFuncs = QOpenGLContext::currentContext()->functions();
    position_vbo.create();
    float val[] = {
           -1.0f,   -1.0f, 0.0f, 1.0f,
            0.0f, -0.366f, 0.0f, 1.0f,
            1.0f,    1.0f, 0.0f, 1.0f,
            1.0f,    0.0f, 0.0f, 1.0f,
            0.0f,    1.0f, 0.0f, 1.0f,
            0.0f,    0.0f, 1.0f, 1.0f,
        };
    position_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
    position_vbo.bind();
    position_vbo.allocate(val, sizeof(val));
    position_vbo.release();
}

void Triangle::render()
{
    position_vbo.bind();
    glFuncs->glEnableVertexAttribArray(0);
    glFuncs->glEnableVertexAttribArray(1);
    glFuncs->glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glFuncs->glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)(3*4*sizeof(float)));
    glFuncs->glDrawArrays(GL_TRIANGLES, 0, 3);
    glFuncs->glDisableVertexAttribArray(0);
    glFuncs->glDisableVertexAttribArray(1);
    position_vbo.release();
}

//виджет.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>

#include "triangle.h"

class Widget : public QOpenGLWidget
             , protected QOpenGLFunctions
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);    
    ~Widget();

protected:
    virtual void initializeGL() override;
    virtual void paintGL() override;
    virtual void resizeGL(int w, int h) override;
private:
    QOpenGLShaderProgram* program;
    Triangle t;
};

#endif // WIDGET_H

//виджет.cpp

#include "widget.h"
#include <exception>
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
}

Widget::~Widget()
{

}

void Widget::initializeGL()
{
    initializeOpenGLFunctions();
    program = new QOpenGLShaderProgram(this);
    if(!program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertexshader.vert"))
    {
       throw std::exception(("Vertex Shader compilation error: " + program->log()).toLocal8Bit().constData());
    }
    if(!program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragmentshader.frag"))
    {
       throw std::exception(("Fragment Shader compilation error: " + program->log()).toLocal8Bit().constData());
    }
    if(!program->link())
    {
       throw std::exception(("Program Link error: " + program->log()).toLocal8Bit().constData());
    }

    t.create();
}


void Widget::paintGL()
{
    glClearColor(0.f, 0.15f, 0.05f, 0.f);
    glClear(GL_COLOR_BUFFER_BIT);
    //glViewport(0, 0, width()/2, height()/2); //works!!
    program->bind();
    t.render();
    program->release();
}

void Widget::resizeGL(int w, int h)
{
    glViewport(0, 0, w/2, h/2); //doesn't work
}

//main.cpp

#include "widget.h"

#include <exception>

#include <QApplication>
#include <QMessageBox>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    try
    {
        Widget w;
        w.show();
        return a.exec();
    }
    catch(std::exception const & e)
    {
        QMessageBox::warning(nullptr, "Error", e.what());
    }
}

//вершинный шейдер

#version 330
layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;

smooth out vec4 theColor;

void main()
{
    gl_Position = position;
    theColor = color;
}

//фрагментный шейдер

#version 330
out vec4 fragColor;
smooth in vec4 theColor;
void main()
{
    fragColor = theColor;
}

person Armen Tsirunyan    schedule 10.10.2015    source источник
comment
Насколько мне известно, QOpenGLWidget привязывает внутренний фреймбуфер (id=1), а также устанавливает область просмотра на размер фреймбуфера перед вызовом paintGL.   -  person BDL    schedule 11.10.2015


Ответы (1)


Таким образом, кажется, что glViewport переопределен где-то между resizeGL и paintGL. Что происходит и как это исправить? Должен ли я прибегать к преобразованиям окна просмотра в моей функции paintGL?

Qt5 может использовать OpenGL для собственного рисования. Также содержимое виджетов, являющихся дочерними для QOpenGLWindow, отображается в FBO. Это означает, что между вашим кодом и тем, что делает Qt, делается много вызовов glViewport.

Когда я фактически добавляю вызов glViewport в свою функцию resizeGL, он явно игнорируется (…)

да. И чего именно вы ожидаете? Единственное допустимое место для вызова glViewport для надежности программы OpenGL — это код отрисовки. Каждый туториал, который помещает glViewport в обработчик изменения размера окна, неверен и должен быть сожжен.

Когда я вызываю glViewport(0, 0, width()/2, height()/2) в функции paingGL, рендеринг соответствует ожиданиям.

Да, именно так вы должны использовать его.

person datenwolf    schedule 11.10.2015
comment
Что ж, я только рад, что вызов glViewport в paintGL — это хорошо. Я подумал, что не стоит делать это на каждом кадре, потому что, похоже, все уроки, которые я когда-либо видел, должны быть сожжены. То есть вы хотите сказать, что матрицу перспективы тоже нужно настроить в PaintGL? - person Armen Tsirunyan; 11.10.2015
comment
@ArmenTsirunyan: Да, это действительно так, вы всегда должны выполнять полную настройку состояния для каждого кадра. Вы должны понимать, что настройка самой матрицы или выполнение каких-либо манипуляций с ней не требует каких-либо затрат на GPU (просто потребляет немного процессорного времени). А когда дело доходит до рисования, матрица все равно загружается в юниформ-регистры GPU. - person datenwolf; 11.10.2015
comment
@ArmenTsirunyan Я согласен с datenwolf: вы должны поделиться контекстом с самим Qt (если вы не создадите свой собственный общий контекст). Лично я настраиваю все (область просмотра, матрицы, параметры смешивания, параметры текстур и т. д.) в PaintGL без потери производительности. Помните, что OpenGL — это конечный автомат, и что разработчики редко восстанавливают состояние, поскольку они нашли его в точке входа своих функций! - person Bertrand; 02.04.2016
comment
На самом деле восстановить государство в целом очень сложно. Вы не знаете, что было включено, привязано и т. д. другим кодом, особенно если это связано с некоторыми расширениями, о которых вы даже не знаете. Таким образом, в целом гораздо надежнее использовать свой собственный контекст, которым вы полностью управляете, вместо того, чтобы надеяться, что другой код не нарушил контекст по умолчанию. - person Ruslan; 04.09.2016