diff options
-rw-r--r-- | examples/datavisualization/volumetric/main.cpp | 41 | ||||
-rw-r--r-- | examples/datavisualization/volumetric/volumetric.cpp | 44 | ||||
-rw-r--r-- | examples/datavisualization/volumetric/volumetric.h | 4 | ||||
-rw-r--r-- | src/datavisualization/data/customrenderitem.cpp | 8 | ||||
-rw-r--r-- | src/datavisualization/data/customrenderitem_p.h | 12 | ||||
-rw-r--r-- | src/datavisualization/data/qcustom3dvolume.cpp | 251 | ||||
-rw-r--r-- | src/datavisualization/data/qcustom3dvolume.h | 10 | ||||
-rw-r--r-- | src/datavisualization/data/qcustom3dvolume_p.h | 10 | ||||
-rw-r--r-- | src/datavisualization/engine/abstract3drenderer.cpp | 10 | ||||
-rw-r--r-- | src/datavisualization/engine/shaders/texture3d.frag | 14 | ||||
-rw-r--r-- | src/datavisualization/engine/shaders/texture3dslice.frag | 54 | ||||
-rw-r--r-- | src/datavisualization/utils/shaderhelper.cpp | 16 | ||||
-rw-r--r-- | src/datavisualization/utils/shaderhelper_p.h | 4 | ||||
-rw-r--r-- | tests/volumetrictest/volumetrictest.cpp | 4 |
14 files changed, 372 insertions, 110 deletions
diff --git a/examples/datavisualization/volumetric/main.cpp b/examples/datavisualization/volumetric/main.cpp index 5a90070e..84062969 100644 --- a/examples/datavisualization/volumetric/main.cpp +++ b/examples/datavisualization/volumetric/main.cpp @@ -36,16 +36,18 @@ int main(int argc, char **argv) QWidget *container = QWidget::createWindowContainer(graph); QSize screenSize = graph->screen()->size(); - container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5)); + container->setMinimumSize(QSize(screenSize.width() / 3, screenSize.height() / 3)); container->setMaximumSize(screenSize); container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); container->setFocusPolicy(Qt::StrongFocus); - QWidget *widget = new QWidget; + QWidget *widget = new QWidget(); QHBoxLayout *hLayout = new QHBoxLayout(widget); QVBoxLayout *vLayout = new QVBoxLayout(); + QVBoxLayout *vLayout2 = new QVBoxLayout(); hLayout->addWidget(container, 1); hLayout->addLayout(vLayout); + hLayout->addLayout(vLayout2); widget->setWindowTitle(QStringLiteral("Volumetric Object Example")); @@ -122,10 +124,17 @@ int main(int argc, char **argv) sliceImageYLabel->setScaledContents(true); sliceImageZLabel->setScaledContents(true); - vLayout->addWidget(fpsCheckBox); - vLayout->addWidget(fpsLabel); - vLayout->addWidget(textureDetailGroupBox); - vLayout->addWidget(colorTableCheckBox); + QSlider *alphaMultiplierSlider = new QSlider(Qt::Horizontal, widget); + alphaMultiplierSlider->setMinimum(0); + alphaMultiplierSlider->setMaximum(139); + alphaMultiplierSlider->setValue(100); + alphaMultiplierSlider->setEnabled(true); + QLabel *alphaMultiplierLabel = new QLabel(QStringLiteral("Alpha multiplier: 1.0")); + + QCheckBox *preserveOpacityCheckBox = new QCheckBox(widget); + preserveOpacityCheckBox->setText(QStringLiteral("Preserve opacity")); + preserveOpacityCheckBox->setChecked(true); + vLayout->addWidget(sliceXCheckBox); vLayout->addWidget(sliceXSlider); vLayout->addWidget(sliceImageXLabel); @@ -135,6 +144,13 @@ int main(int argc, char **argv) vLayout->addWidget(sliceZCheckBox); vLayout->addWidget(sliceZSlider); vLayout->addWidget(sliceImageZLabel, 1, Qt::AlignTop); + vLayout2->addWidget(fpsCheckBox); + vLayout2->addWidget(fpsLabel); + vLayout2->addWidget(textureDetailGroupBox); + vLayout2->addWidget(colorTableCheckBox); + vLayout2->addWidget(alphaMultiplierLabel); + vLayout2->addWidget(alphaMultiplierSlider); + vLayout2->addWidget(preserveOpacityCheckBox, 1, Qt::AlignTop); VolumetricModifier *modifier = new VolumetricModifier(graph); modifier->setFpsLabel(fpsLabel); @@ -142,6 +158,7 @@ int main(int argc, char **argv) modifier->setHighDetailRB(highDetailRB); modifier->setSliceSliders(sliceXSlider, sliceYSlider, sliceZSlider); modifier->setSliceLabels(sliceImageXLabel, sliceImageYLabel, sliceImageZLabel); + modifier->setAlphaMultiplierLabel(alphaMultiplierLabel); QObject::connect(fpsCheckBox, &QCheckBox::stateChanged, modifier, &VolumetricModifier::setFpsMeasurement); @@ -157,14 +174,18 @@ int main(int argc, char **argv) &VolumetricModifier::adjustSliceY); QObject::connect(sliceZSlider, &QSlider::valueChanged, modifier, &VolumetricModifier::adjustSliceZ); - QObject::connect(lowDetailRB, &QRadioButton::toggled, modifier, + QObject::connect(lowDetailRB, &QRadioButton::toggled, modifier, &VolumetricModifier::toggleLowDetail); - QObject::connect(mediumDetailRB, &QRadioButton::toggled, modifier, + QObject::connect(mediumDetailRB, &QRadioButton::toggled, modifier, &VolumetricModifier::toggleMediumDetail); - QObject::connect(highDetailRB, &QRadioButton::toggled, modifier, + QObject::connect(highDetailRB, &QRadioButton::toggled, modifier, &VolumetricModifier::toggleHighDetail); - QObject::connect(colorTableCheckBox, &QCheckBox::stateChanged, modifier, + QObject::connect(colorTableCheckBox, &QCheckBox::stateChanged, modifier, &VolumetricModifier::changeColorTable); + QObject::connect(preserveOpacityCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::setPreserveOpacity); + QObject::connect(alphaMultiplierSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustAlphaMultiplier); widget->show(); return app.exec(); diff --git a/examples/datavisualization/volumetric/volumetric.cpp b/examples/datavisualization/volumetric/volumetric.cpp index a553ccf8..56f02dcb 100644 --- a/examples/datavisualization/volumetric/volumetric.cpp +++ b/examples/datavisualization/volumetric/volumetric.cpp @@ -80,7 +80,7 @@ VolumetricModifier::VolumetricModifier(Q3DScatter *scatter) // Both tables have a fully transparent colors to fill outer portions of the volume. // The primary color table. - // The first visible layer, red, is somewhat transparent. Rest of to colors are opaque. + // The top two layers are transparent. m_colorTable1.resize(colorTableSize); m_colorTable2.resize(colorTableSize); @@ -88,9 +88,9 @@ VolumetricModifier::VolumetricModifier(Q3DScatter *scatter) if (i < cutOffColorIndex) m_colorTable1[i] = qRgba(0, 0, 0, 0); else if (i < 60) - m_colorTable1[i] = qRgba((i * 2) + 120, 0, 0, 20); + m_colorTable1[i] = qRgba((i * 2) + 120, 0, 0, 15); else if (i < 120) - m_colorTable1[i] = qRgba(0, ((i - 60) * 2) + 120, 0, 255); + m_colorTable1[i] = qRgba(0, ((i - 60) * 2) + 120, 0, 50); else if (i < 180) m_colorTable1[i] = qRgba(0, 0, ((i - 120) * 2) + 120, 255); else @@ -98,14 +98,15 @@ VolumetricModifier::VolumetricModifier(Q3DScatter *scatter) } // The alternate color table. - // The first visible layer is a thin single color, and rest of the volume uses a smooth gradient. + // The first visible layer is a thin opaque color, and rest of the volume uses a smooth + // transparent gradient. for (int i = 1; i < colorTableSize; i++) { if (i < cutOffColorIndex) m_colorTable2[i] = qRgba(0, 0, 0, 0); else if (i < cutOffColorIndex + 4) m_colorTable2[i] = qRgba(75, 150, 0, 255); else - m_colorTable2[i] = qRgba(i, 0, 255 - i, 255); + m_colorTable2[i] = qRgba(i, 0, 255 - i, i); } m_volumeItem->setColorTable(m_colorTable1); @@ -167,6 +168,11 @@ void VolumetricModifier::setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel * adjustSliceZ(m_sliceSliderZ->value()); } +void VolumetricModifier::setAlphaMultiplierLabel(QLabel *label) +{ + m_alphaMultiplierLabel = label; +} + void VolumetricModifier::sliceX(int enabled) { if (m_volumeItem) @@ -322,6 +328,34 @@ void VolumetricModifier::changeColorTable(int enabled) adjustSliceZ(m_sliceSliderZ->value()); } +void VolumetricModifier::setPreserveOpacity(bool enabled) +{ + m_volumeItem->setPreserveOpacity(enabled); + + // Rerender image labels + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); +} + +void VolumetricModifier::adjustAlphaMultiplier(int value) +{ + float mult; + if (value > 100) + mult = float(value - 99) / 2.0f; + else + mult = float(value) / float(500 - value * 4); + m_volumeItem->setAlphaMultiplier(mult); + QString labelFormat = QStringLiteral("Alpha multiplier: %1"); + m_alphaMultiplierLabel->setText(labelFormat.arg( + QString::number(m_volumeItem->alphaMultiplier(), 'f', 3))); + + // Rerender image labels + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); +} + int VolumetricModifier::createVolume(int textureSize, int startIndex, int count, QVector<uchar> *textureData) { diff --git a/examples/datavisualization/volumetric/volumetric.h b/examples/datavisualization/volumetric/volumetric.h index eb8a4172..497506ad 100644 --- a/examples/datavisualization/volumetric/volumetric.h +++ b/examples/datavisualization/volumetric/volumetric.h @@ -41,6 +41,7 @@ public: void setMediumDetailRB(QRadioButton *button); void setHighDetailRB(QRadioButton *button); void setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel); + void setAlphaMultiplierLabel(QLabel *label); public slots: void sliceX(int enabled); @@ -58,6 +59,8 @@ public slots: void setFpsMeasurement(bool enabled); void setSliceSliders(QSlider *sliderX, QSlider *sliderY, QSlider *sliderZ); void changeColorTable(int enabled); + void setPreserveOpacity(bool enabled); + void adjustAlphaMultiplier(int value); private: @@ -86,6 +89,7 @@ private: QLabel *m_sliceLabelX; QLabel *m_sliceLabelY; QLabel *m_sliceLabelZ; + QLabel *m_alphaMultiplierLabel; }; #endif diff --git a/src/datavisualization/data/customrenderitem.cpp b/src/datavisualization/data/customrenderitem.cpp index 3eb68845..c316fd38 100644 --- a/src/datavisualization/data/customrenderitem.cpp +++ b/src/datavisualization/data/customrenderitem.cpp @@ -36,7 +36,13 @@ CustomRenderItem::CustomRenderItem() m_textureWidth(0), m_textureHeight(0), m_textureDepth(0), - m_isVolume(false) + m_isVolume(false), + m_textureFormat(QImage::Format_ARGB32), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true) { } diff --git a/src/datavisualization/data/customrenderitem_p.h b/src/datavisualization/data/customrenderitem_p.h index e21b6f39..5428ce43 100644 --- a/src/datavisualization/data/customrenderitem_p.h +++ b/src/datavisualization/data/customrenderitem_p.h @@ -89,9 +89,13 @@ public: inline void setSliceIndexX(int index) { m_sliceIndexX = index; } inline void setSliceIndexY(int index) { m_sliceIndexY = index; } inline void setSliceIndexZ(int index) { m_sliceIndexZ = index; } - int sliceIndexX() const { return m_sliceIndexX; } - int sliceIndexY() const { return m_sliceIndexY; } - int sliceIndexZ() const { return m_sliceIndexZ; } + inline int sliceIndexX() const { return m_sliceIndexX; } + inline int sliceIndexY() const { return m_sliceIndexY; } + inline int sliceIndexZ() const { return m_sliceIndexZ; } + inline void setAlphaMultiplier(float mult) { m_alphaMultiplier = mult; } + inline float alphaMultiplier() const { return m_alphaMultiplier; } + inline void setPreserveOpacity(bool enable) { m_preserveOpacity = enable; } + inline bool preserveOpacity() const { return m_preserveOpacity; } private: Q_DISABLE_COPY(CustomRenderItem) @@ -120,6 +124,8 @@ private: int m_sliceIndexX; int m_sliceIndexY; int m_sliceIndexZ; + float m_alphaMultiplier; + bool m_preserveOpacity; }; typedef QHash<QCustom3DItem *, CustomRenderItem *> CustomRenderItemArray; diff --git a/src/datavisualization/data/qcustom3dvolume.cpp b/src/datavisualization/data/qcustom3dvolume.cpp index cab79ac0..c1a77dba 100644 --- a/src/datavisualization/data/qcustom3dvolume.cpp +++ b/src/datavisualization/data/qcustom3dvolume.cpp @@ -112,6 +112,30 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty real Custom3DVolume::alphaMultiplier + * + * The alpha value of every texel of the volume texture is multiplied with this value at + * the render time. This can be used to introduce uniform transparency to the volume. + * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are + * affected, and fully opaque texels are not affected. + * The value must not be negative. + * Defaults to \c{1.0}. + * + * \sa preserveOpacity + */ + +/*! + * \qmlproperty bool Custom3DVolume::preserveOpacity + * + * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have + * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all + * texels. + * Defaults to \c{true}. + * + * \sa alphaMultiplier + */ + +/*! * Constructs QCustom3DVolume with given \a parent. */ QCustom3DVolume::QCustom3DVolume(QObject *parent) : @@ -578,84 +602,72 @@ QImage::Format QCustom3DVolume::textureFormat() const } /*! - * Renders the slice specified by \a index along \a axis into an image. - * The texture format of this object is used. + * \property QCustom3DVolume::alphaMultiplier * - * \return the rendered image of the slice, or a null image if invalid index is specified. + * The alpha value of every texel of the volume texture is multiplied with this value at + * the render time. This can be used to introduce uniform transparency to the volume. + * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are + * affected, and fully opaque texels are not affected. + * The value must not be negative. + * Defaults to \c{1.0f}. * - * \sa textureFormat + * \sa preserveOpacity, textureData */ -QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index) +void QCustom3DVolume::setAlphaMultiplier(float mult) { - if (index < 0) - return QImage(); - - int x; - int y; - if (axis == Qt::XAxis) { - if (index >= textureWidth()) - return QImage(); - x = textureDepth(); - y = textureHeight(); - } else if (axis == Qt::YAxis) { - if (index >= textureHeight()) - return QImage(); - x = textureWidth(); - y = textureDepth(); + if (mult >= 0.0f) { + if (dptr()->m_alphaMultiplier != mult) { + dptr()->m_alphaMultiplier = mult; + dptr()->m_dirtyBitsVolume.alphaDirty = true; + emit alphaMultiplierChanged(mult); + emit dptr()->needUpdate(); + } } else { - if (index >= textureDepth()) - return QImage(); - x = textureWidth(); - y = textureHeight(); + qWarning() << __FUNCTION__ << "Attempted to set negative multiplier."; } +} - int padding = 0; - int pixelWidth = 4; - if (textureFormat() == QImage::Format_Indexed8) { - padding = x % 4; - pixelWidth = 1; - } - QVector<uchar> data((x + padding) * y * pixelWidth); - int frameSize = textureDataWidth() * textureHeight(); +float QCustom3DVolume::alphaMultiplier() const +{ + return dptrc()->m_alphaMultiplier; +} - int dataIndex = 0; - if (axis == Qt::XAxis) { - for (int i = 0; i < y; i++) { - const uchar *p = textureData()->constData() - + (index * pixelWidth) + (textureDataWidth() * i); - for (int j = 0; j < x; j++) { - data[dataIndex++] = *p; - for (int k = 1; k < pixelWidth; k++) - data[dataIndex++] = *(p + k); - p += frameSize; - } - } - } else if (axis == Qt::YAxis) { - for (int i = 0; i < y; i++) { - const uchar *p = textureData()->constData() + (index * textureDataWidth()) - + (frameSize * i); - for (int j = 0; j < (x * pixelWidth); j++) { - data[dataIndex++] = *p; - p++; - } - } - } else { - for (int i = 0; i < y; i++) { - const uchar *p = textureData()->constData() + (index * frameSize) - + (textureDataWidth() * i); - for (int j = 0; j < (x * pixelWidth); j++) { - data[dataIndex++] = *p; - p++; - } - } +/*! + * \property QCustom3DVolume::preserveOpacity + * + * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have + * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all + * texels. + * Defaults to \c{true}. + * + * \sa alphaMultiplier + */ +void QCustom3DVolume::setPreserveOpacity(bool enable) +{ + if (dptr()->m_preserveOpacity != enable) { + dptr()->m_preserveOpacity = enable; + dptr()->m_dirtyBitsVolume.alphaDirty = true; + emit preserveOpacityChanged(enable); + emit dptr()->needUpdate(); } +} - QImage image(data.constData(), x, y, x * pixelWidth, textureFormat()); - image.bits(); // Call bits() to detach the new image from local data - if (textureFormat() == QImage::Format_Indexed8) - image.setColorTable(colorTable()); +bool QCustom3DVolume::preserveOpacity() const +{ + return dptrc()->m_preserveOpacity; +} - return image; +/*! + * Renders the slice specified by \a index along \a axis into an image. + * The texture format of this object is used. + * + * \return the rendered image of the slice, or a null image if invalid index is specified. + * + * \sa textureFormat + */ +QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index) +{ + return dptr()->renderSlice(axis, index); } /*! @@ -683,7 +695,9 @@ QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) : m_textureHeight(0), m_textureDepth(0), m_textureFormat(QImage::Format_ARGB32), - m_textureData(0) + m_textureData(0), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true) { m_isVolumeItem = true; m_meshFile = QStringLiteral(":/defaultMeshes/barFull"); @@ -705,7 +719,9 @@ QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector m_textureDepth(textureDepth), m_textureFormat(textureFormat), m_colorTable(colorTable), - m_textureData(textureData) + m_textureData(textureData), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true) { m_isVolumeItem = true; m_shadowCasting = false; @@ -738,6 +754,103 @@ void QCustom3DVolumePrivate::resetDirtyBits() m_dirtyBitsVolume.textureFormatDirty = false; } +QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index) +{ + if (index < 0) + return QImage(); + + int x; + int y; + if (axis == Qt::XAxis) { + if (index >= m_textureWidth) + return QImage(); + x = m_textureDepth; + y = m_textureHeight; + } else if (axis == Qt::YAxis) { + if (index >= m_textureHeight) + return QImage(); + x = m_textureWidth; + y = m_textureDepth; + } else { + if (index >= m_textureDepth) + return QImage(); + x = m_textureWidth; + y = m_textureHeight; + } + + int padding = 0; + int pixelWidth = 4; + int dataWidth = qptr()->textureDataWidth(); + if (m_textureFormat == QImage::Format_Indexed8) { + padding = x % 4; + pixelWidth = 1; + } + QVector<uchar> data((x + padding) * y * pixelWidth); + int frameSize = qptr()->textureDataWidth() * m_textureHeight; + + int dataIndex = 0; + if (axis == Qt::XAxis) { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + + (index * pixelWidth) + (dataWidth * i); + for (int j = 0; j < x; j++) { + for (int k = 0; k < pixelWidth; k++) + data[dataIndex++] = *(p + k); + p += frameSize; + } + } + } else if (axis == Qt::YAxis) { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + (index * dataWidth) + + (frameSize * i); + for (int j = 0; j < (x * pixelWidth); j++) { + data[dataIndex++] = *p; + p++; + } + } + } else { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i); + for (int j = 0; j < (x * pixelWidth); j++) { + data[dataIndex++] = *p; + p++; + } + } + } + + if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) { + for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth) + data[i] = static_cast<uchar>(multipliedAlphaValue(data.at(i))); + } + + QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat); + image.bits(); // Call bits() to detach the new image from local data + if (m_textureFormat == QImage::Format_Indexed8) { + QVector<QRgb> colorTable = m_colorTable; + if (m_alphaMultiplier != 1.0f) { + for (int i = 0; i < colorTable.size(); i++) { + QRgb curCol = colorTable.at(i); + int alpha = multipliedAlphaValue(qAlpha(curCol)); + if (alpha != qAlpha(curCol)) + colorTable[i] = qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), alpha); + } + } + image.setColorTable(colorTable); + } + + return image; +} + +int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha) +{ + int modifiedAlpha = alpha; + if (!m_preserveOpacity || alpha != 255) { + modifiedAlpha = int(m_alphaMultiplier * float(alpha)); + modifiedAlpha = qMin(modifiedAlpha, 255); + } + return modifiedAlpha; +} + QCustom3DVolume *QCustom3DVolumePrivate::qptr() { return static_cast<QCustom3DVolume *>(q_ptr); diff --git a/src/datavisualization/data/qcustom3dvolume.h b/src/datavisualization/data/qcustom3dvolume.h index 00733d17..2f95fa5d 100644 --- a/src/datavisualization/data/qcustom3dvolume.h +++ b/src/datavisualization/data/qcustom3dvolume.h @@ -40,7 +40,8 @@ class QT_DATAVISUALIZATION_EXPORT QCustom3DVolume : public QCustom3DItem Q_PROPERTY(QVector<QRgb> colorTable READ colorTable WRITE setColorTable NOTIFY colorTableChanged) Q_PROPERTY(QVector<uchar> *textureData READ textureData WRITE setTextureData NOTIFY textureDataChanged) Q_PROPERTY(QImage::Format textureFormat READ textureFormat WRITE setTextureFormat NOTIFY textureFormatChanged) - + Q_PROPERTY(float alphaMultiplier READ alphaMultiplier WRITE setAlphaMultiplier NOTIFY alphaMultiplierChanged) + Q_PROPERTY(bool preserveOpacity READ preserveOpacity WRITE setPreserveOpacity NOTIFY preserveOpacityChanged) public: explicit QCustom3DVolume(QObject *parent = 0); @@ -80,6 +81,11 @@ public: void setTextureFormat(QImage::Format format); QImage::Format textureFormat() const; + void setAlphaMultiplier(float mult); + float alphaMultiplier() const; + void setPreserveOpacity(bool enable); + bool preserveOpacity() const; + QImage renderSlice(Qt::Axis axis, int index); signals: @@ -92,6 +98,8 @@ signals: void colorTableChanged(); void textureDataChanged(QVector<uchar> *data); void textureFormatChanged(QImage::Format format); + void alphaMultiplierChanged(float mult); + void preserveOpacityChanged(bool enabled); protected: QCustom3DVolumePrivate *dptr(); diff --git a/src/datavisualization/data/qcustom3dvolume_p.h b/src/datavisualization/data/qcustom3dvolume_p.h index 69dd1eb2..b83e27fb 100644 --- a/src/datavisualization/data/qcustom3dvolume_p.h +++ b/src/datavisualization/data/qcustom3dvolume_p.h @@ -40,13 +40,15 @@ struct QCustomVolumeDirtyBitField { bool colorTableDirty : 1; bool textureDataDirty : 1; bool textureFormatDirty : 1; + bool alphaDirty : 1; QCustomVolumeDirtyBitField() : textureDimensionsDirty(false), sliceIndicesDirty(false), colorTableDirty(false), textureDataDirty(false), - textureFormatDirty(false) + textureFormatDirty(false), + alphaDirty(false) { } }; @@ -64,6 +66,7 @@ public: virtual ~QCustom3DVolumePrivate(); void resetDirtyBits(); + QImage renderSlice(Qt::Axis axis, int index); QCustom3DVolume *qptr(); @@ -79,9 +82,14 @@ public: QVector<QRgb> m_colorTable; QVector<uchar> *m_textureData; + float m_alphaMultiplier; + bool m_preserveOpacity; + QCustomVolumeDirtyBitField m_dirtyBitsVolume; private: + int multipliedAlphaValue(int alpha); + friend class QCustom3DVolume; }; diff --git a/src/datavisualization/engine/abstract3drenderer.cpp b/src/datavisualization/engine/abstract3drenderer.cpp index 8eb1d2ce..37688beb 100644 --- a/src/datavisualization/engine/abstract3drenderer.cpp +++ b/src/datavisualization/engine/abstract3drenderer.cpp @@ -967,6 +967,8 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) newItem->setSliceIndexX(volumeItem->sliceIndexX()); newItem->setSliceIndexY(volumeItem->sliceIndexY()); newItem->setSliceIndexZ(volumeItem->sliceIndexZ()); + newItem->setAlphaMultiplier(volumeItem->alphaMultiplier()); + newItem->setPreserveOpacity(volumeItem->preserveOpacity()); #endif } newItem->setScaling(scaling); @@ -1114,6 +1116,11 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) renderItem->setSliceIndexZ(volumeItem->sliceIndexZ()); volumeItem->dptr()->m_dirtyBitsVolume.sliceIndicesDirty = false; } + if (volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty) { + renderItem->setAlphaMultiplier(volumeItem->alphaMultiplier()); + renderItem->setPreserveOpacity(volumeItem->preserveOpacity()); + volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty = false; + } #endif } } @@ -1292,6 +1299,9 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state, item->colorTable().constData(), 256); } shader->setUniformValue(shader->color8Bit(), color8Bit); + shader->setUniformValue(shader->alphaMultiplier(), item->alphaMultiplier()); + shader->setUniformValue(shader->preserveOpacity(), + item->preserveOpacity() ? 1 : 0); if (shader == volumeSliceShader) { QVector3D slices((float(item->sliceIndexX()) + 0.5f) / float(item->textureWidth()) * 2.0 - 1.0, diff --git a/src/datavisualization/engine/shaders/texture3d.frag b/src/datavisualization/engine/shaders/texture3d.frag index 90876596..1192ae85 100644 --- a/src/datavisualization/engine/shaders/texture3d.frag +++ b/src/datavisualization/engine/shaders/texture3d.frag @@ -8,6 +8,8 @@ uniform highp vec4 colorIndex[256]; uniform highp int color8Bit; uniform highp vec3 textureDimensions; uniform highp int sampleCount; // This is the maximum sample count +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; const highp float alphaThreshold = 0.0001; @@ -69,7 +71,7 @@ void main() { // Adjust alpha multiplier according to the step size to get uniform alpha effect // regardless of the ray angle. - highp float alphaMultiplier = stepSize / (1.0 / sampleCount); + highp float totalAlphaMultiplier = (stepSize / (1.0 / sampleCount)) * alphaMultiplier; highp vec4 curColor = vec4(0, 0, 0, 0); highp vec3 curRgb = vec3(0, 0, 0); @@ -81,10 +83,12 @@ void main() { if (color8Bit != 0) curColor = colorIndex[int(curColor.r * 255.0)]; - if (curColor.a == 1.0) + // Unless we have explicit alpha multiplier, we want to preserve opacity anyway + if (curColor.a == 1.0 && (preserveOpacity != 0 || alphaMultiplier == 1.0)) curAlpha = 1.0; else - curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curAlpha = clamp(curColor.a * totalAlphaMultiplier, 0.0, 1.0); + if (curAlpha > alphaThreshold) { curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); destColor.rgb += curRgb; @@ -96,6 +100,10 @@ void main() { } } + // Brighten up the final color if there is some transparency left + if (totalAlpha > alphaThreshold && totalAlpha < 1.0) + destColor *= 1.0 / totalAlpha; + destColor.a = totalAlpha; gl_FragColor = clamp(destColor, 0.0, 1.0); } diff --git a/src/datavisualization/engine/shaders/texture3dslice.frag b/src/datavisualization/engine/shaders/texture3dslice.frag index 8870b26d..3d4c9030 100644 --- a/src/datavisualization/engine/shaders/texture3dslice.frag +++ b/src/datavisualization/engine/shaders/texture3dslice.frag @@ -7,6 +7,8 @@ uniform highp vec3 cameraPositionRelativeToModel; uniform highp vec3 volumeSliceIndices; uniform highp vec4 colorIndex[256]; uniform highp int color8Bit; +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; const highp vec3 xPlaneNormal = vec3(1.0, 0, 0); const highp vec3 yPlaneNormal = vec3(0, 1.0, 0); @@ -76,47 +78,67 @@ void main() { } highp vec4 destColor = vec4(0.0, 0.0, 0.0, 0.0); + highp vec4 curColor = vec4(0.0, 0.0, 0.0, 0.0); highp float totalAlpha = 0.0; highp vec3 curRgb = vec3(0, 0, 0); + highp float curAlpha = 0.0; // Convert intersection to texture coords if (firstD <= tFar) { highp vec3 firstTex = rayStart + rayDir * firstD; firstTex = 0.5 * (firstTex + 1.0); - highp vec4 firstColor = texture3D(textureSampler, firstTex); + curColor = texture3D(textureSampler, firstTex); if (color8Bit != 0) - firstColor = colorIndex[int(firstColor.r * 255.0)]; + curColor = colorIndex[int(curColor.r * 255.0)]; - if (firstColor.a > alphaThreshold) { - destColor.rgb = firstColor.rgb * firstColor.a; - totalAlpha = firstColor.a; + if (curColor.a > alphaThreshold) { + curAlpha = curColor.a; + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + destColor.rgb = curColor.rgb * curAlpha; + totalAlpha = curAlpha; } if (secondD <= tFar && totalAlpha < 1.0) { highp vec3 secondTex = rayStart + rayDir * secondD; secondTex = 0.5 * (secondTex + 1.0); - highp vec4 secondColor = texture3D(textureSampler, secondTex); + curColor = texture3D(textureSampler, secondTex); if (color8Bit != 0) - secondColor = colorIndex[int(secondColor.r * 255.0)]; - if (secondColor.a > alphaThreshold) { - curRgb = secondColor.rgb * secondColor.a * (1.0 - totalAlpha); + curColor = colorIndex[int(curColor.r * 255.0)]; + if (curColor.a > alphaThreshold) { + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); destColor.rgb += curRgb; - totalAlpha += secondColor.a; + totalAlpha += curAlpha; } if (thirdD <= tFar && totalAlpha < 1.0) { highp vec3 thirdTex = rayStart + rayDir * thirdD; thirdTex = 0.5 * (thirdTex + 1.0); - highp vec4 thirdColor = texture3D(textureSampler, thirdTex); - if (color8Bit != 0) - thirdColor = colorIndex[int(thirdColor.r * 255.0)]; - if (thirdColor.a > alphaThreshold) { - curRgb = thirdColor.rgb * thirdColor.a * (1.0 - totalAlpha); + curColor = texture3D(textureSampler, thirdTex); + if (curColor.a > alphaThreshold) { + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); destColor.rgb += curRgb; - totalAlpha += thirdColor.a; + totalAlpha += curAlpha; } } } } + + // Brighten up the final color if there is some transparency left + if (totalAlpha > alphaThreshold && totalAlpha < 1.0) + destColor *= 1.0 / totalAlpha; + destColor.a = totalAlpha; gl_FragColor = clamp(destColor, 0.0, 1.0); } diff --git a/src/datavisualization/utils/shaderhelper.cpp b/src/datavisualization/utils/shaderhelper.cpp index 3361638a..9d1ad0d9 100644 --- a/src/datavisualization/utils/shaderhelper.cpp +++ b/src/datavisualization/utils/shaderhelper.cpp @@ -99,6 +99,8 @@ void ShaderHelper::initialize() m_color8BitUniform = m_program->uniformLocation("color8Bit"); m_textureDimensionsUniform = m_program->uniformLocation("textureDimensions"); m_sampleCountUniform = m_program->uniformLocation("sampleCount"); + m_alphaMultiplierUniform = m_program->uniformLocation("alphaMultiplier"); + m_preserveOpacityUniform = m_program->uniformLocation("preserveOpacity"); m_initialized = true; } @@ -308,6 +310,20 @@ GLuint ShaderHelper::sampleCount() return m_sampleCountUniform; } +GLuint ShaderHelper::alphaMultiplier() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_alphaMultiplierUniform; +} + +GLuint ShaderHelper::preserveOpacity() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_preserveOpacityUniform; +} + GLuint ShaderHelper::posAtt() { if (!m_initialized) diff --git a/src/datavisualization/utils/shaderhelper_p.h b/src/datavisualization/utils/shaderhelper_p.h index bc7609bb..ac815447 100644 --- a/src/datavisualization/utils/shaderhelper_p.h +++ b/src/datavisualization/utils/shaderhelper_p.h @@ -80,6 +80,8 @@ class ShaderHelper GLuint color8Bit(); GLuint textureDimensions(); GLuint sampleCount(); + GLuint alphaMultiplier(); + GLuint preserveOpacity(); GLuint posAtt(); GLuint uvAtt(); @@ -120,6 +122,8 @@ class ShaderHelper GLuint m_color8BitUniform; GLuint m_textureDimensionsUniform; GLuint m_sampleCountUniform; + GLuint m_alphaMultiplierUniform; + GLuint m_preserveOpacityUniform; GLboolean m_initialized; }; diff --git a/tests/volumetrictest/volumetrictest.cpp b/tests/volumetrictest/volumetrictest.cpp index 3485dd24..554373cb 100644 --- a/tests/volumetrictest/volumetrictest.cpp +++ b/tests/volumetrictest/volumetrictest.cpp @@ -317,7 +317,7 @@ void VolumetricModifier::createAnotherVolume() QImage logo; logo.load(QStringLiteral(":/logo.png")); - //logo = logo.convertToFormat(QImage::Format_ARGB8555_Premultiplied); + logo = logo.convertToFormat(QImage::Format_ARGB8555_Premultiplied); qDebug() << "second image dimensions:" << logo.width() << logo.height() << logo.byteCount() << (logo.width() * logo.height()) << logo.bytesPerLine(); @@ -347,6 +347,8 @@ void VolumetricModifier::createAnotherVolume() // Change one picture using subtexture replacement QImage flipped = logo.mirrored(); m_volumeItem2->setSubTextureData(100, flipped); + m_volumeItem2->setAlphaMultiplier(0.2f); + m_volumeItem2->setPreserveOpacity(false); } void VolumetricModifier::createYetAnotherVolume() |