将QML的ShaderEffect剪辑成圆形

Clip QML's ShaderEffect to circular shape

我在 QML 中使用 ShaderEffect 来获得某些项目的缩放视觉副本。 此副本应该是可移动和动态的(ShaderEffectSourcelive 属性 设置为 true)。

我的问题是我希望它在一个圆内 Rectangle 但它不会被它剪掉。 ShaderEffect 与父级重叠并且是二次方的。

我编写了一个显示问题的快速 QML 示例:

import QtQuick 2.4
import QtQuick.Controls 1.3

ApplicationWindow {
    title: qsTr("Hello Shaders")
    width: 640
    height: 480
    visible: true
    color: "green"

    Rectangle {
        id: exampleRect

        property bool redState: false

        anchors.centerIn: parent
        width: parent.width / 3
        height: parent.height / 3
        color: redState ? "red" : "cyan"


        Rectangle {
            anchors.centerIn: parent
            width: parent.width / 2; height: width;
            color: "white"

            Rectangle {
                anchors.centerIn: parent
                width: parent.width / 2; height: width;
                color: "green"

                Rectangle {
                    anchors.centerIn: parent
                    width: parent.width / 2; height: width;
                    color: "yellow"
                }
            }
        }

        Timer {
            interval: 2000
            repeat: true
            running: true

            onTriggered: {
                exampleRect.redState = !exampleRect.redState
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        hoverEnabled: true
        onPositionChanged: {
            shaderEffectContainer.x = mouse.x
            shaderEffectContainer.y = mouse.y
        }
    }

    Rectangle {
        id: shaderEffectContainer

        width: 100; height: width;
        radius: width / 2;
        border.width: 2

        ShaderEffectSource {
            id: source

            sourceItem: exampleRect
            visible: false
        }

        ShaderEffect {
            anchors.fill: parent

            property variant source: source

            vertexShader: "
                uniform highp mat4 qt_Matrix;
                attribute highp vec4 qt_Vertex;
                attribute highp vec2 qt_MultiTexCoord0;
                varying highp vec2 qt_TexCoord0;
                void main() {
                    qt_TexCoord0 = qt_MultiTexCoord0 * 1.5 * vec2(0.5, 0.5);
                    gl_Position = qt_Matrix * qt_Vertex;
                }"

            fragmentShader: "
                varying highp vec2 qt_TexCoord0;
                uniform sampler2D source;
                uniform lowp float qt_Opacity;
                void main() {
                    gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
                }"
        }
    }
}

如您所见,exampleRect 被声明为圆形,但它的子项与它重叠。

我已经尝试了 clip 属性 的所有可能组合,并花了一整天时间尝试使用 fragment/vertex 着色器来完成此操作。正如您可能猜到的那样,没有运气。 :)

我已经使用 layers as shown in this answer 解决了这个问题。
感谢@DenimPowell 的提示。

下面是更新后的示例代码,其中包含循环 ShaderEffect

import QtQuick 2.4
import QtQuick.Controls 1.3

ApplicationWindow {
    title: qsTr("Hello Shaders")
    width: 640
    height: 480
    visible: true
    color: "green"

    Rectangle {
        id: exampleRect

        property bool redState: false

        anchors.centerIn: parent
        width: parent.width / 3
        height: parent.height / 3
        color: redState ? "red" : "cyan"


        Rectangle {
            anchors.centerIn: parent
            width: parent.width / 2; height: width;
            color: "white"

            Rectangle {
                anchors.centerIn: parent
                width: parent.width / 2; height: width;
                color: "green"

                Rectangle {
                    anchors.centerIn: parent
                    width: parent.width / 2; height: width;
                    color: "yellow"
                }
            }
        }

        Timer {
            interval: 2000
            repeat: true
            running: true

            onTriggered: {
                exampleRect.redState = !exampleRect.redState
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        hoverEnabled: true
        onPositionChanged: {
            shaderEffectContainer.x = mouse.x
            shaderEffectContainer.y = mouse.y
        }
    }

    Rectangle {
        id: shaderEffectContainer

        width: 100; height: width;

        color: "transparent"

        Rectangle {
            id: rectangleSource

            anchors.fill: parent

            ShaderEffectSource {
                id: source

                sourceItem: exampleRect
                visible: false
            }

            ShaderEffect {
                anchors.fill: parent

                property variant source: source

                vertexShader: "
                    uniform highp mat4 qt_Matrix;
                    attribute highp vec4 qt_Vertex;
                    attribute highp vec2 qt_MultiTexCoord0;
                    varying highp vec2 qt_TexCoord0;
                    void main() {
                        qt_TexCoord0 = qt_MultiTexCoord0 * 1.5 * vec2(0.5, 0.5);
                        gl_Position = qt_Matrix * qt_Vertex;
                    }"

                fragmentShader: "
                    varying highp vec2 qt_TexCoord0;
                    uniform sampler2D source;
                    uniform lowp float qt_Opacity;
                    void main() {
                        gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
                    }"
            }
            visible: false

            layer.enabled: true
        }

        Rectangle {
            id: maskLayer
            anchors.fill: parent
            radius: parent.width / 2

            color: "red"

            border.color: "black"

            layer.enabled: true
            layer.samplerName: "maskSource"
            layer.effect: ShaderEffect {

                property var colorSource: rectangleSource
                fragmentShader: "
                    uniform lowp sampler2D colorSource;
                    uniform lowp sampler2D maskSource;
                    uniform lowp float qt_Opacity;
                    varying highp vec2 qt_TexCoord0;
                    void main() {
                        gl_FragColor =
                            texture2D(colorSource, qt_TexCoord0)
                            * texture2D(maskSource, qt_TexCoord0).a
                            * qt_Opacity;
                    }
                "
            }
        }

        // only draw border line
        Rectangle {
            anchors.fill: parent

            radius: parent.width / 2

            border.color: "black"
            border.width: 1

            color: "transparent"
        }
    }
}