通过几天的学习,对openGL、shader有了一个大致的了解。
回到学习的初衷吧,在基于pixi.js重构D3项目的时候,因为精灵层级的问题,我得按照一定的先后顺序将不同类别的精灵添加到场景中去。
例如:
针对人物关系的关系图谱,所有的关系线必须要在所有的任务面板下面,但是移动人物面板的时候,与之关联的关系线也要重新绘制;
所以删除精灵之后再添加精灵使得层级增加的做法就有点不适用了(这会导致当前操作的关系线的层级提升,很显然,这不是我们所想要的)。
因此,我们每次操作,都要重新将所有的精灵(处理好新的位置之后),分批次添加到场景中,这就导致出现下面的代码:
人物面板是一类精灵,关系线又是一类精灵,所以我们要用到两个for循环,并且顺序还得讲究(先for循环关系线,再for循环人物面板);
这样一来,如果页面中有10000个人物面板(相对应的就有10000条关系线),那么就要for循环10000+10000=20000次,针对拖拽操作,每触发一次移动就要for循环20000次,
经过测试,由于精灵数组是存于内存中的,for循环的时候很方便,但是对CPU的消耗很高!
当时,遇到这个问题的时候,朋友说可以将这部分的for循环也让GPU去执行,然后就看到了shader,进而学习了openGL;
学完之后,发现着色器程序的渲染模式可以用四个字概括:逐点绘制。确实解决了在CPU上执行for循环的问题。但是一个错综复杂的人物关系图谱,要用着色器去绘制的话(咱先不讨论shader加载纹理的问题),是不是就失去了使用pixi.js的意义,而且这也将使得开发工作变得更加复杂。
所以,我决定,鉴于关系图谱项目的特殊性,使用依次for循环的方式来处理精灵层级的问题。
分析完问题之后,我们还是要看看,在pixi中如何使用我们自定义的着色器程序。
一、pixi_shaders
百度云链接:https://pan.baidu.com/s/16m2BX5qKXr5Wm-J-68eXGA
提取码:j5t9
效果图:
3个iframe,3个片元着色器效果。
二、着色器代码分析
第三个iframe对应的shader,效果是颜色不断变化
let width = window.innerWidth; let height = window.innerHeight; let app = new PIXI.Application(width, height); document.body.appendChild(app.view); let shaderFrag = ` precision mediump float; uniform vec3 iResolution; uniform float iTime; void main() { // Normalized pixel coordinates (from 0 to 1) vec2 uv = gl_FragCoord.xy/iResolution.xy; // Time varying pixel color vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); // Output to screen gl_FragColor = vec4(col,1.0); } `; let container = new PIXI.Container(); container.filterArea = app.screen; app.stage.addChild(container); let filter = new PIXI.Filter(null, shaderFrag); filter.uniforms.iResolution = [width, height, 1.0]; filter.uniforms.iTime = 1.0; container.filters = [filter]; // Animate the filter app.ticker.add(function(delta) { filter.uniforms.iTime += 0.1; });
前面我们谈到,着色器的渲染模式是逐点绘制,所以我们只要定义任意一点绘制方式就可以了,因为他会自动将我们的着色器代码应用到指定区域内的每个像素点上。
绘制方式,这个概念有点抽象了,实际上就是颜色,每个像素点的颜色!!!
我们来看上面的着色器代码:
let shaderFrag = ` precision mediump float; uniform vec3 iResolution; uniform float iTime; void main() { // Normalized pixel coordinates (from 0 to 1) vec2 uv = gl_FragCoord.xy/iResolution.xy; // Time varying pixel color vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); // Output to screen gl_FragColor = vec4(col,1.0); } `;
这个着色器接收由外面传入的两个参数:三维坐标iResolution和随着时间递增的时间参数iTime;
let filter = new PIXI.Filter(null, shaderFrag); filter.uniforms.iResolution = [width, height, 1.0]; filter.uniforms.iTime = 1.0; container.filters = [filter];
然后着色器程序先将坐标进行转换(转换为pixi适用的坐标),
然后根据转换后的坐标(向量)和时间参数计算出三维颜色(RGB),着色器程序输出的颜色常量gl_FragColor是四维向量(RGBA),所以我们人为地给他加上alpha通道值。
这样一来,单个像素点输出的颜色就确定了,然后逐点绘制,屏幕上所有的点的颜色就渲染出来了。
此外,加上一个pixi的动画,通过改变iTime参数的值,实现改变单个像素点输出的颜色值,这样,我们看上去就是颜色不断变化的屏幕了。
app.ticker.add(function(delta) { filter.uniforms.iTime += 0.1; });