OpenGL 实现 2D 多边形渐变色填充

多边形填充渐变色。渐变色由 2 个锚点确定。如下图,给定 2 个锚点 p0, p1 的坐标及各自的颜色,直线 p0-p1 上每一点的颜色由锚点的颜色通过线性插值获得;平面上任意一点 p2 在直线 p0-p1 上的投影为 p,p2 的颜色与 p 相同:

根据直线 p0-p1 方程有

(x - x0)(y1 - y0) - (x1 - x0)(y - y0) = 0

又矢量 p0-p1 与 p2-p 正交,故二者点积为 0,有

(x - x2)(x1 - x0) + (y - y2)(y1 - y0) = 0

将以上 2 式变形,并组成线性方程组

(y1 - y0)⋅x - (x1 - x0)⋅y = (y1 - y0)⋅x0 - (x1 - x0)⋅y0

(x1 - x0)⋅x + (y1 - y0)⋅y = (x1 - x0)⋅x2 + (y1 - y0)⋅y2

若记

A = x1 - x0

B = y1 - y0

C = B⋅x0 - A⋅y0

D = A⋅x2 + B⋅y2

则有

B⋅x - A⋅y = C

A⋅x + B⋅y = D

求解得

x = (AD + BC)/(A2 + B2)

y = (BD - AC)/(A2 + B2)

注意到,A, B, C 的值固定,而 D 的值与 p2 相关。

p 点颜色分量 c 由 p0, p1 点对应的颜色分量 c0, c1 线形插值得到

t = (x - x0 + y - y0)/(x1 - x0 + y1 - y0)

c = c0 + t⋅(c1 - c0)

OpenGL 实现

为对比程序运行结果,预先制作一张填充了渐变色的图片,如下。将从这张图片上取多边形顶点坐标,以及 2 个锚点的坐标和颜色:

取得的顶点及锚点数据如下:

C 复制代码
GLfloat vertices[] = {
    194, 350, //
    407, 297, //
    444, 166, //
    331, 108, //
    246, 147, //
    199, 77,  //
    90, 75,   //
    44, 248,  //
};

GLfloat anchors[] = {
    91, 295, .976, .082, .067,  //
    401, 170, .875, .922, .008, //
};

其中,在锚点数据数组 anchors 中,每个点占 5 个数组元素,依次为位置坐标 (x, y) 和颜色 (r, g, b)。颜色分量已转换到 [0, 1] 区间。

Vertex shader 主要代码如下:

C++ 复制代码
struct anchor
{
    vec2 pos;
    vec3 color;
};

attribute vec4 a_pos;

uniform mat4 m_trans;
uniform anchor u_anchors[2];

varying mediump vec4 v_color;

vec3 gradient(vec2 pos);

void main()
{
    gl_Position = m_trans * a_pos;
    v_color = vec4(gradient(vec2(a_pos)), 1.0);
}

空间变换部分这里只做简要介绍。a_pos 为顶点坐标,m_trans 是变换矩阵。将变换矩阵应用到顶点坐标,得到投影后的 NDC 坐标,赋值给内建变量 gl_Position

这里将锚点数据定义为一个 struct(代码第 1 行),并以 uniform 类型从应用程序绑定到 OpenGL,以使得 shader 能够访问(第 10 行)。

gradient() 是一个自定义函数,根据前文推导的算法,实现渐变色的计算。函数参数是某一点的 2D 坐标 (x, y),返回值为该点的颜色 (r, g, b)。Vertex shader 调用 gradient() 获得顶点的颜色后,将其赋值给 varying 变量(第 19 行)。该 varying 变量值将进入 OpenGL 渲染流水线的后续流程,并最终到达 fragment shader,由 fragment shader 设置为最终像素的颜色。

gradient() 完整定义如下。可以看到,它是对前面所推导出来的算法的忠实实现:

C++ 复制代码
vec3 gradient(vec2 pos)
{
    float x0 = u_anchors[0].pos.x;
    float y0 = u_anchors[0].pos.y;
    float x1 = u_anchors[1].pos.x;
    float y1 = u_anchors[1].pos.y;

    float A = x1 - x0;
    float B = y1 - y0;
    float C = B * x0 - A * y0;
    float D = A * pos.x + B * pos.y;

    float d = A * A + B * B;
    float x = (A * D + B * C) / d;
    float y = (B * D - A * C) / d;

    float t = (x - x0 + y - y0) / (x1 - x0 + y1 - y0);
    vec3 c = u_anchors[0].color + (u_anchors[1].color - u_anchors[0].color) * t;
    return clamp(c, 0.0, 1.0);
}

程序运行结果如下图。目测基本可认定,实际填充效果与我预先制作的参照图片一致:

完整代码见 gitlab.com/sihokk/lear...

相关推荐
二进制人工智能14 天前
【OpenGL学习】(二)OpenGL渲染简单图形
c++·opengl
六bring个六18 天前
qtcreater配置opencv
c++·qt·opencv·计算机视觉·图形渲染·opengl
爱看书的小沐20 天前
【小沐学GIS】基于C++绘制二维瓦片地图2D Map(QT、OpenGL、GIS)
c++·qt·gis·opengl·glfw·glut·二维地图
六bring个六24 天前
图形渲染+事件处理最终版
c++·qt·图形渲染·opengl
星火撩猿24 天前
OpenGl实战笔记(3)基于qt5.15.2+mingw64+opengl实现光照变化效果
笔记·qt·opengl·光照效果
星火撩猿24 天前
OpenGl实战笔记(2)基于qt5.15.2+mingw64+opengl实现纹理贴图
笔记·qt·opengl·纹理贴图
程序员爱德华1 个月前
计算机图形学中的深度学习
图形学·opengl
:mnong1 个月前
开放原子大赛石油软件赛道参赛经验分享
c++·qt·hdfs·开放原子·图形渲染·webgl·opengl
米芝鱼1 个月前
Unity URPShader:实现和PS一样的色相/饱和度调整参数效果(修复)
游戏·unity·游戏引擎·图形渲染·opengl·着色器
三村阿明1 个月前
OpenGL ES 3.0 第二章总结:你好,三角形(Hello Triangle)
opengl·opengl es·opengl android