1 )回顾 WebGL 三种面的适应场景
2 )适合 TRIANGLES 单独三角形的的模型
3 )TRIANGLE_STRIP 和TRIANGLE_FAN 的优点和缺点
_gl.drawArrays(_gl.TRIANGLES, 0, object.count);
4 )图形转面的基本步骤
5 )使用TRIANGLES 独立三角形的方式,将图形转成面
原理
举个例子
已知:逆时针绘图的路径G
求:将其变成下方网格的方法
1 )绘制路径G
const pathData = [
0, 0,
600, 0,
600, 100,
100, 100,
100, 500,
500, 500,
500, 300,
300, 300,
300, 400,
200, 400,
200, 200,
600, 200,
600, 600,
0, 600
];
2 )在webgl 中绘制正方形
从pathData 数据中我们可以看出,路径G的宽高都是600,是一个正方形。
所以,我可以将路径G映射到webgl 画布的一个正方形中。
这个正方形的高度我可以暂且定为1,那么其宽度就应该是高度除以canvas画布的宽高比。
//宽高比
const ratio = canvas.width / canvas.height;
// 正方形高度 这里 1 实际上是整个canvas画布高度的一半
const rectH = 1.0;
// 正方形宽度 (这里是由宽高比计算出来的) 在webgl坐标系上宽度上一个单位和高度上一个单位可能是不一样的,是根据canvas画布来走的
const rectW = rectH / ratio;
3 )正方形的定位,把正方形放在webgl画布的中心
获取正方形尺寸的一半,然后求出其x、y方向的两个极值即可。
//正方形宽高的一半
const [halfRectW, halfRectH] = [rectW / 2, rectH / 2];
//两个极点
const minX = -halfRectW;
const minY = -halfRectH;
const maxX = halfRectW;
const maxY = halfRectH;
4 )利用之前的Poly对象绘制正方形,测试一下效果
// 这个 Poly 对象的实现参考前面webgl博文
const rect = new Poly({
gl,
vertices: [
minX, maxY,
minX, minY,
maxX, minY,
maxX, maxY,
],
});
rect.draw();
5 )建立x轴和y轴比例尺
const scaleX = ScaleLinear(0, minX, 600, maxX);
const scaleY = ScaleLinear(0, minY, 600, maxY);
function ScaleLinear(ax, ay, bx, by) {
const delta = {
x: bx - ax,
y: by - ay,
};
const k = delta.y / delta.x;
const b = ay - ax * k;
return function (x) {
return k * x + b;
};
}
ScaleLinear(
0, minX,
600, maxX
);
6 )将路径G中的像素数据解析为 webgl 数据
const glData = [];
for (let i = 0; i < pathData.length; i += 2) {
// 把每个点做一个解析,并存入集合中
glData.push(scaleX(pathData[i]), scaleY(pathData[i + 1]));
}
const path = new Poly({
gl,
vertices: glData,
types: ["POINTS", "LINE_LOOP"],
});
path.draw();
7 )将图形网格化,把图形变成面
建立了一个ShapeGeo 对象,用于将图形网格化
const shapeGeo = new ShapeGeo(glData)
ShapeGeo.js
export default class ShapeGeo {
constructor(pathData = []) {
this.pathData = pathData; // 路径数据 平铺展开
this.geoData = []; // 将路径数据 转换成 对象型数组 方便操作
this.triangles = []; // 存储的三角形集合
this.vertices = []; // 平铺展开的 独立三角形数据,后面会交给缓冲区进行渲染
this.parsePath(); // 将 pathData 转换成 geoData
this.update(); // 更新方法,默认执行
}
update() {
this.vertices = [];
this.triangles = [];
this.findTriangle(0); // 寻找独立三角形
this.upadateVertices() // 从独立三角形区域解析出 vertices
}
parsePath() {
this.geoData = [];
const { pathData, geoData } = this
for (let i = 0; i < pathData.length; i += 2) {
geoData.push({ x: pathData[i], y: pathData[i + 1] })
}
}
findTriangle(i) {
const { geoData, triangles } = this;
const len = geoData.length;
if (geoData.length <= 3) {
triangles.push([...geoData]);
} else {
// 对位置做了一个加工,因为如果从最后一个点开始找,最后一个点的下一个肯定是第一个,这里对顶点长度进行取余操作,循环进行
const [i0, i1, i2] = [
i % len,
(i + 1) % len,
(i + 2) % len
];
const triangle = [
geoData[i0],
geoData[i1],
geoData[i2],
];
// 判断找的三角形是否符合条件:在左手边 && 在三角形中是否有点
if (this.cross(triangle) > 0 && !this.includePoint(triangle)) {
triangles.push(triangle); // 存储三角形
geoData.splice(i1, 1); // 移除 B点 (第二个点)
}
this.findTriangle(i1); // 接着再从第二个点开始寻找, 这里即便第二个点被删了,之前的i2也会是这里的i1
}
}
includePoint(triangle) {
// 遍历所有顶点
for (let ele of this.geoData) {
// 判断当前三角形是否包含当前点,如果否
if (!triangle.includes(ele)) {
// 判断其他点是否在这个三角形之中
if (this.inTriangle(ele, triangle)) {
return true;
}
}
}
return false;
}
inTriangle(p0, triangle) {
let inPoly = true;
// 遍历三角形点
for (let i = 0; i < 3; i++) {
const j = (i + 1) % 3; // j 是下一个点,这里 余3运算 和上面一样的原理
const [p1, p2] = [triangle[i], triangle[j]];
// p0 点是否在三角形每一条边的 右侧 或 左侧
if (this.cross([p0, p1, p2]) < 0) {
inPoly = false;
break
}
}
return inPoly;
}
// 这个就是这面讲到的 θ > 0的原理中,只要右边 > 0了,θ 就 > 0
cross([p0, p1, p2]) {
const [ax, ay, bx, by] = [
p1.x - p0.x,
p1.y - p0.y,
p2.x - p0.x,
p2.y - p0.y,
];
return ax * by - bx * ay;
}
upadateVertices() {
const arr = []
this.triangles.forEach(triangle => {
for (let { x, y } of triangle) {
arr.push(x, y)
}
})
this.vertices = arr
}
}
属性
方法
绘制G形面
const face = new Poly({
gl,
vertices: shapeGeo.vertices,
types: ["TRIANGLES"],
});
face.draw();
<canvas id="canvas">canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
}
script>
<script id="fragmentShader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1,1,0,1);
}
script>
<script type="module">
import { initShaders, ScaleLinear } from "./utils/utils.js";
import Poly from "../utils/Poly.js";
import ShapeGeo from "../utils/ShapeGeo.js";
const canvas = document.querySelector("#canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 获取着色器文本
const vsSource = document.querySelector("#vertexShader").innerText;
const fsSource = document.querySelector("#fragmentShader").innerText;
//三维画笔
const gl = canvas.getContext("webgl");
//初始化着色器
initShaders(gl, vsSource, fsSource);
//声明颜色 rgba
gl.clearColor(0, 0, 0, 1);
//刷底色
gl.clear(gl.COLOR_BUFFER_BIT);
//路径G-逆时针
const pathData = [
0, 0,
0, 600,
600, 600,
600, 200,
200, 200,
200, 400,
300, 400,
300, 300,
500, 300,
500, 500,
100, 500,
100, 100,
600, 100,
600, 0,
];
//宽高比
const ratio = canvas.width / canvas.height;
//正方形高度
const rectH = 1.0;
//正方形宽度
const rectW = rectH / ratio;
//正方形宽高的一半
const [halfRectW, halfRectH] = [rectW / 2, rectH / 2];
//两个极点
const minX = -halfRectW;
const minY = -halfRectH;
const maxX = halfRectW;
const maxY = halfRectH;
//正方形
const rect = new Poly({
gl,
vertices: [
minX, maxY,
minX, minY,
maxX, minY,
maxX, maxY,
],
});
rect.draw();
//建立比例尺
const scaleX = ScaleLinear(
0, minX,
600, maxX
);
const scaleY = ScaleLinear(
0, maxY,
600, minY
);
//将路径G中的像素数据解析为webgl数据
const glData = [];
for (let i = 0; i < pathData.length; i += 2) {
glData.push(scaleX(pathData[i]), scaleY(pathData[i + 1]));
}
const path = new Poly({
gl,
vertices: glData,
types: ["POINTS", "LINE_LOOP"],
});
path.draw();
const shapeGeo = new ShapeGeo(glData)
const face = new Poly({
gl,
vertices: shapeGeo.vertices,
types: ["TRIANGLES"],
});
face.draw();
script>