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

相关推荐
weifexie20 分钟前
ruby可变参数
开发语言·前端·ruby
千野竹之卫21 分钟前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
sunbyte21 分钟前
初识 Three.js:开启你的 Web 3D 世界 ✨
前端·javascript·3d
半兽先生42 分钟前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
南星沐2 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
孙_华鹏2 小时前
手撸一个可以语音操作高德地图的AI智能体
前端·javascript·coze
zhangxingchao2 小时前
Jetpack Compose 动画
前端
@PHARAOH3 小时前
HOW - 缓存 React 自定义 hook 的所有返回值(包括函数)
前端·react.js·缓存
拉不动的猪3 小时前
设计模式之--------工厂模式
前端·javascript·架构
前端开发张小七3 小时前
16.Python递归详解:从原理到实战的完整指南
前端·python