Draw a dashed and dotted bezier curve in QML

2020-05-26 06:35发布

问题:

I've seen there is an example implementation of a Bezier curve in QML, but I'm looking for a hint how to implement dashed or dotted bezier curve line. As far as I see, tha authors of Bezier curve example are using QSGGeometryNode to store inside QSGGeometry with a QSGFlatColorMaterial material applied on it. Then they simply create list of points and draw segments between them.

Is it possible to write a shader and apply it to QSGFlatColorMaterial (to display line as dashed, dotted, etc)?

Eventually, is it possible to store more than one QSGGeometry inside QSGGeometryNode?

UPDATE

I would like to implement this in "pure QtQuick" - not in "old" interfaces (like QPainter etc) - because I do not want to use something, which switches context (openGL and CPU). I prefer the solution with custom shader (if is it doable) - because I'll have more possibilities in implementing custom look and feel (dashed, doted, colored, maybe animated etc).

If it is not possible, I'll use QPainter.

回答1:

I don't think this task is a good candidate for implementing using QSGGeometryNode, it would be much easier to implement it using QPainter based drawing and a QQuickPaintedItem. You will still get the benefits of OpenGL, since QPainter supports GL drawing as well and it is still faster than software. You can use the stock QPen with stock dotted or dashed patterns or make your own with a simple QVector.

Alternatively, you can go for a custom GL drawing approach instead of using the classes provided by Qt, which are pretty limited when it comes to representing advanced compound geometry. You can even use instancing (if available) to improve performance even further, and just position dashes or dot geometry along the path curve.

Last but not least, you can use a QML Canvas element, which supports pretty much the same operations as QPainter and probably offers the same performance.

EDIT: As your update suggests, you missed the part where I said QPainter can draw in both software and GL, with GL drawing being often significantly faster. Also, by drawing to a GL context, you don't have to move the framebuffer from CPU to GPU memory, it is kept in GPU memory. So no overhead. As for animations and the other stuff, sure, it is not possible with QPainter you are limited to whatever QPen provides - different joins, caps and so on can be used to modify the shape to some extent, but no miracles... It won't be possible with shaders too, it will only be possible with custom geometry. And if you use a QObject based object for each dash/dot element in order to independently animate them, it will end up quite expenssive, QObject is very heavy and should not be used with such light hand. So custom GL rendering to a FBO is pretty much the way to go if you want that kind of flexibility, but you will have to move completely out of the QtQuick API and into GL land.

At any rate, a dashed line shader should not be all that complex, basically you color the fragment based on the distance from the curve and the "period" along its length. I found this example, haven't tried it myself. You could animate the thresholds, even use a sine function to get funky looking styling.

As for a "pure" QtQuick implementation, the API has not really been designed to handle such type of drawing tasks, that is why the Canvas element was provided to fill the gap and get advanced paining functionality from QML/JS. The Canvas is effectively a wrapper around QPainter that draws onto a FBO.

In the end it doesn't boil down to what is possible/impossible but which approach makes the most sense and is most efficient at getting the job done. Try the QQuickPaintedItem approach first, since it is the easiest, if you are not happy with performance, you can implement another more complex solution and profile against the first. After all, that is why QQuickPaintedItem was introduced in the first place - to handle legacy painting that is not convenient to do with the QQuickItem class.



回答2:

With Qt 5.10 the Shape element has been introduced and seems to do exactly what you want.

https://doc.qt.io/qt-5.10/qml-qtquick-shapes-shape.html

Shape {
    width: 20
    ShapePath {
        strokeColor: "blue"
        strokeWidth: 2
        strokeStyle: ShapePath.DashLine
        startX: 0
        startY: 0
        PathLine { x: parent.width; y: 0 }
    }
}


回答3:

Why don't you use this approach:

Beziercurve example modified to use QSGVertexColorMaterial

Then, you can specify your color and alpha for each vetex, to get the dashes, dots, or whatever pattern you choose.

Here are the important parts:

 geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), m_segmentCount);
 //geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);

QSGVertexColorMaterial *material = new QSGVertexColorMaterial;
//material->setColor(QColor(255, 0, 0));

//QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
QSGGeometry::ColoredPoint2D *vertices = geometry->vertexDataAsColoredPoint2D();

vertices[i].set(x, y, 0, 0, 0, 0);
//vertices[i].set(x, y);


回答4:

No, you can not store multiple geometries in a geometry node. The API is very explicit about it. There's no reason to store multiple geometries there, since a node pairs a geometry and a material. You can reuse geometries and materials between nodes - that's how it's intended to be used, in fact.

The rest of the question is not really complete, and even if a shader-based implementation was provided, it won't be very useful initially. It'll be merely a premature optimization. Let's look at what you're missing.

The example BezierCurve item is just a proof of concept. It's not useful by itself, since you need a way of chaining multiple items that are stroked using the same pen. You need something akin to a simple QPainterPath. In fact, the geometry itself could be generated by the QPainterPath and QPainterPathStroker.

Once you've got a complete tesselated geometry for a continuously-stroked item, you can then either further cut it up based on the line style, or use a shader. It'd require profiling to show that a line-style shader by itself is a big win. It may well be that you need a geometry shader to do the stroking etc. and all the performance gain would be centered there. Think of the number of computations to be done, the line-styling is relatively simple.



回答5:

import QtQuick 2.0

Rectangle {
    width : 1024
    height: 600

    Rectangle {
        x: -3 + 158
        y: 355
        width: 4; height: 4;
        color: "black";
        }

    Rectangle {
        x: 359 + 158
        y: 220
        width: 4; height: 4;
        color: "black";
        }

    Rectangle {
        x: 175 + 158
        y: 238
        width: 2; height: 2;
        color: "black";
        }

    Rectangle {
        x: 711 + 158
        y: 355
        width: 4; height: 4;
        color: "black";
        }

    Rectangle {
        x: 533 + 158
        y: 238
        width: 2; height: 2;
        color: "black";
        }

    Rectangle {
        x: -3 + 118
        y: 355
        width: 4; height: 4;
        color: "darkBlue";
        }

    Rectangle {
        x: 399 + 118
        y: 220
        width: 4; height: 4;
        color: "darkBlue";
        }

    Rectangle {
        x: 196 + 118
        y: 238
        width: 4; height: 4;
        color: "darkBlue";
        }

    Rectangle {
        x: 791 + 118
        y: 355
        width: 4; height: 4;
        color: "darkBlue";
        }

    Rectangle {
        x: 592 + 118
        y: 238
        width: 4; height: 4;
        color: "darkBlue";
        }


    Path {
        id: path
        startX: -3
        startY: 355
        PathQuad { x: 359; y:220; controlX: 175; controlY:238 }
        PathQuad { x: 711; y:355; controlX: 533; controlY:238 }
    }

    Path {
        id: path2
        startX: -3
        startY: 355

        PathQuad { x: 399; y:220; controlX: 196; controlY:238 }
        PathQuad { x: 791; y:355; controlX: 592; controlY:238 }
    }


    PathView {
    id: pathView;
    x: 158
    width: 708
    model: 300;
    path: path
    delegate: Rectangle {
    id: dot;
    width: 1; height: 1;
    color: "red";
    }
    }

    PathView {
    id: pathView2;
    x: 118
    width: 788
    model: 300;
    path: path2
    delegate: Rectangle {
    id: dot2;
    width: 1; height: 1;
    color: "green";
    }
    }
}