凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

QML で表示した WebView をシェーダでいじって遊んでみた

はじめに

QML では ShaderEffect という要素を利用して、表示している要素をソースにシェーダでいじることが出来ます。

そこで WebView で表示した Web ページをソースに色々と遊んでみました。

Fragment Shader

デモ

波打たせてみます。

これで Vim ガールさんがセクシーにうねんうねんします。

解説

驚きのコード量の少なさです。

import QtQuick 2.0
import QtWebKit 3.0

Rectangle {
    width: 720
    height: 480

    WebView {
        id: webview
        anchors.fill: parent
        url: 'http://b.hatena.ne.jp'
    }

    ShaderEffect {
        anchors.fill: parent
        property real time: 0
        NumberAnimation on time {
            loops: Animation.Infinite
            from: 0
            to: Math.PI * 2
            duration: 1000
        }
        property var source: ShaderEffectSource {
            sourceItem: webview
            hideSource: true
        }
        fragmentShader: "
            uniform highp float time;
            uniform sampler2D source;
            varying highp vec2 qt_TexCoord0;
            void main() {
                float a = 0.01 * (0.5 * (sin(time) + 1.0));
                highp vec2 uv = qt_TexCoord0.xy;
                highp vec2 delta = a * sin((vec2(uv.y, uv.x) * 30.0 + time));
                gl_FragColor  = texture2D(source, uv + delta);
            }"
    }
}

QML のプロパティの値を uniform 変数として何もせずとも渡せてしまうお手軽感パないです。

Vertex Shader

デモ

頂点シェーダも使えます。こっちはもう少し実用感も見据えてスクロール速度に応じてミョーンと伸びるエフェクトを作ってみました。

たまにガタつくのは何故か不明ですが、結構気持ち良いです。

解説
import QtQuick 2.0
import QtWebKit 3.0

Rectangle {
    width: 720
    height: 480

    WebView {
        id: webview
        anchors.fill: parent
        url: 'http://b.hatena.ne.jp'
    }

    MouseArea {
        id: scroll
        anchors.fill: parent
        property double value: 0
        property double stretch: 0.0005
        property double elastic: 0.9
        propagateComposedEvents: true
        onWheel: {
            value -= Math.abs(wheel.pixelDelta.y * stretch);
            wheel.accepted = false;
        }
        Timer {
            interval: 10; running: true; repeat: true
            onTriggered: { scroll.value *= scroll.elastic; }
        }
    }

    ShaderEffect {
        anchors.fill: parent
        property double speed: scroll.value
        property real time: 0
        NumberAnimation on time {
            loops: Animation.Infinite
            from: 0
            to: Math.PI * 2
            duration: 1000
        }
        property var source: ShaderEffectSource {
            sourceItem: webview
            hideSource: true
        }
        vertexShader: "
            uniform highp float time;
            uniform highp float speed;
            uniform highp mat4 qt_Matrix;
            attribute highp vec4 qt_Vertex;
            attribute highp vec2 qt_MultiTexCoord0;
            varying highp vec2 qt_TexCoord0;
            void main() {
                highp vec4 pos = qt_Vertex;
                highp float d = 0.5 * smoothstep(0.0, 1.0, qt_MultiTexCoord0.y);
                gl_Position = qt_Matrix * pos;
                highp vec2 coord = qt_MultiTexCoord0;
                if (coord.y == 1.0) coord.y += speed;
                qt_TexCoord0 = coord;
            }"
    }
}

MouseArea で propagateComposedEvents をすればイベントをスティールされてしまうのを防げるのを初めて知りました:

おわりに

もちろん頂点シェーダとフラグメントシェーダの組み合わせも可能です。色々遊んでみて下さい。
QML が楽過ぎて生きるのが辛い。