WebGL 3d贺卡

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

内容简介:这两天接到一个项目,是有关全屏视频的,整个项目中分到我这儿最主要的部分就是结束页要求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

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


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

查看所有标签

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

Types and Programming Languages

Types and Programming Languages

Benjamin C. Pierce / The MIT Press / 2002-2-1 / USD 95.00

A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute. The study of typ......一起来看看 《Types and Programming Languages》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具