博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WebGL 手撸3d贺卡+小草飘动滤镜
阅读量:5872 次
发布时间:2019-06-19

本文共 8213 字,大约阅读时间需要 27 分钟。

前言

这两天接到一个项目,是有关全屏视频的,整个项目中分到我这儿最主要的部分就是结束页要求3d贺卡展示,正巧和前几天童鞋分享的一样,干脆点儿,这次搞个webGL版的。哈哈~

实现过程

20190426-现在阶段就是只做了个基础版,曲线动画啥的都是小事儿。

20190427-现在加上了easebackout曲线方法,用的是感觉挺好用的,还有小花的飘动的逻辑,稍后会讲解。。太饿了~吃饭去。哈哈(已更新,纯文字,不懂得随时提问)

20190429-设计大改,已经不是这个样子了,我把这个提出来当demo了,汗~~。不过还好,道理都一样

手指拖拽旋转逻辑这个项目用不到,所以没有添加复制代码

ps:有没有觉得chrome里devtool不是这个界面啊~哈哈哈,最近在弄一个可视化的工具复制代码

言归正传,咱们接着往下进行:

webGL初始化(常规操作)

  1. 获取 WebGLRenderingContext
const gl = canvas.getContext('webgl');复制代码
  1. 编译shader并把编译好的shader附加到创建好的program中
//顶点着色器    const vertShader = gl.createShader(gl.VERTEX_SHADER);     gl.shaderSource(vertShader,vertSource);//vertSource:着色器源码    gl.compileShader(vertShader);    //片元着色器    const fragShader = gl.createShader(gl.FRAGMENT_SHADER);     gl.shaderSource(fragShader,fragSource);//fragSource:着色器源码    gl.compileShader(fragShader);        //program相关    const program = gl.createProgram();    gl.attachShader(program,vertShader); //附加顶点着色器    gl.attachShader(program,fragShader); //附加片元着色器    gl.linkProgram(program);复制代码

3.因为贺卡是3d的所以要打开深度测试

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

4.因为元素不是模型而是一个个矩形,只是材质有的是透明的,在元素叠加时会把当前像素覆盖到缓冲中,比如颜色值(0,0,0,0)会覆盖已有颜色(1,0,0,1),导致这个像素不是你想要的红色而是透明色。解决办吧是开启混合模式。

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);    gl.enable(gl.BLEND);复制代码

当然你如果确保每个元素都是jpg的话 可以不用开启这个功能。blendFunc 是定义混合方式,第一个参数是定义源像素采取怎样的处理,第二个参数是目标像素(颜色缓冲区)采取怎样的处理。上面写的函数的意思 最终像素= 源像素颜色*源透明度+缓冲区颜色*(1-源透明度)复制代码

基本上初始化工作就完成了,下面来看下怎样添加元素。

添加贺卡元素

先来附上shader源码

//vertSourceuniform mat4 uCameraMatrix;uniform mat4 uTransformMatrix;attribute vec3 aPosition;attribute vec2 aUv;varying vec2 vUv;void main(){    const float scale = 1.0/1.6;    //这个矩阵不用管 我是懒得写lookAt了 和lookAt的功能是一样的    const mat4 viewAngle = mat4(        1,0,0,0,        0,cos(-.1),-sin(-.1),0,        0,sin(-.1),cos(-.1),0,        0,-50,-1000,1    );    vec3 cPosition = (aPosition)*vec3(scale,scale,-1);    gl_Position = uCameraMatrix*viewAngle*(uTransformMatrix*vec4(cPosition.xy,0.0,1.0)+vec4(0,0,cPosition.z,0));    vUv = aUv;//uv传给片元着色器的,供采样定位用} 复制代码
//片元着色器precision highp float;uniform sampler2D uImage;varying vec2 vUv;void main(){    vec4 color = texture2D(uImage,vUv);    if(color.a == 0.0){        discard;//这个是如果采样的颜色是透明的则丢弃该颜色,和不加有一点儿区别,看下方图(需要关闭BLEND)    }    gl_FragColor = color;}复制代码

上述顶点着色器主要说下

gl_Position = uCameraMatrix*viewAngle*(uTransformMatrix*vec4(cPosition.xy,0.0,1.0)+vec4(0,0,cPosition.z,0));复制代码

uCameraMatrix:透视矩阵

viewAngle:相当于lookAt,我也想直接在js中把这两个矩阵整合了,但是看的lookAt方法用不对,也没深究,后来放弃了,直接写进去了,再就是我这里面的Z轴取反了,因为gl-mat4里的透视矩阵给我反过来了,我用不惯。。?,然后正回来了。

uTransformMatrix:变换矩阵,用于变换当前元素用的,心细的童鞋看了应该会问我为什么不直接写成

uTransformMatrix*vec4(cPosition,1.0)而是写成

uTransformMatrix*vec4(cPosition.xy,0.0,1.0)+vec4(0,0,cPosition.z,0)呢?我的做法是用同一个矩阵使每个元素按照自身的底部进行旋转,如果z轴不是0的话旋转就不是底部了,所以要先变换,在进行Z轴位移,就是我想要的每个元素以自身的底儿来旋转。

说完shader接下来就是drawArrays了。

整个3d中我分成了两类元素,一类是不变的,也就是地面,一类是跟着展开旋转的,也就是非地面的部分。

地面是相对于其他部分来说只有 uTransformMatrix是个单位矩阵,其他的是随时间变换而变换,所以我选择了把他们统一做成了一样的结构,添加了一个rotateFlag做区分。 每个数据结构如下:

interface attribData{    buffer:WebGLBuffer;     data:Float32Array; //记录的顶点和UV    texture?:WebGLTexture; //自身所需的素材    rotateFlag:boolean; //旋转开关}复制代码
  1. 首先要写入元素数据
createStandEle(file,[x,y,z]){        //file:图片名称        //this.option.assets[file]:图片元素        const scale = Math.sqrt((600+z)/600); //这个下面会重点说下        const imgWidth = (
this.option.assets[file]).naturalWidth*scale; const imgHeight = (
this.option.assets[file]).naturalHeight*scale; const name = file.match(/card\_([^\.]+)/)[1]; const data = { buffer:this.gl.createBuffer(), data:new Float32Array([ //顶点数据 UV数据 x-imgWidth/2,y,z, 0,1, x+imgWidth/2,y,z, 1,1, x-imgWidth/2,y+imgHeight,z, 0,0, x+imgWidth/2,y+imgHeight,z, 1,0 ]), texture:this.gl.createTexture(), rotateFlag:true, }; //this.cardData:是我的所有元素的集合 this.cardData[name] = data; this.gl.bindBuffer(this.gl.ARRAY_BUFFER,data.buffer); //给ARRAY_BUFFER写入数据 this.gl.bufferData(this.gl.ARRAY_BUFFER, data.data, this.gl.STATIC_DRAW); this.gl.activeTexture(this.gl.TEXTURE0); this.gl.bindTexture(this.gl.TEXTURE_2D,data.texture); let format = this.gl.RGB; //jpg没必要用alpha if(texture.search(/\.png$/)>=0){ format = this.gl.RGBA; } //给gl.TEXTURE_2D设置纹理 this.gl.texImage2D(this.gl.TEXTURE_2D,0,format,format,this.gl.UNSIGNED_BYTE,this.option.assets[texture]); //下面是缩放采样和包装方式 this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR); this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR); this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE); this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE); }复制代码
基本逻辑就是创建缓冲->绑定缓冲—>给缓冲赋值复制代码

代码中的scale有必要说一下,透视矩阵中是符合近大远小的特征,但是设计稿件是平面的,没有远近的概念,加上远近之后,psd的前面的元素根据近大远小的原则是不做处理的话,近处的会大的很离谱,这时有同学会说,我直接缩小图片就好啦,那么问题又来了,缩小图片后近大远小的原则,其实是近处的元素处于放大的效果 ,小图片放大会虚大家都知道的吧,所以采取直接缩小图片的做法是错的,唯一的做法是修改元素大小,来填充图片,这样就不会出现虚的现象了。

  1. 执行渲染
render(timeStamp,offsetTime){        if(this.rotateX
{ //遍历并渲染所有元素 this.renderBuffer(this.cardData[i]); }); super.render(timeStamp,offsetTime); } renderBuffer(data:attribData){ this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT); this.gl.useProgram(this.cardProgram); if(data.rotateFlag){ const rotate = this.rotateX-Math.PI/2; this.gl.uniformMatrix4fv(this.cardParam.uTransformMatrix,false,new Float32Array([ 1,0,0,0, 0,Math.cos(rotate),-Math.sin(rotate),0, 0,Math.sin(rotate),Math.cos(rotate),0, 0,0,0,1, ])); }else{ //如果不是旋转元素则赋值给uTransformMatrix 一个单位矩阵。 this.gl.uniformMatrix4fv(this.cardParam.uTransformMatrix ,false,this.identityMatrix); } this.gl.bindBuffer(this.gl.ARRAY_BUFFER,data.buffer); this.gl.vertexAttribPointer(
this.cardParam.aPosition,3,this.gl.FLOAT,false,4*5,0); this.gl.vertexAttribPointer(
this.cardParam.aUv,2,this.gl.FLOAT,false,4*5,4*3); this.gl.activeTexture(this.gl.TEXTURE0); this.gl.bindTexture(this.gl.TEXTURE_2D,data.texture); this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4); }复制代码

基本逻辑就 绑定缓冲&绑定纹理—>告诉显卡从当前绑定的缓冲区中读取顶点数据->drawArrays

花飘动动效

这个效果最主要的操作都在片元着色器中,原理就是获取uv插值
vUV,并对其进行偏移运算,然后读取计算后uv位置的纹理采样。很简单吧~ 附上更新好的片元着色器代码;

//片元着色器precision highp float;uniform sampler2D uImage;uniform int uType;//0:非小草 1:小草 这些都是在js中设置的uniform float uTime;//当前时间戳varying vec2 vUv;void main(){    vec4 color = vec4(0);    if(uType == 1){        //小草部分        float offset = distance(vUv,vec2(0.5,1.0));        offset = pow(offset,2.)/8.0*sin(uTime);        mat2 rotate = mat2(            cos(offset),-sin(offset),            sin(offset),cos(offset)        );        vec2 cUv = vec2(0.5,1.0)+rotate*(vUv-vec2(0.5,1.0));        if(cUv.x<0.||cUv.y<0.||cUv.x>1.||cUv.y>1.) discard;        color = texture2D(uImage,cUv);    }else{        color = texture2D(uImage,vUv);    }    if(color.a == 0.0){        discard;    }    gl_FragColor = color;}复制代码

上述小草部分 就是对当前uv做偏移处理。

因为小草底部是扎在地上不动的,而且飘动不是线性变化的,越远离地面飘动幅度越大,所以不能在顶点着色器里操作斜切啥的运算(类似于css transform的skew操作)。

我这里选择的是当前uv到底部中心(小草根部)的距离取二次方,距离底部中心越远幅度也越明显。

float offset = distance(vUv,vec2(0.5,1.0));offset = pow(offset,2.)/8.0;// /8.0是直接用的话幅度太大 而UV值在0-1之间,做一个缩小处理复制代码

再乘以和时间相关的sin值,当sin为0时,因为相乘的关系,所以也就是最后的计算结果和传入的vUv一样,也就是说和贴图元素一样,当sin为1和-1是就是偏移最大,也就是扭曲后的图片。

offset = offset*sin(uTime);复制代码

然后把这个值当作旋转矩阵的角度,最终生成新的uv坐标;

mat2 rotate = mat2(//旋转矩阵    cos(offset),-sin(offset),    sin(offset),cos(offset));vec2 cUv = vec2(0.5,1.0)+rotate*(vUv-vec2(0.5,1.0));//相对于底部中心做旋转处理复制代码

剩下的部分就是在js中每一帧传入时间戳还有uType值即可。剩下的就是交给webGL渲染管线处理。

这部分其实应该叫做滤镜了。像水动效啊、火焰动效啊,还有pixiJs中的filter基本上都是一样的流程,什么抖音效果,rgb颜色分离都是在这儿处理。

这部分纯文字也不知道能不能讲懂。。 后续应该没啥要加的了。剩下的就是非webGL部分了。过两天这个项目做好后webGL部分会在结尾处展示哈,想看效果先看完前面的视频。。。汗。。。

有啥不明白的留言~~~欢迎提问~哈哈哈

转载于:https://juejin.im/post/5cc2ef0ef265da0379417f8f

你可能感兴趣的文章
ubuntu Unable to locate package sysv-rc-conf
查看>>
笔记:认识.NET平台
查看>>
cocos2d中CCAnimation的使用(cocos2d 1.0以上版本)
查看>>
【吉光片羽】短信验证
查看>>
MacBook如何用Parallels Desktop安装windows7/8
查看>>
gitlab 完整部署实例
查看>>
GNS关于IPS&ASA&PIX&Junos的配置
查看>>
七天学会ASP.NET MVC (四)——用户授权认证问题
查看>>
upgrade to iOS7,how to remove stroyboard?
查看>>
影响企业信息化成败的几点因素
查看>>
Thinkphp5 模型里别名alias不生效bug【已解决】
查看>>
SCCM 2016 配置管理系列(Part8)
查看>>
zabbix监控部署
查看>>
关于Tomcat下项目中文名在Windows和Linux下编码混乱问题解决
查看>>
struts中的xwork源码下载地址
查看>>
Android硬件抽象层(HAL)深入剖析(二)
查看>>
记录一些有用的代码技巧,不定期更新
查看>>
Centos 安装后不能上网 解决备忘
查看>>
cisco 交换机链路聚合
查看>>
[AX]AX2012 Form开发概览
查看>>