WebGL入门与进阶3

前文已经大概讲解了一些基础知识,涵盖了WebGL创建,着色器,着色器编程以及缓存区等知识,一些简单的点面效果即可以基于此来完成,接下来让我们绘制一下如下的效果:

让我们先准备一下前期的WebGL内容,首先让我们来创建一个可以接受参数的着色器,(如果不清楚具体要怎么创建可以参考上一章教程内容) 接下来,我们需要创建多个点的坐标,所有点在同一行时,只有X坐标是变化的,y是相同的。

但因为WebGL的坐标系与实际页面中的坐标系是不同的,如下图,普通canvas坐标系与正常的浏览器像素值相同,但WebGL中的坐标系是以整个WebGL中心点为(0.0,0.0),而且坐标的精确度为小数点后一位。坐标系对比如下图所示:

然后你知道了这个依旧会绘制出超级模糊的图像,那是因为整个WebGL的尺寸是与canvas宽度与高度相关连的,并且canvas的宽度与高度如果用css来设置的话,会被默认成100×100,也就意味着,你绘制出来的图形是把100×100的图形拉伸到当前canvas的尺寸中。所以正确的设置canvas的方式应该如下:

//错误
<canvas id="glcanvas" style="width: 700px; height: 500px;">
//正确的方式
<canvas id="glcanvas" width="700" height="500">

首先我们需要先将对应的真实坐标转换成WebGL坐标,因为canvas宽度的一半对应为WebGL的1.0尺寸。所以将真实像素除以宽度的一半就可以得到对应的WebGL尺寸,高度与宽度的处理方式一致。

function webglX (num) {
   return num/(width/2);
};
function webglY (num) {
   return num/(height/2);
}

有了点,那么我们就可以使用先前讲过的缓存区开始批量绘制点了。(如果不清楚具体如何使用缓存区绘制,也可以点击上一章)。

接下来我们要让他动起来,怎么动起来呢?思路是我们可以在不停的重绘,并且将点进行轻微位移即可达到效果,定时执行函数可以使用setInterval或setTimeout。但此2个函数性能上有很大问题,并且也无法完美的匹配浏览器的刷新频率。

在这里我跟大家介绍一个新函数requestAnimationFrame。

requestAnimationFrame是为了提高js动画性能而诞生的神器。下面我们简单来介绍一下这个函数为何被誉为神器。

requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,也就是没s刷新60次。因与浏览器刷新频率一样所以不会出现看似卡顿的情况出现。 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量 requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销

具体的内容以后有机会可以单独详细来介绍此函数功能。

接下来让我们来绘制一个变化的函数,变化值从0开始,requestAnimationFrame内容入参为循环执行的函数名。

var num = 0;
function render() {
   requestAnimationFrame(render);
   num = num - 1;
   var data = createPoints(num);
   setPoints(data, 1000);
}

接下来我们需要将之前绘制点的部分功能函数提取到单独的一个函数中,方便动画循环调用时使用。

function setPoints(data, num) {
   var vertexBuffer = gl.createBuffer();
   if(!vertexBuffer) {
       log('创建缓存区失败。');
       return -1;
   }
   gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
   var a_position = gl.getAttribLocation(gl.program, 'a_p');
   gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0);
   gl.enableVertexAttribArray(a_position);
   gl.clearColor(0.0,0.0,0.0,1.0);
   gl.clear(gl.COLOR_BUFFER_BIT);
   gl.drawArrays(gl.POINTS, 0 , num);
}

接下来也就是最重要的,我们需要每次都变更对应点的坐标位置,因我们要使用波浪效果,所以采用正弦函数来实现,首先让所有的点都按照每次变更10度的浮动,并且因为是上下移动,我们只需要变更y轴的坐标即可。

var width = 700;
var height = 500;
function createPoints(gap) {
   var max = 10
   var arr = [];
   var n = 100;
   var m = 10;
   for(var i = 0; i < n; i++) {
       for(var j = 0; j < m; j++) {
           var x = webglX(-(width/2) + i*20);
           var y = webglY((height/2)  - j*20);
           var z = -1;
           var item = [x, y, z];
           arr = arr.concat(item);
       }
   }
   return new Float32Array(arr)
}

正弦函数Math.sin传入的值不是角度,而是弧度,我们需要通过下面的函数把角度转化为弧度,函数如下:(如果您不知道弧度与角度的关系,建议自行查询)

function numToDeg(num) {
   return Math.PI*num / 180;
};

我们为此函数中x与y的值进行一下操作。

为保证所有的点肯定全部覆盖屏幕,我们将每个点的间距调整按照整个canvas宽度超过500的尺寸来平均分,整个坐标系向做便宜200,也就是右边还存在300的富于空间,这样即使有错开位移也不会让屏幕露出空白。接下来,为了让上面的点波动比下面的点大,让我们来设置了一个最大倍数,并且将行数与此倍数关联,这样就会使动画上下浮动不一样了。

var deg = (j*20+gap);
var x = webglX(-(width/2)-200+i*((width+500)/n)+j*20);
var y = webglY(-(height/2)+Math.sin(numToDeg(deg))*(max+j*4) + j*20);

我们达到了横向。

接下来我们在角度便宜中添加上x轴的影响力。

var deg = (i-j*20+gap);
var x = webglX(-(width/2)-200+i*((width+500)/n)+j*20);
var y = webglY(-(height/2)+Math.sin(numToDeg(deg))*(max+j*4) + j*20);

让我们看看效果,波动开始有偏差了。

效果按照我们预期的方向前进了,接下来我们扩大x轴的影响,这个个人可以一点一点尝试,我们直接扩大到5倍。

var deg = (i*5-j*20+gap);
var x = webglX(-(width/2)-200+i*((width+500)/n)+j*20);
var y = webglY(-(height/2)+Math.sin(numToDeg(deg))*(max+j*4) + j*20);

看样子动画效果已经基本达到了效果,接下来让我们把上面的点变小,下面的点变大。产生前后的视觉差。 如何让渲染的点尺寸不同呢?没错我们也可以使用缓存区来操作。 对创建点的函数进行改造:

function createPoints(gap) {
   //波动最大幅度 10px;
   var max = 10
   var n = 100;
   var m = 10;
   var arr = [];
   var size = [];
   for(var i = 0; i < n; i++) {
       for(var j = 0; j < m; j++) {
           var deg = (i*7-j*20+gap);
           var x = webglX(-(width/2)-200+i*((width+500)/n)+j*20);
           var y = webglY(-(height/2)+Math.sin(numToDeg(deg))*(max+j*4) + j*20);
           var z = -1;
           var item = [x, y, z];
           arr = arr.concat(item);
           size.push((4-j/4));
       }
   }
   return {
       positions: new Float32Array(arr),
       size: new Float32Array(size),
       num: m * n
   }
}

我们把点与对应的坐标匹配一下,并且把点的数量动态化,让我们在使用缓存区来把size动态批量化。

function setSize(sizes, n) {
   var sizeBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);
   var a_pointsize = gl.getAttribLocation(gl.program, 'size');
   gl.vertexAttribPointer(a_pointsize, 1, gl.FLOAT, false, 0, 0);
   gl.enableVertexAttribArray(a_pointsize);
};

记得着色器上要声明一个接收的参数哦。

attribute float size;

然后我们在render函数中可以直接使用其函数进行重绘。

我们已经绘制出来了最终效果,本着开源的原则,完整代码链接如下:

https://github.com/jdf2e/webgl-demo

有兴趣欢迎关注我们的公众号:全栈探索。欢迎交流。

文章来源:

Author:杨磊
link:https://jdc.jd.com/archives/212370