WebGL打开 3D 世界的大门(五):正射投影

从这篇文章开始我们就可以看到3D图像,我们正式进入3D的世界。

上一节我们介绍了2D图形的旋转,缩放,和平移,也就是说绘制的点是固定的,我们通过修改变换矩阵达到变换的目的。我们继续沿着这个思路绘制3D的E字。

绘制3D的E字

我们要在2维的基础上进行绘制,首先我们要修改顶点着色器,将2维升级为3维,需要注意z轴的显示范围也是-1到1,我们假定z轴的取值范围是0到1000到我们距离近的为0,远处为1000.接下来需要调整顶点着色器。

修改顶点着色器

我们要升一个维度,将原来的2维向量,变为3维向量,将原来2D的33矩阵,变成3D的44矩阵,注意平面图形只有一个旋转方向,到了3维就有3个旋转方向,有兴趣的同学可以思考一下,如果进入4维空间有几个旋转方向呢?

ini 复制代码
// an attribute will receive data from a buffer
attribute vec3 a_position;
// 坐标转换
uniform vec3 u_resolution;
// 平移矩阵
uniform mat4 matrix_translate;
// x轴旋转矩阵
uniform mat4 matrix_rotateX;
// y轴旋转矩阵
uniform mat4 matrix_rotateY;
// z轴旋转矩阵
uniform mat4 matrix_rotateZ;
// 缩放矩阵
uniform mat4 matrix_scale;
// all shaders have a main function
varying vec4 v_color;
void main() {
    // gl_Position is a special variable a vertex shader
    // is responsible for setting
    // 从像素坐标转换到 0.0 到 1.0
    vec3 position = (matrix_translate * matrix_rotateX * matrix_rotateY * matrix_rotateZ * matrix_scale * vec4(a_position, 1)).xyz;
    vec3 zeroToOne = position - u_resolution / 2.0;

    // 再把 0->1 转换 0->2
    vec3 zeroToTwo = zeroToOne / u_resolution * 2.0;

    // 把 0->2 转换到 -1->+1 (裁剪空间)
    vec3 clipSpace = zeroToTwo * vec3(1.0,-1.0,-1.0);

    gl_Position = vec4(clipSpace, 1);
    gl_PointSize = 10.0;
    v_color = vec4(clipSpace,1.0);
}
添加旋转矩阵

我们定义了坐标转换矩阵,平移矩阵,X,Y,Z轴的旋转矩阵,和缩放矩阵,上一节我们介绍了2D变化的旋转矩阵,其实3D的很简单,这就是为什么我将三个维度写开的原因。

ini 复制代码
xRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
        1, 0, 0, 0,
        0, c, s, 0,
        0, -s, c, 0,
        0, 0, 0, 1,
    ];
},

yRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
        c, 0, -s, 0,
        0, 1, 0, 0,
        s, 0, c, 0,
        0, 0, 0, 1,
    ];
},

zRotation: function(angleInRadians) {
    var c = Math.cos(angleInRadians);
    var s = Math.sin(angleInRadians);

    return [
        c, s, 0, 0,
        -s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1,
    ];
},

这样非常方便理解,如果哪个轴不变,就将这个对应的轴赋值成,单位向量。另外两个轴旋转。接下来我们定义E的顶点

定义顶点坐标
markdown 复制代码
   // 横线(顶部)
    0, 0, 500,
    300, 0, 500,
    300, 30, 500,
    0, 0, 500,
    0, 30, 500,
    300, 30, 500,

    // 竖线(左侧)
    0, 0, 500,
    0, 400, 500,
    30, 400, 500,
    0, 0, 500,
    30, 0, 500,
    30, 400, 500,

    // 横线(底部)
    0, 400, 500,
    300, 400, 500,
    0, 370, 500,
    0, 370, 500,
    300, 370, 500,
    300, 400, 500,

    // 中间横线
    0, 185, 500,
    200, 185, 500,
    200, 215, 500,
    0, 215, 500,
    0, 185, 500,
    200, 215, 500,

    // ===== 后面的 E (z=400) =====
    // 横线(顶部)
    0, 0, 400,
    300, 0, 400,
    300, 30, 400,
    0, 0, 400,
    0, 30, 400,
    300, 30, 400,

    // 竖线(左侧)
    0, 0, 400,
    0, 400, 400,
    30, 400, 400,
    0, 0, 400,
    30, 0, 400,
    30, 400, 400,

    // 横线(底部)
    0, 400, 400,
    300, 400, 400,
    0, 370, 400,
    0, 370, 400,
    300, 370, 400,
    300, 400, 400,

    // 中间横线
    0, 185, 400,
    200, 185, 400,
    200, 215, 400,
    0, 215, 400,
    0, 185, 400,
    200, 215, 400,

    // ===== 侧面(连接前后 E)=====
    // 顶部横线侧面
    0, 0, 500,  300, 0, 500,  300, 0, 400,
    0, 0, 500,  300, 0, 400,  0, 0, 400,

    300, 0, 500,  300, 30, 500,  300, 30, 400,
    300, 0, 500,  300, 30, 400,  300, 0, 400,

    0, 0, 500,  0, 30, 500,  0, 30, 400,
    0, 0, 500,  0, 30, 400,  0, 0, 400,

    0, 30, 500,  300, 30, 500,  300, 30, 400,
    0, 30, 500,  300, 30, 400,  0, 30, 400,

    // 左侧竖线侧面
    0, 0, 500,  0, 400, 500,  0, 400, 400,
    0, 0, 500,  0, 400, 400,  0, 0, 400,

    30, 0, 500,  30, 400, 500,  30, 400, 400,
    30, 0, 500,  30, 400, 400,  30, 0, 400,

    0, 0, 500,  30, 0, 500,  30, 0, 400,
    0, 0, 500,  30, 0, 400,  0, 0, 400,

    0, 400, 500,  30, 400, 500,  30, 400, 400,
    0, 400, 500,  30, 400, 400,  0, 400, 400,

    // 底部横线侧面
    0, 370, 500,  300, 370, 500,  300, 370, 400,
    0, 370, 500,  300, 370, 400,  0, 370, 400,

    0, 400, 500,  300, 400, 500,  300, 400, 400,
    0, 400, 500,  300, 400, 400,  0, 400, 400,

    // 中间横线侧面
    0, 185, 500,  200, 185, 500,  200, 185, 400,
    0, 185, 500,  200, 185, 400,  0, 185, 400,

    0, 215, 500,  200, 215, 500,  200, 215, 400,
    0, 215, 500,  200, 215, 400,  0, 215, 400,

    200, 185, 500,  200, 215, 500,  200, 215, 400,
    200, 185, 500,  200, 215, 400,  200, 185, 400,
    

有了以上材料,我们就可以像绘制2D图形一样绘制3D图形,效果如下:

代码地址:https://gitee.com/feng-lianxiang/webgl-3d-world/blob/master/part_5.html 我们可以让这个文字旋转起来。

添加颜色缓冲

看看我们3D实现了,不过等等,我们用的每一个面的颜色都是跟顶点相关的。现在我们把每个面都增加个对应的颜色看看什么效果。 还是以前的过程,创建缓冲区,向缓冲区添加数据,获取颜色属性,激活颜色属性,告诉程序怎么从缓冲区取值。

ini 复制代码
var colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(colorData), gl.STATIC_DRAW);
var colorLocation = gl.getAttribLocation(program, "a_color");
gl.enableVertexAttribArray(colorLocation);
var size = 3;                 // 3 components per iteration
var type = gl.UNSIGNED_BYTE;  // the data is 8bit unsigned values
var normalize = true;         // normalize the data (convert from 0-255 to 0-1)
var stride = 0;               // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0;               // start at the beginning of the buffer
gl.vertexAttribPointer(
    colorLocation, size, type, normalize, stride, offset);

颜色值的数组如下:

csharp 复制代码
var colorData = [
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
// top rung front
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,

// middle rung front
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,
200,  70, 120,

// left column back
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,

// top rung back
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,

// middle rung back
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,
80, 70, 200,

// top
70, 200, 210,
70, 200, 210,
70, 200, 210,
70, 200, 210,
70, 200, 210,
70, 200, 210,

// top rung right
200, 200, 70,
200, 200, 70,
200, 200, 70,
200, 200, 70,
200, 200, 70,
200, 200, 70,

// under top rung
210, 100, 70,
210, 100, 70,
210, 100, 70,
210, 100, 70,
210, 100, 70,
210, 100, 70,

// between top rung and middle
210, 160, 70,
210, 160, 70,
210, 160, 70,
210, 160, 70,
210, 160, 70,
210, 160, 70,

// top of middle rung
70, 180, 210,
70, 180, 210,
70, 180, 210,
70, 180, 210,
70, 180, 210,
70, 180, 210,

// right of middle rung
100, 70, 210,
100, 70, 210,
100, 70, 210,
100, 70, 210,
100, 70, 210,
100, 70, 210,

// bottom of middle rung.
76, 210, 100,
76, 210, 100,
76, 210, 100,
76, 210, 100,
76, 210, 100,
76, 210, 100,

// right of bottom
140, 210, 80,
140, 210, 80,
140, 210, 80,
140, 210, 80,
140, 210, 80,
140, 210, 80,

// bottom
90, 130, 110,
90, 130, 110,
90, 130, 110,
90, 130, 110,
90, 130, 110,
90, 130, 110,

// left side
160, 160, 220,
160, 160, 220,
160, 160, 220,
160, 160, 220,
160, 160, 220,
160, 160, 220];
启动深度缓冲

看一下,效果:

你会发现乖乖这个3D怎么怪怪的?仔细想想会发现,原来没有深度信息,谁先画谁就在下面,谁后画谁就在前面,没有3D深度信息。那怎么办呢?难道还要调整绘画3角形的先后顺序吗?当然不用:一句话就行了:

ini 复制代码
 gl.enable(gl.DEPTH_TEST);

开启深度缓冲,效果如下:

这就好多了。

调整三角形绘画方向

这个实体的内部是不用绘制出来的,现在其实有点性能浪费,因为每个三角形我们都绘制了他的正面和反面,我们开启gl只绘制正面的功能,

scss 复制代码
gl.enable(gl.CULL_FACE); //设置正反三角形绘制。

效果如下:

我们发现一下三角形不见了,说明大部分三角形的顺序是不对的。我们将顶点坐标顺序都调整为顺时针就可以了。 这是由于webgl区分绘画的是正面还是反面。

结束

我们我们实现了3维图形,并且可以让它转起来。但是有一个问题,从我们正常人的视野中,距离我们远的物体会变小,距离我们近的变大。下一节我们讲解透视原理,让三维更加真实。

相关推荐
paopaokaka_luck12 小时前
非遗文物数字化系统(AI非遗问答、ONNX图像识别、协同过滤推荐、ECharts数据分析、非遗知识浏览与互动、文创商城订单闭环、文化活动报名签到、社区交流)
前端·javascript·vue.js·人工智能·数据分析·echarts
乘风gg12 小时前
AI Coding 提效 2 倍是真的吗?到底怎么衡量效果
前端·ai编程·claude
用户9210802628612 小时前
从对象到原型链:理解 this、构造函数和 new
前端
猫不易12 小时前
Webpack 与 Vite:从 Loader / Plugin 到 Rolldown 统一引擎
前端·vite
YHL12 小时前
🎯 Danci —— 用 AI 驱动开发一个全栈英语单词学习平台
前端·后端
平头哥~13 小时前
Day 20 _ 3D 翻卡_perspective 写错人,卡片就穿帮
前端·3d·css3·学习资料
小聪70813 小时前
elpis-core 前端 Webpack 工程化实践
前端
2601_9539880713 小时前
Ricon组态实时监控 - 毫秒级数据可视化
前端·物联网·数学建模·信息可视化·架构·前端框架
V1588972620113 小时前
从滴滴模式看机器人租赁:撮合型租赁平台开发源码的调度系统设计思路
linux·前端·机器人
szarron13 小时前
国产手持式频谱分析仪选型攻略:TFN RC系列 vs HTOOL SA8T频谱分析仪 专业参数对比(军工/路测/调试全覆盖)
开发语言·前端·状态模式