图形开发学院|GraphAnyWhere
- 课程名称:图形系统开发实战课程(基础篇)
- 课程章节:“渲染效果”
- 原文地址:http://graphanywhere.com/graph/foundation/1-5.html
\quad 在前几章的内容中,我们讲述了各种几何图形的绘制,并学习了使用颜色填充几何图形或者给几何图形描边,本章将会讲解在绘制几何图形的时候使用各种渲染效果。本章的内容包括:
在图形系统开发实战-基础篇:绘制基本图形中讲述了在绘制矩形、直线、多边形、圆等内容时我们学习到了以下几类边框样式:
strokeStyle
属性可以指定边框的颜色lineWidth
属性可以指定描边的粗细setLineDash()
方法可以指定虚线风格本节我们还将学习:
通过画布的渲染上下文对象lineCap属性可指定线路末端样式,其API如下所示:
属性值 | 说明 |
---|---|
butt | 线段末端以方形结束 |
round | 线段末端以圆形结束 |
square | 线段末端以方形结束 |
ctx.lineCap = "butt";
ctx.lineCap = "round";
ctx.lineCap = "square";
运行效果如下图所示:
线段的末端样式规则为:
其完整源代码如下:
<!DOCTYPE html>
<html lang="cn">
<head>
<title>渲染效果(stroke)</title>
<meta charset="UTF-8">
<script src="./js/helper.js"></script>
</head>
<body style="overflow: hidden; margin:10px; background-color: white;">
<canvas id="canvas" width="750" height="300" style="border:solid 1px #CCCCCC;"></canvas>
</body>
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画布中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 指定线宽
ctx.lineWidth = 20;
// ctx.lineCap 默认值(butt);
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(450, 50);
ctx.stroke();
// ctx.lineCap = "butt";
ctx.beginPath();
ctx.moveTo(50, 110);
ctx.lineTo(450, 110);
ctx.lineCap = "butt";
ctx.stroke();
// ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(50, 170);
ctx.lineTo(450, 170);
ctx.lineCap = "round";
ctx.stroke();
// ctx.lineCap = "square";
ctx.beginPath();
ctx.moveTo(50, 230);
ctx.lineTo(450, 230);
ctx.lineCap = "square";
ctx.stroke();
</script>
</html>
通过画布的渲染上下文对象lineJoin
属性可指定线路末端样式,其API如下所示:
ctx.lineJoin = "bevel";
ctx.lineJoin = "round";
ctx.lineJoin = "miter";
属性值 | 说明 |
---|---|
bevel | 在相连部分的末端填充一个额外的以三角形为底的区域,每个部分都有各自独立的矩形拐角。 |
round | 通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。圆角的半径是线段的宽度。 |
miter | 通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。 |
运行效果如下图所示:
其源代码如下:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 指定线宽
ctx.lineWidth = 40;
// ctx.lineJoin = "bevel";
ctx.beginPath();
ctx.moveTo(50, 70);
ctx.lineTo(220, 120);
ctx.lineTo(50, 170);
ctx.lineJoin = "bevel";
ctx.stroke();
// ctx.lineJoin默认值(miter)
ctx.beginPath();
ctx.moveTo(320, 70);
ctx.lineTo(490, 120);
ctx.lineTo(320, 170);
ctx.lineJoin = "round";
ctx.stroke();
// ctx.lineJoin = "round";
ctx.beginPath();
ctx.moveTo(590, 70);
ctx.lineTo(760, 120);
ctx.lineTo(590, 170);
ctx.lineJoin = "miter";
ctx.stroke();
</script>
在讲解绘制曲线和路径时我们提到过,在绘制多边形时,可通过ctx.closePath()
闭合一个多边形,有时也会通过ctx.moveTo()
到起始点的坐标闭合一个多边形。这两种方法均可实现闭合一个多边形,但在设置了lineJoin
属性后,其运行结果会有所差异,如下图所示:
从上图可以看出,使用ctx.closePath()
闭合多边形才会更符合我们所希望的效果。
通过画布的渲染上下文对象shadowBlur
属性和shadowColor
可指定线路末端样式,其API如下所示:
ctx.shadowBlur = level;
ctx.shadowColor = color;
属性 | 说明 |
---|---|
shadowBlur | 描述模糊效果程度的,float 类型的值。注意:只有设置 shadowColor 属性值为不透明,阴影才会被绘制。 |
shadowColor | 可以转换成 CSS 值的DOMString 字符串。默认值是 fully-transparent black.。 |
下图是设置了该属性的运行效果:
其源代码如下:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);
// 设置阴影效果
ctx.shadowBlur = 30;
ctx.shadowColor = "red";
// 开始绘制路径
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI)
ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI);
ctx.rect(450, 50, 200, 100);
ctx.fillStyle = "green";
ctx.fill();
</script>
在设置阴影效果时,除了阴影颜色和模糊程度外,还可设置阴影的偏移距离,其API如下:
ctx.shadowOffsetX = offset;
ctx.shadowOffsetY = offset;
属性 | 说明 |
---|---|
shadowOffsetX | 阴影水平偏移距离的 float 类型的值。默认值是 0。 |
shadowOffsetY | 阴影垂直偏移距离的 float 类型的值。默认值是 0。 |
下图是设置了该属性的运行效果:
其源代码如下:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);
// 设置阴影效果
ctx.shadowBlur = 30;
ctx.shadowColor = "red";
// 开始绘制路径
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI)
ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI);
ctx.rect(450, 50, 200, 100);
ctx.fillStyle = "green";
ctx.fill();
ctx.translate(0, 160);
// 设置阴影效果
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
// 开始绘制路径
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI)
ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI);
ctx.rect(450, 50, 200, 100);
ctx.fillStyle = "green";
ctx.fill();
ctx.translate(0, 160);
// 设置阴影效果
ctx.shadowOffsetX = -10;
ctx.shadowOffsetY = 10;
// 开始绘制路径
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI)
ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI);
ctx.rect(450, 50, 200, 100);
ctx.fillStyle = "green";
ctx.fill();
</script>
\quad 在之前的内容中,我们使用过ctx.strokeStyle=color
和ctx.fillStyle=color
设置绘制图形时的边框颜色和填充颜色,其实这两个属性除了可以指定颜色之外,还可以指定为渐变对象或填充图案对象,这一节我们讲解设置渐变风格的实现过程。
\quad 渐变风格又包括了“线性渐变”和“径向渐变”两种不同的风格。
\quad 线性渐变是一种颜色过渡方式,它以一条直线(水平或垂直)为轴线,从起点到终点颜色进行顺序渐变。渲染上下文对象提供了建立线性渐变的方法,其定义如下:
CanvasGradient ctx.createLinearGradient(x0, y0, x1, y1);
参数 | 说明 |
---|---|
x0 | 起点的 x 轴坐标 |
y0 | 起点的 y 轴坐标 |
x1 | 终点的 x 轴坐标 |
y1 | 终点的 y 轴坐标 |
\quad 该方法的返回值是一个CanvasGradient对象,该对象包含了一个方法:
void gradient.addColorStop(offset, color);
参数 | 说明 |
---|---|
offset | 偏移位置,0到1之间的值 |
color | 颜色值 |
\quad 我们通过一个例子熟悉一下线性渐变的用法,运行效果如下:
\quad 这个例子使用了线性渐变填充在圆形、矩形和文本。起始颜色是金色(gold),终止颜色是红色(red),其源代码如下:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 建立线性渐变,其坐标相对于画布的坐标
const gradient = ctx.createLinearGradient(0, 50, 0, 200);
gradient.addColorStop(0.05, "gold");
gradient.addColorStop(0.95, "red");
ctx.fillStyle = gradient;
// 绘制圆
ctx.beginPath();
ctx.arc(125, 125, 75, 0, 2 * Math.PI);
ctx.fill();
// 绘制矩形
ctx.translate(200, 0);
ctx.beginPath();
ctx.rect(50, 50, 150, 150);
ctx.fill();
// 绘制文字
ctx.translate(250, 0);
ctx.font = "150px 黑体"
ctx.textBaseline = "top";
ctx.fillText("图形", 0, 50);
</script>
\quad 在这个例子中建立的线性渐变对象,其坐标为(0,50)至(0,200),表示其渐变颜色的方向为从上往下垂直渐变。改变坐标就能改变颜色逐渐变换的方向,例如可以改为水平方向,也可以改为沿着斜线的方向。我们看看下面这个示例:
\quad 渐变对象的addColorStop()
方法用于指定渐变颜色,该方法可以调用n次,第一次表示渐变开始的颜色,第二次表示渐变结束的颜色,第三次会以第二次为开始颜色渐变,以此类推。我们看看下面这个示例:
其源代码如下:
// 建立线性渐变,其坐标相对于画布的坐标
const gradient = ctx.createLinearGradient(0, 50, 0, 200);
gradient.addColorStop(0.05, "red");
gradient.addColorStop(0.5, "gold");
gradient.addColorStop(0.95, "green");
ctx.fillStyle = gradient;
径向渐变是指从起点到终点颜色从内到外进行圆形渐变(从中间向外拉)。渲染上下文对象提供了建立径向渐变的方法,其定义如下:
CanvasGradient ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
参数 | 说明 |
---|---|
x0 | 开始圆形的 x 轴坐标 |
y0 | 开始圆形的 y 轴坐标 |
r0 | 开始圆形的半径 |
x1 | 结束圆形的 x 轴坐标 |
y1 | 结束圆形的 y 轴坐标 |
r1 | 结束圆形的半径 |
\quad 该方法的返回值是一个CanvasGradient对象,该对象包含了方法addColorStop()
与线性渐变的该方法完全一样。接下来看一个径向渐变的例子,运行效果如下:
\quad 径向渐变同样可填充在圆形、矩形和文本等图形中。从上图可知径向渐变是一种颜色从内到外的渐变,从一个起点向所有方向进行的渐变。完整源代码如下:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 建立径向渐变,其坐标相对于画布的坐标
const gradient = ctx.createRadialGradient(125, 125, 0, 125, 125, 160);
gradient.addColorStop(0.05, "gold");
gradient.addColorStop(0.95, "red");
ctx.fillStyle = gradient;
// 绘制圆
ctx.beginPath();
ctx.arc(125, 125, 75, 0, 2 * Math.PI);
ctx.fill();
// 绘制矩形
ctx.translate(200, 0);
ctx.beginPath();
ctx.rect(50, 50, 150, 150);
ctx.fill();
// 绘制文字
ctx.translate(250, 0);
ctx.font = "150px 黑体"
ctx.textBaseline = "top";
ctx.fillText("图形", 0, 50);
</script>
重要提示:渐变对象可同时应用于多个形状或文字,该示例中的圆形、矩形和文字均使用了该渐变对象。在绘制了圆形之后,由于使用了
translate()
将画布坐标系进行了偏移,在绘制矩形和文字时的坐标仍与绘制圆的坐标位置基本一致,因此该渐变对象的坐标可同时适用于此三个形状/文字。关于translate()
的用法我们将在后续的章节中进行介绍。
\quad 使用createRadialGradient()
创建径向渐变对象时可指定开始圆的位置和半径,也可指定结束圆的位置和半径,下面的效果图片就是改变了这几个参数后的运行效果,如下图:
\quad 除了渐变对象外,fillStyle
还可以使用图案/纹理方式填充各类图形,使得画布可以很方面的实现类似于CAD等绘图软件提供的图案填充功能。渲染上下文对象提供了创建图案的方法createPattern()
,其定义如下:
CanvasPattern ctx.createPattern(image, repetition);
参数 | 说明 |
---|---|
image | 填充图案源 |
repetition | 重复方式 |
\quad 填充图案亦可应用于路径、矩形和文字中,下面这个示例将在圆形、矩形和文本绘制中使用图案填充,其效果如下所示:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 装载图案后,渲染图形
loadImage("./images/15.jpg", function (image) {
let pattern = ctx.createPattern(image, "repeat");
ctx.save();
// 设置图案填充样式
ctx.fillStyle = pattern;
// 设置描边时的线宽
ctx.lineWidth = 2;
// 绘制带图案填充的圆
ctx.beginPath();
ctx.arc(125, 125, 75, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
// 绘制带图案填充的矩形
ctx.translate(200, 0);
ctx.beginPath();
ctx.rect(50, 50, 150, 150);
ctx.fill();
ctx.stroke();
// 绘制带图案填充的文字
ctx.translate(250, 0);
ctx.font = "150px 黑体"
ctx.textBaseline = "top";
ctx.fillText("图形", 0, 50);
ctx.strokeText("图形", 0, 50);
ctx.restore();
})
function loadImage(src, callback) {
// 加载并绘制图片
let image = new Image();
image.onload = function () {
callback(image);
}
image.src = src
}
</script>
\quad 在图形系统中,经常应用“填充图案”描述图形中一些特殊的区域或者材质,如下图所示:
\quad 除了直接使用位图作为填充图案之外,还可以使用画布的内容作为填充图案,比较适合一些使用简易图案作为填充图案的场景,下面这个示例展示了使用画板作为填充图案,运行效果如下图所示:
\quad 在这个示例中,使用document.createElement("canvas")
创建了一个临时画布,在这个画布中绘制了两个线条,并使其重复填充至主画布的圆形和矩形中,就实现了上面这个效果,其完整源代码如下:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 设置样式
ctx.fillStyle = getParrent(25, 25);
ctx.lineWidth = 2;
// 绘制带图案填充的圆
ctx.beginPath();
ctx.arc(130, 125, 100, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
// 绘制带图案填充的矩形
ctx.translate(220, 0);
ctx.beginPath();
ctx.rect(50, 50, 500, 180);
ctx.fill();
ctx.stroke();
/**
* 建立画布填充图案对象
*/
function getParrent(width, height) {
// 创建临时画布
let pcanvas = document.createElement("canvas");
pcanvas.width = width;
pcanvas.height = height;
pctx = pcanvas.getContext('2d');
// 绘制线条
pctx.beginPath();
pctx.moveTo(0, 0);
pctx.lineTo(width, height);
pctx.moveTo(width, 0);
pctx.lineTo(0, height);
pctx.strokeStyle = "blue";
pctx.lineWidth = 0.5;
pctx.stroke();
return ctx.createPattern(pcanvas, "repeat");
}
</script>
掌握了使用渐变和图案作为填充效果之后,接下来我们看一个“围棋”实际案例,该围棋棋盘和棋子的绘制效果非常逼真,如下图所示:
在该示例中,使用“图案填充”技术填充了围棋棋盘,使用“径向渐变”实现了棋子的光影效果,这两种技术都是我们刚刚讲述过的,因此其实现的代码也很容易理解,如下所示:
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 棋盘格子大小
let size = 100;
// 使用木质纹理图案作为围棋棋盘
let image = new Image();
image.onload = function () {
draw();
}
image.src = "./images/wood2.png";
/**
* 绘制棋盘和棋子
*/
function draw() {
let pattern = ctx.createPattern(image, "repeat");
ctx.save();
// 绘制带图案填充的圆和矩形
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// 绘制背景网格
drawGrid('lightgray', 10, 10);
// 绘制棋盘
drawBoards();
// 绘制棋子(第3个参数为棋子类型: 1是为黑子,0为白子)
drawPiece(2, 1, 1);
drawPiece(3, 0, 1);
drawPiece(3, 1, 1);
drawPiece(2, 1, 1);
drawPiece(0, 2, 1);
drawPiece(1, 2, 1);
drawPiece(2, 2, 1);
drawPiece(3, 2, 0);
drawPiece(4, 0, 0);
drawPiece(4, 1, 0);
drawPiece(0, 3, 0);
drawPiece(1, 3, 0);
drawPiece(2, 3, 0);
drawPiece(3, 3, 0);
}
/**
* 绘制棋盘函数
*/
function drawBoards() {
ctx.save();
ctx.beginPath();
ctx.lineCap = "square";
for (let i = size; i < canvas.width; i += size) {
ctx.moveTo(i, size);
ctx.lineTo(i, canvas.height)
}
for (let i = size; i < canvas.width; i += size) {
ctx.moveTo(size, i);
ctx.lineTo(canvas.width, i)
}
ctx.lineWidth = 6;
ctx.stroke();
ctx.restore();
}
/**
* 绘制棋子函数
*/
function drawPiece(x, y, type) {
// 计算棋子的坐标
x = (x + 1) * 100;
y = (y + 1) * 100;
let radius = size/2 - 2;
// 建立径向渐变,其坐标相对于画布的坐标
const gradient = ctx.createRadialGradient(x + radius / 2, y - radius / 2, 0, x, y, radius);
// type == 1是为黑子,否则为白子
gradient.addColorStop(0, type == 1 ? "#B9B9B9" : "#FFFFFF");
gradient.addColorStop(0.95, type == 1 ? "#000000" : "#DCDCDC");
gradient.addColorStop(0.97, type == 1 ? "#000000" : "#DCDCDC00");
gradient.addColorStop(1, type == 1 ? "#00000000" : "#DCDCDC00");
// 绘制圆
ctx.save();
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.restore();
}
</script>
\quad 本章讲解了Canvas所提供的的一些渲染效果的用法,这些效果不仅仅是增强了图形的立体感和视觉效果,更重要的是让图形更具有真实感,使图形看起来更接近现实世界,因此这些这些效果在图形系统中得到了广泛应用。
本章内容使用了Canvas 2D API以下属性和方法:
属性值 | 说明 |
---|---|
ctx.lineCap | 线条末端样式 |
ctx.lineJoin | 线条连接风格 |
ctx.shadowBlur | 模糊程度 |
ctx.shadowColor | 阴影颜色 |
ctx.shadowOffsetX | 阴影水平偏移距离 |
ctx.shadowOffsetY | 阴影垂直偏移距离 |
方法名 | 说明 |
---|---|
ctx.createLinearGradient(x0, y0, x1, y1) | 创建线性渐变样式 |
ctx.createRadialGradient(x0, y0, r0, x1, y1, r1) | 创建径向渐变样式 |
gradient.addColorStop(offset, color) | 添加一个由偏移值和颜色值指定的断点到渐变 |
ctx.createPattern(image, repetition) | 创建填充图像样式 |
按照以下格式要求在Canvas中绘制你喜欢的一本书的名字:
\quad 在本章渐变与图案的示例中,均是将这两种风格应用在了填充样式ctx.fillStyle
中,而且示例中的几何对象也是几个简单的几何对象。其实渐变与图案是可以应用于更复杂的路径,同时还可以应用于描边样式ctx.strokeStyle
的,下面这张图片就是将渐变和图案应用到了贝塞尔曲线中了。
你也动手试一试吧。
▶ 系列教程及代码资料:http://GraphAnyWhere.com
▶ 图形系统开发实战课程:基础篇——图形系统概述
▶ 图形系统开发实战课程:基础篇——1.绘制基本图形
▶ 图形系统开发实战课程:基础篇——2.绘制文字
▶ 图形系统开发实战课程:基础篇——3.绘制图像
▶ 图形系统开发实战课程:基础篇——4.绘制曲线和路径
作者信息
作者 : 图形开发学院
CSDN: https://blog.csdn.net/2301_81340430?type=blog
官网:http://graphanywhere.com