今天介绍的是实际工作中最常用到的着色器:渐变色着色器。
渐变色着色器是一个从一种颜色平滑的过渡到另一种颜色的效果,渐变色着色器的作用主要是增强图形的视觉吸引力。
线性渐变
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 度,顺时针从绿色渐变到黑色。
这两个角度之差大于圆锥顶角值,超出圆锥(扇形)表面的颜色将不会被绘制到窗口中。
程序运行后,绘制结果如下图所示: