WebGL学习01-从真实生活的3D绘画开始

972 阅读4分钟

从真实生活的3D绘画开始

我们如何在真实生活中绘制一个3D物体

我们在真实生活中是如何绘制3D物体的呢,比如绘制一个四面体。

首先我们得知道四面体的几何特性,它有4个顶点,假设我们要绘制的是一个等边的四面体,那么底面就是一个等边三角形,如下图蓝色顶点的三角形所示,第四个顶点从顶部看位于等边三角形的中心,并且与其他三个顶点的距离和底边三角形边长相同,如下图红色顶点所示。

然后我们选择一个视角,利用一些透视的技法画出其在纸上的平面框图

最后我们给每个面涂上颜色,被遮挡的面实际上并不会被涂色,比如底面。但是如果你要画一个透明的四面体,那么被遮挡的面的颜色就要被考虑到了。

使用计算机能够理解的方式来描述绘制过程

接下来我们对上述的过程做一些细化和抽象,变得更容易被计算机理解。

这里我们将上色的方式设定为一个点一个点的上色,类似于打印机,大概如下图所示,我们以最小的网格为上色单位,根据网格所在的面涂不同的颜色

你也可以将这些上色的最小点理解为像素点,对于电子显示屏幕而言,最小的上色单位就是一个像素点。

然后我们将步骤细化为计算机可以理解的方式

1. 计算几何体顶点集合,通过构建一个坐标系,根据等边三角形的特性,可以计算出各个点的坐标。

这一步我们可以得到四个顶点,用Vertex前缀来表示

Vertex1: (x1, y1, z1)
Vertex2: (x2, y2, z2)
Vertex3: (x3, y3, z3)
Vertex4: (x4, y4, z4)

2. 对四个顶点进行移动,透视等变换操作,得到在画布上的映射点坐标,这里我们假设一个公式F(x)可以做到这样的计算。进过这一步,我们可以得到画布坐标系上的四个坐标点,用Point前缀来表示

Point1: (x1, y1) = F(Vertex1)
Point2: (x2, y2) = F(Vertex2)
Point3: (x3, y3) = F(Vertex3)
Point4: (x4, y4) = F(Vertex4)

3. 画布上每三个点组成一个三角形,一共可以得到四个三角形,如下图四个蓝色三角形所示

在这一步,我们得到4个三角形,用Triangle前缀来表示

Triangle1: (Point1, Point2, Point3)
Triangle2: (Point1, Point2, Point4)
Triangle3: (Point1, Point3, Point4)
Triangle4: (Point2, Point3, Point4)

4. 使用网格对每个三角形进行分割,得到最小的上色点并上色

这一步我们分割每个三角形,为每个被三角形覆盖的点上色,如果一个上色点对应多个三角形,使用最上面三角形颜色进行上色。上图中网格间隔比较大,如果你严格按照三角形所占用的网格上色,会发现绘制出来的图形边缘呈阶梯状,这也就是我们常说的边缘锯齿问题。

使用计算机的语言来描述绘制过程

最后我们用伪代码来表示一下这个过程,如果你觉得伪代码会让你的思路更加混乱,你可以忽略他们,等到真正的代码学习阶段再了解这些。

// ================== 使用的数据结构 ==================
// 几何体顶点
Vertex {
    float x;
    float y;
    float z;
}

// 画布上的点
Point {
    float x;
    float y;
}

// 可上色点
Pixel {
    float x;
    float y;
}

// 三角形
Triangle {
    Vertex vertex1;
    Vertex vertex2;
    Vertex vertex3;
}

// 颜色
Color {
    float r,
    float g,
    float b
}


// ================== 定义的方法 ==================

// 此方法将顶点Vertex变换成画布上的点Point
Point perspectiveTransform(Vertex vertex);

// 获得三角形所有可上色点
List<Pixel> pixelsFromTriangle(Triangle triangle);

// 绘制单个上色点
func drawPixel(Pixel pixel, Color color);

// ================== 绘制流程 ==================

// 原始的顶点信息
List<Vertex> vertices = [vertex1, vertex2, vertex3, vertex4];

// 顶点变换到画布上
List<Point> pointsOnCanvas = [perspectiveTransform(vertex1),perspectiveTransform(vertex2),perspectiveTransform(vertex3),perspectiveTransform(vertex4)];

// 生成4个三角形
Triangle triangle1 = Triangle(pointsOnCanvas[0], pointsOnCanvas[1], pointsOnCanvas[2]);
Triangle triangle2 = Triangle(pointsOnCanvas[0], pointsOnCanvas[1], pointsOnCanvas[3]);
Triangle triangle3 = Triangle(pointsOnCanvas[0], pointsOnCanvas[2], pointsOnCanvas[3]);
Triangle triangle4 = Triangle(pointsOnCanvas[1], pointsOnCanvas[2], pointsOnCanvas[3]);

// 获得4个三角形可以上色的点
List<Pixel> pixels = [];
pixels.addMany(pixelsFromTriangle(triangle1));
pixels.addMany(pixelsFromTriangle(triangle2));
pixels.addMany(pixelsFromTriangle(triangle3));
pixels.addMany(pixelsFromTriangle(triangle4));

// 给每个上色点上色
for (let pixel in pixels) {
    // 如果上色点在最顶上,才上色
    if (pixel.isTop()) {
        // 用上色点所在三角形的颜色进行上色
        drawPixel(pixel, colorForTriangle);
    }
}

看完后你可能会有很多疑问,比如perspectiveTransform应该怎么实现,如何获得一个三角形所包含的可上色点,上色点是否位于最上层又是怎么界定的。如果你以2D绘图的思路去思考,可能会发现这些问题都很难以高效的方式去解决。不过这些正是WebGL技术发挥作用的地方,在下一小节中将会简明的阐述WebGL是如何解决这些问题的。