根据OpenGL中的混合索引对关节进行着色

时间:2019-05-09 03:08:13

标签: c++ qt animation opengl glsl

我在使用QtWidgets测试程序来显示旧FVF网格数据中的关节时遇到麻烦,突出显示了用户选择的单个关节。我减少了程序,并包括下面的所有部分。

程序将加载一个由顶点和元素数组数据组成的网格,将其渲染为白色线框,但是将指定混合索引的所有顶点着色为红色。混合索引存在于顶点数据中。它们被收集并显示在列表小部件中。单击索引会将索引转发到片段着色器,该片段着色器将为具有匹配混合索引的顶点覆盖默认颜色白色(红色)。

这是一个程序的顶点着色示例,其混合索引为0:

enter image description here

彩色部分作为骨骼的根部是有意义的。

问题在于,这仅在选定的混合索引为0时有效。选择其他索引会导致线框呈现为全白色。预期的行为是网格的其他部分将被染成红色。经确认顶点数据正确无误,因为它可以在其原始应用程序中使用,并且肯定不是错误的来源。

我不确定如何解决这个问题。我几乎没有调试着色器的经验-假设它们是错误的根源。我想知道的一个想法是数据对齐。这是FVF数据在枚举中的布局:

openglwidget.h

#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H

#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLWidget>
#include <QVector3D>

enum VertexLayout {
    XyzOffset = 0,
    NormalOffset = 12,
    TexcoordOffset = 24,
    TangentOffset = 32,
    BinormalOffset = 44,
    BlendIndexOffset0 = 56,
    BlendWeightOffset0 = 57,
    BlendIndexOffset1 = 61,
    BlendIndexWeight1 = 62,
    BlendIndexOffset2 = 66,
    BlendWeightOffset2 = 67,
    Size = 71 // Size in bytes of one raw vertex
};

class OpenGLWidget : public QOpenGLWidget {
    Q_OBJECT
public:
    OpenGLWidget(QWidget* parent=nullptr);
    ~OpenGLWidget() override;
    void initGeometry(const QByteArray& rawVertices, const QByteArray& rawIndices);
    void setIndex(int index);
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int y) override;
signals:
    void readyForData();
private:
    void initShaders();
    float aspect = 1.33f;
    qint32 numIndices = 0;
    int blendIndex = 0;
    QOpenGLShaderProgram program;
    QOpenGLVertexArrayObject vao;
    QOpenGLBuffer arrayBuf;
    QOpenGLBuffer indexBuf;
    QVector3D eye, target, up;
};

#endif // OPENGLWIDGET_H

虽然所有其他属性均为浮点数,但混合索引为字节。我在下面的initGeometry中用GL_UNSIGNED_BYTE指定了它们:

openglwidget.cpp

#include "openglwidget.h"

#include <QOpenGLBuffer>
#include <QOpenGLVersionProfile>
#include <QOpenGLWidget>

OpenGLWidget::OpenGLWidget(QWidget* parent)
    :QOpenGLWidget(parent),
     arrayBuf(QOpenGLBuffer::VertexBuffer),
     indexBuf(QOpenGLBuffer::IndexBuffer),
     up(0.0f, 0.0f, 1.0f)
{
}

OpenGLWidget::~OpenGLWidget()
{
}

void OpenGLWidget::initGeometry(const QByteArray& rawVertices, const QByteArray& rawIndices)
{
    vao.create();
    vao.bind();

    arrayBuf.create();
    indexBuf.create();
    arrayBuf.bind();
    arrayBuf.allocate(rawVertices, rawVertices.size());
    indexBuf.bind();
    indexBuf.allocate(rawIndices, rawIndices.size());

    numIndices = rawIndices.size() / 2; // sizeof(qint16)

    int vertexLocation = program.attributeLocation("a_position");
    program.enableAttributeArray(vertexLocation);
    program.setAttributeBuffer(vertexLocation,
                               GL_FLOAT,
                               VertexLayout::XyzOffset,
                               3,
                               VertexLayout::Size);

    int blend0Location = program.attributeLocation("a_blend0");
    program.enableAttributeArray(blend0Location);
    program.setAttributeBuffer(blend0Location,
                               GL_UNSIGNED_BYTE,
                               VertexLayout::BlendIndexOffset0,
                               1,
                               VertexLayout::Size);

    vao.release();

    update();
}

void OpenGLWidget::setIndex(int index)
{
    if (index != blendIndex) {
        blendIndex = index;
        update();
    }
}

void OpenGLWidget::initializeGL()
{
    QSurfaceFormat format;
    QOpenGLVersionProfile profile(format);
    auto functions = context()->versionFunctions(profile);
    QOpenGLWidget::initializeGL();
    functions->initializeOpenGLFunctions();
    glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    initShaders();
    emit readyForData();
}

void OpenGLWidget::paintGL()
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // simple wireframe
    glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Hard-coded pose
    QMatrix4x4 mv;
    eye = QVector3D(-1.50f, 2.33f, 1.43f);
    target = QVector3D(0.50f, 0.28f, 0.94f);
    mv.lookAt(eye, target, up);
    QMatrix4x4 mp;
    mp.perspective(75.0f, aspect, 1.0f, 200.0f);
    QMatrix4x4 mm;
    mm.translate(0.0f, 0.0f, 0.0f);

    program.setUniformValue("mvp_matrix", mp * mv * mm);
    program.setUniformValue("boneToColor", blendIndex);
    vao.bind();
    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, nullptr);
}

void OpenGLWidget::resizeGL(int w, int h)
{
    aspect = h == 0 ? 1 : w / h;
}

static const char* vertexShaderSource =
    "#version 330 core\n"
    "in vec4 a_position;\n"
    "in int a_blend0;\n"
    "flat out int v_blend0;\n"
    "uniform mat4 mvp_matrix;\n"
    "void main() {\n"
    "gl_Position = mvp_matrix * a_position;\n"
    "v_blend0 = a_blend0;\n"
    "\n}";

static const char* fragmentShaderSource =
    "#version 330 core\n"
    "flat in int v_blend0;\n"
    "out vec4 fragmentColor;\n"
    "uniform int boneToColor;\n"
    "void main() {\n"
    "if (v_blend0 == boneToColor)\n"
    "fragmentColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "else\n"
    "fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
    "\n}";

void OpenGLWidget::initShaders()
{
    if (!program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) {
        qCritical() << "Failed to load vertex shader";
        close();
    }
    if (!program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) {
        qCritical() << "Failed to load fragment shader";
        close();
    }
    if (!program.link()) {
        qCritical() << "Failed to link program";
        close();
    }
    if (!program.bind()) {
        qCritical() << "Failed to bind program";
        close();
    }
}

我知道GLSL会将这种类型转换为int。也许这种转换是麻烦的原因,但是如果是这样,我不确定如何进行测试。着色器中可能还存在其他错误,但是blendIndex == 0的情况有效。

值得注意的是,该网格将三个混合索引和三个混合权重与每个顶点关联。该程序只注意第一个混合索引。对于这个主题,我太新了,无法理解这是否会导致问题。

尽管这个问题很可能是由于我对OpenGL或底层数据缺乏了解,但我也用Qt标记了这个问题,以防OpenGL界面出现皱纹。

以下是运行项目所需的其余文件:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class QListWidget;
class QListWidgetItem;
class OpenGLWidget;

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget* parent=nullptr);
    ~MainWindow();
private:
    void loadGeometry();
    void onIndexSelected(QListWidgetItem* item);
    QListWidget* selector;
    OpenGLWidget* openGLWidget;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

#include <set>

#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QHBoxLayout>
#include <QListWidget>

#include "openglwidget.h"

MainWindow::MainWindow(QWidget* parent)
    :QMainWindow(parent),
     selector(new QListWidget(this)),
     openGLWidget(new OpenGLWidget(this))
{
    selector->setFixedWidth(80);
    auto dummy = new QWidget(this);
    auto hbox = new QHBoxLayout(dummy);
    hbox->addWidget(selector);
    hbox->addWidget(openGLWidget);
    setCentralWidget(dummy);
    setMinimumSize(640, 480);

    connect(openGLWidget, &OpenGLWidget::readyForData,
            this, &MainWindow::loadGeometry);
    connect(selector, &QListWidget::itemClicked,
            this, &MainWindow::onIndexSelected);
}

MainWindow::~MainWindow()
{
}

void MainWindow::onIndexSelected(QListWidgetItem* item)
{
    int index = item->text().toInt();
    openGLWidget->setIndex(index);
}

void MainWindow::loadGeometry()
{
    // Read raw geometry from file:
    QFile inf("boneShaderTestGeometry.dat");
    if (!inf.open(QIODevice::ReadOnly)) {
        qCritical() << "Failed to open geometry file";
        return;
    }
    QDataStream ins(&inf);
    ins.setByteOrder(QDataStream::LittleEndian);

    qint32 numVerts;
    ins >> numVerts;
    QByteArray rawVertices(numVerts * VertexLayout::Size, 0);
    ins.readRawData(rawVertices.data(), rawVertices.size());

    qint32 numIndices;
    ins >> numIndices;
    QByteArray rawIndices(numIndices * 2 /* sizeof(qint16) */, 0);
    ins.readRawData(rawIndices.data(), rawIndices.size());

    // Parse raw vertices for blend indices:
    std::set<char> blendIndices;
    for (int i=0; i<numVerts; ++i) {
        auto offset = VertexLayout::Size * i + VertexLayout::BlendIndexOffset0;
        blendIndices.insert(rawVertices[offset]);
    }
    // Populate selector:
    for (auto blendIndex: blendIndices)
        selector->addItem(QString::number(blendIndex));
    selector->setCurrentRow(0);

    // Forward raw geometry:
    openGLWidget->initGeometry(rawVertices, rawIndices);
}

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

BoneShaderTest.pro

QT       += core gui opengl
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = BoneShaderTest
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++11
SOURCES += \
        main.cpp \
        mainwindow.cpp \
    openglwidget.cpp
HEADERS += \
        mainwindow.h \
    openglwidget.h
LIBS += opengl32.lib
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

The raw mesh data at tinyupload.com

2 个答案:

答案 0 :(得分:1)

我已经浏览了一下,似乎没什么错。 可能可能会引起问题的唯一原因是调用:

program.setAttributeBuffer(blend0Location,
                           GL_UNSIGNED_BYTE,
                           VertexLayout::BlendIndexOffset0,
                           1,
                           VertexLayout::Size);

如果要为混合索引指定无符号整数类型,请确保使用以下格式:glVertexArrayAttrib I Format(而不是glVertexArrayAttribFormat)。但是话又说回来,如果它适用于索引为零的情况,那有点表明设置统一索引值时出了点问题。

当追踪这样的东西时,通常最好挂入OpenGL调试API以使其输出当前错误。最糟糕的是,您总是可以使用旧的glGetError方法,并使用简单的check宏将所有调用包装到GL中,以查看是否发生任何错误。

inline void checkGlError(const char* const file, const char* const op, int line)
{
  switch(glGetError())
  {
  case GL_NO_ERROR:  return;
  case GL_INVALID_ENUM: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_ENUM\n", file, op, line); break;
  case GL_INVALID_VALUE: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_VALUE\n", file, op, line); break;
  case GL_INVALID_OPERATION: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_OPERATION\n", file, op, line); break;
  case GL_INVALID_FRAMEBUFFER_OPERATION: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_FRAMEBUFFER_OPERATION\n", file, op, line); break;
  case GL_OUT_OF_MEMORY: fprintf(stderr, "file: %s\n\t%s: line %d: GL_OUT_OF_MEMORY\n", file, op, line); break;
  default: fprintf(stderr, "file: %s\n\t%s: line %d: unknown Gl error\n", file, op, line); break;
  }
}

#define CHECK_GL_ERROR(X) X; checkGlError(__FILE__, #X, __LINE__);

答案 1 :(得分:0)

robthebloke给出的提示导致了解决方案。有两个更改是必要的。

首先,必须使用glVertexAttribIPointer来正确处理整数类型:

program.setAttributeBuffer(blend0Location,
                           GL_UNSIGNED_BYTE,
                           VertexLayout::BlendIndexOffset0,
                           1,
                           VertexLayout::Size);

被替换为:

#include <QOpenGLFunctions_4_0_Core>

auto fun = new QOpenGLFunctions_4_0_Core();
fun->initializeOpenGLFunctions();
fun->glVertexAttribIPointer(blend0Location, 1, GL_UNSIGNED_BYTE,
                       VertexLayout::Size, reinterpret_cast<const void*>(qintptr(VertexLayout::BlendIndexOffset0)));

其次,有必要更新着色器。顶点着色器:

#version 330 core
in vec4 a_position;
in int a_blend0;
flat out int v_blend0;
uniform mat4 mvp_matrix;
void main() {
    gl_Position = mvp_matrix * a_position;
    v_blend0 = a_blend0;
}

和片段着色器:

#version 330 core
flat in int v_blend0;
out vec4 fragmentColor;
uniform int boneToColor;
void main() {
    if (v_blend0 == boneToColor)
        fragmentColor = vec4(1.0, 0.0, 0.0, 1.0);
    else
        fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
}