今天介绍的是实际工作中最常用到的着色器:渐变色着色器。
渐变色着色器是一个从一种颜色平滑的过渡到另一种颜色的效果,渐变色着色器的作用主要是增强图形的视觉吸引力。
线性渐变
Skia 里的线性渐变色着色器是最简单的渐变色着色器,它用于在 2D 图形中创建从一点到另一点平滑过渡的颜色效果。它通过在起点和终点之间进行线性插值,实现了两种或多种颜色之间的平滑渐变。
现在来看一下如何使用线性渐变色着色器来绘制窗口背景,如下代码所示:
// #include "include/effects/SkGradientShader.h"
void drawLinearGradientColor(SkCanvas *canvas)
{
SkPaint paint;
paint.setAntiAlias(true);
SkPoint pts[2]{SkPoint::Make(0, 0), SkPoint::Make(w, h)};
SkColor colors[6]{ 0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00,0xFF0000FF,0xFF00FF00,0xFFFF0000 };
sk_sp<SkShader> shader = SkGradientShader::MakeLinear(pts, colors, nullptr, 6, SkTileMode::kClamp);
paint.setShader(shader);
canvas->drawPaint(paint);
//用线性渐变色绘制圆形
//auto x = w / 2;
//auto y = h / 2;
//auto r = std::min(x - 60, y - 60);
//canvas->drawCircle(x, y, r, paint);
//用线性渐变色绘制路径
//paint.setStroke(true);
//paint.setStrokeWidth(16);
//paint.setStrokeJoin(SkPaint::kRound_Join);
//SkPath path;
//path.moveTo(60, 120);
//path.lineTo(180, 60);
//path.lineTo(w - 60, 120);
//path.lineTo(w - 160, h - 160);
//path.lineTo(180, h - 60);
//path.close();
//canvas->drawPath(path, paint);
}
在这段代码中使用 SkGradientShader 类型的静态方法 MakeLinear 创建了一个线性渐变着色器。
MakeLinear 方法的第一个参数是一个只有两个 SkPoint 元素的数组,数组内存储的两个点为线性渐变的起点和终点。
第二个参数是一个颜色数组,这个数组中存储的颜色将被分布在线性渐变的起点和终点之间。
第三个参数为一个 SkPoint 数组,这个数组用于控制各个颜色的位置,如果不传递这个参数(nullptr),那么颜色将被均匀分布。
第四个参数为第二个数组参数和第三个数组参数的元素数量。
第五个参数 SkTileMode::kClamp 表示如果着色器在起点和终点之外绘制,则超出的区域使用 colors 首尾两端的颜色进行绘制。
除此之外 SkTileMode 还有一些其他的枚举项,这里就不一一介绍了。
MakeLinear方法返回一个 SkShader 类型的对象,这个对象可以被设置到 SkPaint 对象中。
SkPaint设置了shader之后,它设置的Color就不再生效,SkPaint优先使用shader。
程序运行后的结果如下图所示:

由于渐变色是在 SkPaint 对象设置的,可以用来绘制任何几何图形,
注释的代码就是使用这个渐变色绘制圆形和路径的示例代码,运行结果如下图所示:

素材:WebGradients 是一个提供了180个线性渐变色的免费网站。
径向渐变
径向渐变允许颜色从中心点向四周以圆形扩散,绘制径向渐变颜色与绘制线性渐变颜色非常相似,代码如下所示:
void drawRadialGradientColor(SkCanvas *canvas)
{
SkPaint paint;
paint.setAntiAlias(true);
SkColor colors[6]{ 0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00,0xFF0000FF,0xFF00FF00,0xFFFF0000 };
auto x = w / 2;
auto y = h / 2;
auto r = std::min(x - 10, y - 10);
auto shader = SkGradientShader::MakeRadial(SkPoint::Make(x, y), r, colors, nullptr, 6, SkTileMode::kClamp);
paint.setShader(shader);
canvas->drawPaint(paint);
}
使用SkGradientShader类型的MakeRadial方法创建径向渐变,
这个方法的第一个参数是径向渐变的圆心,第二个参数是径向渐变的半径,其他参数与线性渐变SkGradientShader::MakeLinear方法相同。
程序运行后的结果如下图所示:

从上图中可以看出,窗口边缘的颜色为红色,这是因为设置了SkTileMode::kClamp,所以超出的区域使用colors数组首尾两端的颜色进行绘制。
锥形渐变
锥型渐变允许颜色从一个中心点向四周以锥形的方式扩散渐变,这种渐变色常用于绘制光晕或放射效果。如下代码所示:
void drawConicalGradientColor(SkCanvas* canvas)
{
SkPaint paint;
auto x = w / 2;
auto y = h / 2;
SkColor colors[6]{ 0xFF00FFFF, 0xFFFFFF66, 0xFFFF00FF, 0xFF66FFFF, 0xFFFFFF00, 0xFFFF66FF };
auto shader = SkGradientShader::MakeTwoPointConical(SkPoint::Make(x, y), y, SkPoint::Make(x, 60.0f), 20.0f,
colors, nullptr, 6, SkTileMode::kClamp);
paint.setShader(shader);
canvas->drawPaint(paint);
}
SkGradientShader::MakeTwoPointConical方法负责创建一个锥型渐变。这个方法接收两个圆作为参数。
第一个参数为第一个圆的圆心,第二个参数为第一个圆的半径。
第二个参数为第二个圆的圆心,第三个参数为第二个圆的半径,其他参数与制作线性渐变颜色相同。
程序运行结果如下图所示:

旋转渐变
旋转渐变把一系列颜色平均分布到360°的角度范围内(默认情况下以0°开始,以360°结束),如下代码所示:
void drawSweepGradientColor(SkCanvas* canvas)
{
SkPaint paint;
auto x = w / 2;
auto y = h / 2;
SkColor colors[7]{ 0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00,0xFF0000FF,0xFF00FF00,0xFFFF0000,0xFF00FFFF };
auto shader = SkGradientShader::MakeSweep(x, y, colors, nullptr, 7, 0, nullptr);
paint.setShader(shader);
canvas->drawPaint(paint);
}
SkGradientShader::MakeSweep方法负责创建旋转渐变。
此方法前两个参数为旋转渐变的圆心,其他参数与制作线性渐变色相同。
为了让开始颜色和结束颜色更平滑的过度,我把开始颜色和结束颜色设置成了同一个颜色。
程序运行结果如下图所示:

综合示例:绘制圆柱体
Skia里的几何图形、路径、颜色和渐变色着色器,运用这些知识绘制一个圆柱体,如下代码所示:
void drawCylinder(SkCanvas* canvas) {
int bodyW{ 160 }, top{60};
int x1{ w / 2 - bodyW / 2 }, x2{ w / 2 + bodyW / 2 };
SkPath path;
SkRect rect;
rect.setXYWH(x1, h - 80, bodyW, 40);
path.arcTo(rect, 0, 180,true);
path.lineTo(x1, top+20);
path.lineTo(x2, top+20);
path.close();
SkPaint paint;
paint.setAntiAlias(true);
SkPoint pts[2]{ SkPoint::Make(x1, top), SkPoint::Make(x2, top) };
SkColor colors[2]{ 0xFF287191, 0xFF85B5CB };
sk_sp<SkShader> shader = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
paint.setShader(shader);
canvas->drawPath(path, paint);
rect.setXYWH(x1, top, bodyW, 40);
colors[0] = 0xffBDE4F8;
colors[1] = 0xffA7CFE6;
pts[0] = SkPoint::Make(x1, top + 40);
pts[1] = SkPoint::Make(x2, top);
shader = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
paint.setShader(shader);
canvas->drawOval(rect, paint);
}
在这段代码中,分两个步骤来绘制这个圆柱体:
- 使用路径绘制圆柱体的
Body
圆柱体的Body是由一个圆弧和一部分矩形组成的,绘制这个路径时,使用了一个从左到右的线性渐变色,左侧颜色较深,右侧颜色较浅。
这个路径绘制完成之后,得到的结果如下图所示:

- 2,使用椭圆绘制圆柱体的顶面
圆柱体的顶面是一个椭圆,椭圆的横向中轴线就是圆柱体的Body的顶边。
同样也是使用一个线性渐变色绘制这个椭圆,椭圆的左下角颜色较浅,右上角颜色较深。
椭圆绘制完成之后,圆柱体的Body的顶边就被这个椭圆盖住了。
最终的绘制结果如下图所示:

在这个示例中,我们使用 2D 图形绘图知识绘制了一个 3D 图形。
要知道无论是 2D 图形引擎还是 3D 图形引擎,最终都是在处理像素,交付给客户的也都是二维平面像素数据(你的显示器只能承载二维平面像素数据)。3D 图形只不过看起来是立体的而已。
所以,2D 图形引擎可以画 3D 图形,3D 图形引擎也可以画 2D 图形,只不过这两个图形引擎的内部实现原理大相径庭。
综合示例:绘制圆锥体
稍稍改动一下圆柱体的绘制代码,就可以绘制圆锥体了,如下代码所示:
void drawCone(SkCanvas* canvas) {
int bodyW{ 160 }, top{ 60 };
int x1{ w / 2 - bodyW / 2 }, x2{ w / 2 + bodyW / 2 };
SkPath path;
SkRect rect;
rect.setXYWH(x1, h - 80, bodyW, 40);
path.arcTo(rect, 0, 180, true);
path.lineTo(x1+bodyW/2, top + 20);
path.close();
SkPaint paint;
paint.setAntiAlias(true);
SkColor colors[2]{ 0xFF00FF00, 0xFF000000 };
auto shader = SkGradientShader::MakeSweep(w/2, top + 20, colors, nullptr,2,SkTileMode::kClamp, 50.0, 130.0, 0,nullptr);
paint.setShader(shader);
canvas->drawPath(path, paint);
}
这段代码去除了圆柱体的顶面和圆柱体 Body 的顶边,在绘制路径时,使用了旋转渐变颜色。
旋转渐变的起始角度为 50 度,结束角度为 130 度,顺时针从绿色渐变到黑色。
这两个角度之差大于圆锥顶角值,超出圆锥(扇形)表面的颜色将不会被绘制到窗口中。
程序运行后,绘制结果如下图所示:

2824

被折叠的 条评论
为什么被折叠?



