WebGL 3d贺卡

栏目: 编程工具 · 发布时间: 6年前

内容简介:这两天接到一个项目,是有关全屏视频的,整个项目中分到我这儿最主要的部分就是结束页要求3d贺卡展示,正巧和前几天NingBo童鞋分享的一样,干脆点儿,这次搞个webGL版的。哈哈~项目地址 现在阶段就是只做了个基础版,曲线动画啥的都是小事儿。20190427-现在加上了easebackout曲线方法,用的是

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

实现过程

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

20190427-现在加上了easebackout曲线方法,用的是 d3-ease 感觉挺好用的,还有小花的飘动的逻辑,稍后会讲解。。太饿了~吃饭去。哈哈

手指拖拽旋转逻辑这个项目用不到,所以没有添加
复制代码
WebGL 3d贺卡
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);
复制代码
WebGL 3d贺卡
当然你如果确保每个元素都是jpg的话 可以不用开启这个功能。
blendFunc 是定义混合方式,第一个参数是定义源像素采取怎样的处理,第二个参数是目标像素(颜色缓冲区)采取怎样的处理。
上面写的函数的意思 最终像素= 源像素颜色*源透明度+缓冲区颜色*(1-源透明度)
复制代码

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

添加贺卡元素

先来附上shader源码

//vertSource
uniform 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;
}
复制代码
WebGL 3d贺卡

上述顶点着色器主要说下

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

uCameraMatrix :透视矩阵

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

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 = (<HTMLImageElement>this.option.assets[file]).naturalWidth*scale;
        const imgHeight = (<HTMLImageElement>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的前面的元素根据近大远小的原则是不做处理的话,近处的会大的很离谱,这时有同学会说,我直接缩小图片就好啦,那么问题又来了,缩小图片后近大远小的原则,其实是近处的元素处于放大的效果 ,小图片放大会虚大家都知道的吧,所以采取直接缩小图片的做法是错的,唯一的做法是修改元素大小,来填充图片,这样就不会出现虚的现象了。

WebGL 3d贺卡
  1. 执行渲染
render(timeStamp,offsetTime){
        if(this.rotateX<Math.PI/2){
            this.rotateX+=0.01*offsetTime;
        }else{
            this.rotateX = Math.PI/2;
        }
        Object.keys(this.cardData).forEach(i=>{
            //遍历并渲染所有元素
            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(<GLint>this.cardParam.aPosition,3,this.gl.FLOAT,false,4*5,0);
        this.gl.vertexAttribPointer(<GLint>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

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大连接

大连接

[美] 尼古拉斯•克里斯塔基斯(Nicholas A. Christakis)、[美] 詹姆斯•富勒(James H. Fowler) / 简学 / 中国人民大学出版社 / 2013-1 / 59.90元

[内容简介] 1. 本书是继《六度分隔》之后,社会科学领域最重要的作品。作者发现:相距三度之内是强连接,强连接可以引发行为;相聚超过三度是弱连接,弱连接只能传递信息。 2. 本书讲述了社会网络是如何形成的以及对人类现实行为的影响,如对人类的情绪、亲密关系、健康、经济的运行和政治的影响等,并特别指出,三度影响力(即朋友的朋友的朋友也能影响到你)是社会化网络的强连接原则,决定着社会化网络的......一起来看看 《大连接》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具