Catalyst

WebGL 笔记(2)3D 正方体

上次是画了一个基础图形,在上次的基础上,这次画个正方体。

线框正方体

准备好正方体的点,画线段所以比较杂……

const vertices=[1,1,1,1,-1,1,-1,-1,1,-1,1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,-1,1,1,-1,1,1,-1,-1,1,1,-1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1];

然后就是有关摄影机,坐标等等相关内容了。相关概念教程可以看 LearnOpenGL

看到矩阵乘法,想起了被论文支配的恐惧,四个坐标系之类的是 3D 场景的共识了。

使用 gl-matrix 库来进行矩阵的运算。最终需要物体,相机,投影三个矩阵。

  //物体本身转 45 度
  let model = glMatrix.mat4.create();
  glMatrix.mat4.rotate(
    model,
    model,
    45,
    glMatrix.vec3.fromValues(0.0, 1.0, 0.0)
  );
  //相机向后移四格
  let camera = glMatrix.mat4.create();
  glMatrix.mat4.translate(
    camera,
    camera,
    glMatrix.vec3.fromValues(0.0, 0.0, -4.0)
  );
  //投影矩阵
  let projection = glMatrix.mat4.create();
  let ratio = 8 / 6;
  glMatrix.mat4.perspective(projection, (75 * Math.PI) / 180, ratio, 0.1, 100);

修改顶点着色器,添加放以上三个矩阵的变量。点的最终位置也改为矩阵乘法算出。

attribute vec3 pos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main(){
    gl_Position=proj * view * model *vec4(pos.x,pos.y,pos.z,1.0);
}

然后就是把数据传到着色器中。由于用的是uniform型变量,查找变量的函数也不一样。

//注:下面的要写在启用 program 之后
gl.useProgram(shaderProgram);
//矩阵传入着色器
//1.找到变量的位置
let modelPosIndex = gl.getUniformLocation(shaderProgram, "model");
//2.传值
gl.uniformMatrix4fv(modelPosIndex,false,model);

attribute型是只能在顶点着色器使用的变量类型,主要描述顶点数据。uniform型在顶点和片段着色器内都可使用,描述颜色、光照等等信息。

查看结果:

线框正方体

实心正方体

上面画线框的顶点集实在是太扭曲了……这回做个真正的三角片。

const vertices=[1,1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,-1,1,-1,1,1,-1,1,-1,-1,1,1,1,1,1,1,1,-1,-1,1,-1,1,1,-1,-1,1,-1,1,-1,-1,1,-1,-1,1,-1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,-1,-1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,1,-1,1,1,1,1,-1,-1,1,-1,-1,1,1,1,1,-1,1,1,1,-1,-1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,1,1,-1];

将绘图参数改为gl.drawArrays(gl.TRIANGLES, 0, 36);,结果是白色一片的轮廓……这是当然的啦!因为实心白色融合在一起了。为了更清楚的看见,尝试给各个面上不同的颜色。

首先给vertices里加上各个点的颜色:

const vertices=[1,1,1,1,0,0,1,1,-1,1,0,0,-1,1,-1,1,0,0,1,1,1,1,0,0,-1,1,1,1,0,0,-1,1,-1,1,0,0,1,1,-1,1,1,1,1,-1,-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1,1,1,1,1,-1,1,1,1,1,1,-1,-1,0,1,0,1,-1,1,0,1,0,-1,-1,1,0,1,0,-1,-1,1,0,1,0,-1,-1,-1,0,1,0,1,-1,-1,0,1,0,-1,-1,1,0,0,1,-1,-1,-1,0,0,1,-1,1,1,0,0,1,-1,1,1,0,0,1,-1,1,-1,0,0,1,-1,-1,-1,0,0,1,1,-1,1,1,0,1,1,1,1,1,0,1,-1,-1,1,1,0,1,-1,-1,1,1,0,1,1,1,1,1,0,1,-1,1,1,1,0,1,1,-1,-1,1,1,0,1,1,-1,1,1,0,-1,-1,-1,1,1,0,-1,-1,-1,1,1,0,-1,1,-1,1,1,0,1,1,-1,1,1,0];

uniform变量是全局的、与顶点无关的,所以颜色使用attribute型。然后利用varying型变量传给片段着色器。修改着色器,加上输入的颜色:

attribute vec3 pos;
attribute vec3 in_color;
varying lowp vec3 color;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main(){
    color = in_color;
    gl_Position=proj * view * model *vec4(pos,1.0);
}
varying lowp vec3 color;
void main(){
      gl_FragColor = vec4(1.0,color.y,1.0,1.0);
  }

修改vertices的值,使六个数字表示一个点,前三个值为位置,后三个值为颜色。修改传入数据的地方。gl.vertexAttribPointer的最后两个参数:步长:一组数据的长度;偏移量:开始到第一个此类数据的长度。

//float 元素长度  
let size = Float32Array.BYTES_PER_ELEMENT;
//输入位置
let index = gl.getAttribLocation(shaderProgram, "pos");
gl.vertexAttribPointer(index, 3, gl.FLOAT, false, 6 * size, 0);
//输入颜色
let colorIndex = gl.getAttribLocation(shaderProgram, "colors");
gl.vertexAttribPointer(colorIndex, 3, gl.FLOAT, false, 6 * size, 3 * size);
//启用属性
gl.enableVertexAttribArray(index);
gl.enableVertexAttribArray(colorIndex);

查看结果,发现颜色堆叠了……深度测试,on!

gl.enable(gl.DEPTH_TEST);
gl.clearDepth(1);

最后调一下物体的旋转角度,好看到更多的面:

实心正方体

索引正方体

写 36 个点的正方体的确挺抽风的。利用gl.drawElements,用索引来画一系列物体,完成一个线框正方体。

经测试用索引画的话无法分开设置颜色(像上图一样)?

//正方体的点
var vertices = [
    -1, -1, -1,//
    1, -1, -1,//
    1, 1, -1,//
    -1, 1, -1,//
    -1, 1, 1,//
    1, 1, 1,//
    -1, -1, 1,//
    1, -1, 1
  ];
//组成面的点索引 (12 个三角形)
const indices = [
    1,2,5, 1,5,7,
    1,2,3, 1,0,3,
    0,3,4, 0,6,4,
    6,4,7, 7,5,4,
    6,7,0, 0,7,1,
    4,5,3, 3,5,2
];
//...
//正方体
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
var index = gl.getAttribLocation(shaderProgram, "pos");
gl.vertexAttribPointer(index, 3, gl.FLOAT, false, 0, 0);

const indexesBuffer = gl.createBuffer();
//传入索引的时候使用 gl.ELEMENT_ARRAY_BUFFER 类型
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
gl.enableVertexAttribArray(index);

//...

//利用`gl.drawElements',用索引来画一系列物体
//类型,绘制的点数,数据类型,offset(离开始点的偏移量)
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

真实笔记,除了我没人看得懂