想要达到的效果如下
有一个基色C,例如(0,255,0)纯绿色,为每个顶点分配一个实数值h,表示顶点颜色的强度。h在[0.0, 1.0]
范围内,其中0.0
表示能够获得的最暗的着色效果黑色,1.0表示能够获得的最亮的着色效果(原始颜色C,不是白色)。
以知三角形基色C以及在某个像素处的强度值h,为了计算该像素准确的颜色着色效果,按颜色通道进行乘法运算。
主要思想是遍历三条边,三个点h值为Ha,Hb,Hc,那么边ab上的h值则在Ha,Hb之间过渡。其他类似,我们可以做线性函数(当然其他都是可以的,哪怕是曲线函数,可能绘制出来更漂亮)。
我们有一个线性函数x=f(y)
,我们知道这个函数在三角形顶点处的值,我们想要计算沿三角形的x值。我们可以用相似的方式计算沿三角形边的h值,使用Interpolate插值函数将y作为自变量以及将h作为因变量。
= Interpolate(y0, x0, y1, x1)
x01 = Interpolate(y0, h0, y1, h1)
h01
= Interpolate(y1, x1, y2, x2)
x12 = Interpolate(y1, h1, y2, h2)
h12
= Interpolate(y0, x0, y2, x2)
x02 = Interpolate(y0, h0, y2, h2) h02
绘制内部前面做法就是绘制水平线段。对于每条线段,我们知道x_left和x_right,现在我们还知道了h_left,h_right。在从左到右迭代并使用基色绘制每个像素,还需要为线段的每个像素计算h值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>光栅化渐变着色三角形</title>
</head>
<body>
<div class="centered">
<canvas id="canvas" width=600 height=600 style="border: 1px grey solid"></canvas>
</div>
<script>
"use strict";
let canvas = document.getElementById("canvas");
let canvas_context = canvas.getContext("2d");
let canvas_buffer = canvas_context.getImageData(0, 0, canvas.width, canvas.height);
// 颜色对象
function Color(r, g, b) {
return {
, g, b,
rmul: function (n) { return new Color(this.r * n, this.g * n, this.b * n); },
;
}
}
// 设置像素值到画布buffer
function PutPixel(x, y, color) {
= canvas.width / 2 + (x | 0);
x = canvas.height / 2 - (y | 0) - 1;
y
if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) {
return;
}
let offset = 4 * (x + canvas_buffer.width * y);
.data[offset++] = color.r;
canvas_buffer.data[offset++] = color.g;
canvas_buffer.data[offset++] = color.b;
canvas_buffer.data[offset++] = 255; // Alpha = 255 (full opacity)
canvas_buffer
}
// 将buffer内容刷到画布上
function UpdateCanvas() {
.putImageData(canvas_buffer, 0, 0);
canvas_context
}
// 获得新的点对象
function Pt(x, y, h) {
return { x, y, h };
}
// 线性插值函数
function Interpolate(i0, d0, i1, d1) {
if (i0 == i1) {
return [d0];
}
let values = [];
let a = (d1 - d0) / (i1 - i0);
let d = d0;
for (let i = i0; i <= i1; i++) {
.push(d);
values+= a;
d
}
return values;
}
// 画点p0到点p1线段
function DrawLine(p0, p1, color) {
let dx = p1.x - p0.x, dy = p1.y - p0.y;
if (Math.abs(dx) > Math.abs(dy)) { // 偏向横着
// The line is horizontal-ish. Make sure it's left to right.
if (dx < 0) { let swap = p0; p0 = p1; p1 = swap; }
// Compute the Y values and draw.
let ys = Interpolate(p0.x, p0.y, p1.x, p1.y);
for (let x = p0.x; x <= p1.x; x++) {
PutPixel(x, ys[(x - p0.x) | 0], color);
}else { // 偏向竖着
} // The line is verical-ish. Make sure it's bottom to top.
if (dy < 0) { let swap = p0; p0 = p1; p1 = swap; }
// Compute the X values and draw.
let xs = Interpolate(p0.y, p0.x, p1.y, p1.x);
for (let y = p0.y; y <= p1.y; y++) {
PutPixel(xs[(y - p0.y) | 0], y, color);
}
}
}
// 画三角形外框
function DrawWireframeTriangle(p0, p1, p2, color) {
DrawLine(p0, p1, color);
DrawLine(p1, p2, color);
DrawLine(p0, p2, color);
}
function DrawShadedTriangle(p0, p1, p2, color) {
// Sort the points from bottom to top.
if (p1.y < p0.y) { let swap = p0; p0 = p1; p1 = swap; }
if (p2.y < p0.y) { let swap = p0; p0 = p2; p2 = swap; }
if (p2.y < p1.y) { let swap = p1; p1 = p2; p2 = swap; }
// Compute X coordinates and H values of the edges.
let x01 = Interpolate(p0.y, p0.x, p1.y, p1.x);
let h01 = Interpolate(p0.y, p0.h, p1.y, p1.h);
let x12 = Interpolate(p1.y, p1.x, p2.y, p2.x);
let h12 = Interpolate(p1.y, p1.h, p2.y, p2.h);
let x02 = Interpolate(p0.y, p0.x, p2.y, p2.x);
let h02 = Interpolate(p0.y, p0.h, p2.y, p2.h);
// Merge the two short sides.
.pop();
x01let x012 = x01.concat(x12);
.pop();
h01let h012 = h01.concat(h12);
// Determine which is left and which is right.
let x_left, x_right;
let h_left, h_right;
let m = (x02.length / 2) | 0;
if (x02[m] < x012[m]) {
= x02; x_right = x012;
x_left = h02; h_right = h012;
h_left else {
} = x012; x_right = x02;
x_left = h012; h_right = h02;
h_left
}
// Draw horizontal segments.
for (let y = p0.y; y <= p2.y; y++) {
let xl = x_left[y - p0.y] | 0;
let xr = x_right[y - p0.y] | 0;
let h_segment = Interpolate(xl, h_left[y - p0.y], xr, h_right[y - p0.y]);
for (let x = xl; x <= xr; x++) {
PutPixel(x, y, color.mul(h_segment[x - xl]));
}
}
}
let p0 = new Pt(-200, -250, 0.3);
let p1 = new Pt(200, 50, 0.1);
let p2 = new Pt(20, 250, 1.0);
// 着色三角形
DrawShadedTriangle(p0, p1, p2, new Color(0, 255, 0));
UpdateCanvas();
</script>
</body>
</html>