在p5.js中不使用WEBGL实现图像到任意四边形的映射
在p5.js中实现非矩形四边形的图像映射(无需WEBGL)
核心思路:用透视变换替代WEBGL
WEBGL确实能快速实现纹理映射,但如果只想用2D画布,**透视变换(Perspective Transformation)**是核心解决方案——它可以把矩形图像的四个顶点精准映射到任意四边形的四个顶点,实现带透视效果的图像变形。
实现步骤
- 计算透视变换矩阵:根据原始图像的四个顶点(矩形)和目标四边形的四个顶点,求解出能完成坐标映射的变换矩阵
- 优化像素渲染:放弃暴力遍历所有像素,只针对四边形的边界范围计算,结合离屏画布缓存原始图像,用像素数组直接写入的方式提升渲染效率
示例代码
let img; // 目标四边形的四个顶点(顺时针:左上、右上、右下、左下) let quadPoints = []; let offscreenCanvas; function preload() { img = loadImage('your-image.jpg'); // 替换为你的图像路径 } function setup() { createCanvas(800, 600); // 初始化自定义四边形坐标,可根据需求修改 quadPoints = [ createVector(100, 100), createVector(700, 150), createVector(650, 500), createVector(150, 450) ]; // 创建离屏画布缓存原始图像,避免重复加载 offscreenCanvas = createGraphics(img.width, img.height); offscreenCanvas.image(img, 0, 0); offscreenCanvas.loadPixels(); } function draw() { background(220); // 计算透视变换矩阵 let matrix = calculatePerspectiveMatrix( // 原始图像的四个顶点 [createVector(0,0), createVector(img.width,0), createVector(img.width,img.height), createVector(0,img.height)], quadPoints ); loadPixels(); // 只遍历四边形的边界范围,减少无效计算 let minX = Math.min(...quadPoints.map(p => p.x)); let maxX = Math.max(...quadPoints.map(p => p.x)); let minY = Math.min(...quadPoints.map(p => p.y)); let maxY = Math.max(...quadPoints.map(p => p.y)); for (let x = minX; x < maxX; x++) { for (let y = minY; y < maxY; y++) { let currentPoint = createVector(x, y); // 检查当前点是否在目标四边形内 if (pointInQuad(currentPoint, quadPoints)) { // 逆变换:将画布点映射回原始图像坐标 let imgCoord = applyInversePerspectiveMatrix(currentPoint, matrix); // 确保坐标在图像范围内 if (imgCoord.x >= 0 && imgCoord.x < img.width && imgCoord.y >=0 && imgCoord.y < img.height) { // 读取离屏画布的像素颜色 let pixelIdx = (floor(imgCoord.y) * offscreenCanvas.width + floor(imgCoord.x)) * 4; let r = offscreenCanvas.pixels[pixelIdx]; let g = offscreenCanvas.pixels[pixelIdx+1]; let b = offscreenCanvas.pixels[pixelIdx+2]; let a = offscreenCanvas.pixels[pixelIdx+3]; // 写入主画布像素数组 let mainPixelIdx = (y * width + x) * 4; pixels[mainPixelIdx] = r; pixels[mainPixelIdx+1] = g; pixels[mainPixelIdx+2] = b; pixels[mainPixelIdx+3] = a; } } } } updatePixels(); // 可选:绘制四边形边框用于调试 stroke(255, 0, 0); noFill(); beginShape(); quadPoints.forEach(p => vertex(p.x, p.y)); endShape(CLOSE); } // 计算透视变换矩阵(基于四点映射的线性方程组求解) function calculatePerspectiveMatrix(srcPoints, dstPoints) { let A = []; let B = []; // 为每个点添加约束方程 function addConstraint(src, dst) { A.push([src.x, src.y, 1, 0, 0, 0, -src.x*dst.x, -src.y*dst.x]); B.push(dst.x); A.push([0, 0, 0, src.x, src.y, 1, -src.x*dst.y, -src.y*dst.y]); B.push(dst.y); } srcPoints.forEach((s, i) => addConstraint(s, dstPoints[i])); return solveLinearSystem(A, B); } // 简化的高斯消元法求解线性方程组 function solveLinearSystem(A, B) { let n = A.length; let aug = A.map((row, i) => [...row, B[i]]); // 前向消元 for (let col = 0; col < n; col++) { // 寻找主元行 let pivotRow = col; for (let row = col; row < n; row++) { if (Math.abs(aug[row][col]) > Math.abs(aug[pivotRow][col])) pivotRow = row; } // 交换主元行与当前行 [aug[col], aug[pivotRow]] = [aug[pivotRow], aug[col]]; // 归一化主元行 let div = aug[col][col]; for (let j = col; j <= n; j++) aug[col][j] /= div; // 消去其他行的当前列 for (let row = 0; row < n; row++) { if (row !== col && aug[row][col] !== 0) { let factor = aug[row][col]; for (let j = col; j <= n; j++) aug[row][j] -= factor * aug[col][j]; } } } return aug.map(row => row[n]); } // 应用逆透视变换,将画布坐标映射回图像坐标 function applyInversePerspectiveMatrix(p, matrix) { let [a,b,c,d,e,f,g,h] = matrix; let denom = g * p.x + h * p.y + 1; let u = (a * p.x + b * p.y + c) / denom; let v = (d * p.x + e * p.y + f) / denom; return createVector(u, v); } // 射线法判断点是否在四边形内 function pointInQuad(p, quad) { let inside = false; for (let i = 0, j = quad.length - 1; i < quad.length; j = i++) { let [xi, yi] = [quad[i].x, quad[i].y]; let [xj, yj] = [quad[j].x, quad[j].y]; let intersect = ((yi > p.y) !== (yj > p.y)) && (p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; }
性能优化建议
- 缓存变换矩阵:如果四边形顶点不实时变化,只在初始化或顶点修改时计算一次矩阵,无需每帧重复计算
- 缩小遍历范围:只遍历四边形的外接矩形区域,避免整个画布的无效计算
- 整数坐标取整:对映射后的图像坐标取整,减少浮点运算的性能损耗
内容的提问来源于stack exchange,提问作者Game_Freeze
相关产品推荐
相关产品推荐

