Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clip QML's ShaderEffect to circular shape

I'm using ShaderEffect in QML to have a scaled visual copy of some Item. This copy should be moveable and dynamic (live property of ShaderEffectSource set to true).

My problem is that I want it to be inside of a round Rectangle but it won't be clipped by it. ShaderEffect just overlaps its parent and is quadratic.

I've coded a quick QML example that shows the problem:

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;
                }"
        }
    }
}

As you can see, exampleRect is declared to be round, but it's child is overlapping it.

I have already tried all possible combinations of clip property and have spent all day trying to do this with fragment/vertex shaders. With no luck, as you might guess. :)

like image 888
rsht Avatar asked Jul 22 '15 14:07

rsht


1 Answers

I've solved the problem using layers as shown in this answer.
Thanks @DenimPowell for the hint.

Below is an updated example code with circular 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"
        }
    }
}
like image 159
rsht Avatar answered Sep 30 '22 06:09

rsht