SVG三角形的内插填充

时间:2020-07-23 00:56:00

标签: javascript html vue.js svg

我的目标是在SVG中创建一个任意的三角形,每个顶点分别具有红色,黄色和绿色的一个顶点,并根据顶点的颜色对填充颜色进行插值。

非常类似于DirectX,OpenGL等提供的早期RGB三角教程:

enter image description here

我的三角形效果很好:

decent-looking triangle

但是对于钝三角形而言却不是那么多:

enter image description here

我使用VueJS创建了以下SVG,用于数据绑定:

            <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600">
                <defs>
                    <radialGradient id="red" gradientUnits="userSpaceOnUse" :cx="points[0].x" :cy="points[0].y"
                        :r="radius(points[0], points[1], points[2])">
                        <stop offset="0%" stop-color="#ff0000ff" />
                        <stop offset="100%" stop-color="#7fff0000" />
                    </radialGradient>

                    <radialGradient id="green" gradientUnits="userSpaceOnUse" :cx="points[1].x" :cy="points[1].y"
                        :r="radius(points[1], points[0], points[2])">
                        <stop offset="0%" stop-color="#00ff00ff" />
                        <stop offset="100%" stop-color="#ff7f0000" />
                    </radialGradient>

                    <radialGradient id="yellow" gradientUnits="userSpaceOnUse" :cx="points[2].x" :cy="points[2].y"
                        :r="radius(points[2], points[0], points[1])">
                        <stop offset="0%" stop-color="#ffff00ff" />
                        <stop offset="100%" stop-color="#7f7f0000" />
                    </radialGradient>
                </defs>

                <path :d="svgTriangle" fill="url(#red)" />
                <path :d="svgTriangle" fill="url(#yellow)" />
                <path :d="svgTriangle" fill="url(#green)" />
            </svg>

点是在SVG的800x600空间内随机生成的:

for (let p = 0; p < 3; p++) {
    this.points[p] = {
        id: `p${p}`,
        x: Math.floor(Math.random() * 800),
        y: Math.floor(Math.random() * 600)
    };
}

半径计算是基于线的长度到其他两个顶点的中点的。

radius: function (me, other1, other2) {
    const mid = {
        x: (other1.x + other2.x) / 2,
        y: (other1.y + other2.y) / 2
    };
    return Math.sqrt(Math.abs(me.x - mid.x) ** 2 + Math.abs(me.y - mid.y) ** 2);
}

我认为问题在于黄色和绿色(在红色之后呈现)的半径更长,并且基本上隐藏了红色。线性渐变并没有更好。由于梯度方法可能有缺陷,因此使用SVG是否有更好的方法?

我知道可以使用Canvas / WebGL(example),但是使用SVG(也许可以使用混合滤镜)可以完成相同的事情吗?或者,如果我想要这种类型的插值,是否需要使用Canvas / WebGL

1 个答案:

答案 0 :(得分:3)

有两个问题:

  1. 您所提到的,绿色渐变的半径太长。
  2. 绿色渐变呈现在其他两个之上(黄色呈现在红色之上),因此,即使在等边三角形中,颜色也将不平衡。

我会尽力帮助前者。好消息,您绝对可以使用渐变! gradientTransform属性允许渐变为椭圆形而不是圆形,这为您提供了更多选择。您可以使用

        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600">
            <defs>
                <radialGradient id="red"  gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"
                    :gradientTransform="transformation(points[0], points[1], points[2])">
                    <stop offset="0%" stop-color="#ff0000ff" />
                    <stop offset="100%" stop-color="#7fff0000" />
                </radialGradient>

                <radialGradient id="green" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"
                    :gradientTransform="transformation(points[1], points[2], points[0])">
                    <stop offset="0%" stop-color="#00ff00ff" />
                    <stop offset="100%" stop-color="#ff7f0000" />
                </radialGradient>

                <radialGradient id="yellow" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"
                    :gradientTransform="transformation(points[2], points[0], points[1])">
                    <stop offset="0%" stop-color="#ffff00ff" />
                    <stop offset="100%" stop-color="#7f7f0000" />
                </radialGradient>
            </defs>

            <path :d="svgTriangle" fill="url(#red)" />
            <path :d="svgTriangle" fill="url(#yellow)" />
            <path :d="svgTriangle" fill="url(#green)" />
        </svg>

使用此功能代替radius

transformation: function (me, other1, other2) {
    const side1vector = { 
        x: other1.x - me.x,
        y: other1.y - me.y
    };
    const side2vector = { 
        x: other2.x - me.x,
        y: other2.y - me.y
    };
    const matrix = [
        side1vector.x * Math.sqrt(3)/2,
        side1vector.y * Math.sqrt(3)/2,
        side2vector.x - 0.5*side1vector.x,
        side2vector.y - 0.5*side1vector.y,
        me.x,
        me.y
    ];
    return "matrix(" + matrix.join(" ") + ")";
}

这应该做您想要的。

说明:每个径向渐变最初以点[0; 0]为中心并具有半径1。然后应用线性变换,将中心[0; 0]发送到各个顶点并发送点[2 *√3 / 3; 0]和[√3/ 3; 1]到其他顶点(您可以自己检查)。由于这些点在原始渐变之外(比[0; 0]远比1更远),因此其他顶点也将在变换渐变之外,因此渐变将永远不会隐藏它们。

此外,在等边三角形的情况下,此代码产生的结果与您的代码相同。如果您使用此代码填充其他三角形,则与填充等边三角形相同,然后通过线性变换将其压缩为另一个三角形的形状(这是因为线性变换的组合仍然一个特定对象的线性变换和线性变换由三个变换点(在这种情况下为顶点)的位置唯一地定义。重要的结果是,每种颜色在每个三角形中占整个区域的相同“百分比”,因此不必担心红色可能会完全丢失。