SVG 三角形的插值填充

Ste*_*ond 2 html javascript svg vue.js

我的目标是在 SVG 中创建一个任意三角形,其中红色、黄色和绿色各有一个顶点,并根据顶点的颜色插值填充颜色。

与DirectX、OpenGL等提供的早期RGB三角形教程非常相似:

在此输入图像描述

我的对于锐角三角形效果很好:

好看的三角形

但对于钝角三角形来说就不那么重要了:

在此输入图像描述

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

            <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>
Run Code Online (Sandbox Code Playgroud)

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

            <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>
Run Code Online (Sandbox Code Playgroud)

半径计算基于到其他 2 个顶点中点的线的长度:

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)
    };
}
Run Code Online (Sandbox Code Playgroud)

我认为问题在于黄色和绿色(在红色之后渲染)具有更长的半径并且基本上隐藏了红色。线性渐变也好不了多少。由于梯度方法可能存在缺陷,那么使用 SVG 是否有更好的方法?

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

编辑:在使用 SVG 的所有边缘情况下,我无法完全获得我想要的颜色混合,所以最后我最终转向了画布和 WebGL。

Wat*_*ous 5

编辑:除了我在这个答案中解释的近似值 \xe2\x80\x93 看这里之外,似乎还有一个实际上正确的解决方案。

\n

有两个问题:

\n
    \n
  1. 正如您提到的,绿色渐变的半径太长。
  2. \n
  3. 绿色渐变渲染在其他两个渐变之上(黄色渐变渲染在红色渐变之上),因此,即使在等边三角形中,颜色也会不平衡。
  4. \n
\n

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

\n
        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600">\n            <defs>\n                <radialGradient id="red"  gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"\n                    :gradientTransform="transformation(points[0], points[1], points[2])">\n                    <stop offset="0%" stop-color="#ff0000ff" />\n                    <stop offset="100%" stop-color="#7fff0000" />\n                </radialGradient>\n\n                <radialGradient id="green" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"\n                    :gradientTransform="transformation(points[1], points[2], points[0])">\n                    <stop offset="0%" stop-color="#00ff00ff" />\n                    <stop offset="100%" stop-color="#ff7f0000" />\n                </radialGradient>\n\n                <radialGradient id="yellow" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"\n                    :gradientTransform="transformation(points[2], points[0], points[1])">\n                    <stop offset="0%" stop-color="#ffff00ff" />\n                    <stop offset="100%" stop-color="#7f7f0000" />\n                </radialGradient>\n            </defs>\n\n            <path :d="svgTriangle" fill="url(#red)" />\n            <path :d="svgTriangle" fill="url(#yellow)" />\n            <path :d="svgTriangle" fill="url(#green)" />\n        </svg>\n
Run Code Online (Sandbox Code Playgroud)\n

用这个函数代替radius

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

这应该做你想做的。

\n

说明:每个径向渐变最初以点 [0;0] 为中心,半径为 1。然后应用仿射变换,将中心 [0;0] 发送到相应的顶点,并发送点 [2*\xe2\x88\ x9a3/3;0]和[\xe2\x88\x9a3/3;1]到其他顶点(你可以自己检查)。由于这些点位于原始渐变之外(距离 [0;0] 比 1 更远),因此其他顶点也将位于变换后的渐变之外,因此渐变永远不会隐藏它们。

\n

此外,在等边三角形的情况下,此代码会产生与您的代码相同的结果。如果您使用此代码填充任何其他三角形,它将与您填充一个等边三角形,然后通过某种仿射变换将其压缩为另一个三角形的形状相同(这是因为仿射变换的组合仍然是仿射变换和一个特定对象的仿射变换由三个变换点(在本例中为顶点)的位置唯一定义。重要的结果是,每种颜色在每个三角形中覆盖总面积的相同“百分比”,因此无需担心红色会完全丢失。

\n