计算一张图片在内存中的大小

var bytes = (width * height * channels *8)/8
每一个像素点需要几个通道来存,对于png而言他自己有rgba四个通道,每一个通道需要多少个二进制,
所以在内存中需要(width * height * channels *8)个二进制
如果转换为字节数,还需要除以8  8个二进制表示一个字节

第一步加载到一张图片到内存

var img = new Image();
img.onload = function(img){
      //加载完成回调
     //此处的img就是你加载到内存的图像数据HTMLImageElement
     //使用这个img可以发到GPU,
 }.bind(this,img)
 //设置你的图片路径
 img.src = arr[j];

第二步从内存中将图像数据发往GPU,这个时候会在显存中重新生成一份GPU可以识别的纹理信息,注意我这里说的是重新生成,考虑到cpu这边的内存成本,你还必须选择性的干掉加载到在内存中的无用的图像数据,下面会有提到如何删除
应用1:市面上经常说要加快图片的加载速度,使用压缩的纹理,其实就是不使用texImage2D这个函数来加载,因为这个函数会对内存中的纹理数据再按照传来的参数重新生成GPU的纹理数据,而且从本地加载到内存的数据还需要展开,这两个过程比较耗时,如果是压缩过数据,那么就会直接调用glCompressedTexImage2D,不会有前面说的那两个过程,所以加载的速度比较块,而且纹理经过压缩,体积变得更小了,也降低了内存,当然我说的是加载到内存中的纹理的大小,不是指图片文件的大小,我们使用压缩工具(package或者android sdk里的工具)对图片进行纹理压缩,通常纹理文件的大小是大于图片文件的大小的,但是纹理文件加载到内存中,不需要展开,不需要转换为GPU识别的纹理,直接发给GPU,也就是说你的纹理文件大小就是内存中纹理文件的大小,最后,还可以对纹理文件进行ccz压缩,这又可以大幅度降低纹理文件的大小,降低包的体积,加载到内存,解压缩即可,ios一般是pvr,android是etc1和etc2,etc1不支持透明度,可以通过写个简单shader来解决
应用2:显存中的纹理数据就是我们在cpu端对她的控制其实就是通过(this._glID = gl.createTexture())这里面glID,利用这个变量可以实现对显存中的纹理绑定和删除

private onLoadImageFinish(image:HTMLImageElement):void{
        
        // this._gl.activeTexture(this._gl.TEXTURE0);
        console.log("纹理信息-------",image.width,image.height,image);
         // 指定当前操作的贴图
        this._gl.bindTexture(this._gl.TEXTURE_2D,this._glID);
        // Y 轴取反
        this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, true);

        // 创建贴图, 绑定对应的图像并设置数据格式
         // this._gl.texImage2D(
         //      this._gl.TEXTURE_2D,
         //      0, // 就是这个参数指定几级 Mipmap
         //      this._gl.RGBA, 
         //      this._gl.RGBA, 
         //      this._gl.UNSIGNED_BYTE, 
         //      image);
         //256*256   p(gpu内存) = width * height * 4 /1024 = 256k
         this._gl.texImage2D(this._gl.TEXTURE_2D,0, this._gl.RGBA, this._gl.RGBA, this._gl.UNSIGNED_BYTE, image);
         //256*256  p(gpu内存) = width * height * 3 /1024 =342 - 342/4 = 192k 相当于内存减少1/4
        //  this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGB,this._gl.RGB, this._gl.UNSIGNED_BYTE, image);
        

         // 生成 MipMap 映射
         var isOpenMipMap = true;
         // 首先要调用此方法
         // 要在texImage2D 后调用,否则会报错error:GL_INVALID_OPERATION  gl.generateMipmap(this._gl.TEXTURE_2D)
         //如果开启此技术对于256*256这个贴图 它的内存占用会比原来多出三分之一
         //256*256 p(gpu内存) = (width * height * 4 /1024)*(4/3) =342
         //能够使用这个技术的图片的宽高必须是2的幂
         //此技术开启以后,会生成以下级别的图片,256*256这个是0级
         //级别:128*128(1),64*64(1),32*32(1),16*16(1),8*8(1),4*4(1),2*2(1),1*1(1)
         //实时渲染时,根据采样密度选择其中的某一级纹理,以此避免运行时的大量计算
         if(isOpenMipMap&&isPow2(image.width)&&isPow2(image.height))
         {
            //  this._gl.hint(this._gl.GENERATE_MIPMAP_HINT, this._gl.NICEST);
            this._gl.generateMipmap(this._gl.TEXTURE_2D);
         }
         else if(isOpenMipMap)
         {
             console.warn('NPOT textures do not support mipmap filter');
             isOpenMipMap = false;
         }


        //特别注意
        if(isPow2(image.width)==false||isPow2(image.height)==false)
        {
            console.warn('WebGL1 doesn\'t support all wrap modes with NPOT textures');
        }
        
        /**
         * MIN_FILTER 和 MAG_FILTER
         * -------------对于纹理的放大
         * 一个纹理是由离散的数据组成的,比如一个 2x2 的纹理是由 4 个像素组成的,使用 (0,0)、(0, 1) 等四个坐标去纹理上取样,自然可以取到对应的像素颜色;
         * 但是,如果使用非整数坐标到这个纹理上去取色。比如,当这个纹理被「拉近」之后,在屏幕上占据了 4x4 一共 16 个像素,
         * 那么就会使用 (0.33,0) 之类的坐标去取值,如何根据离散的 4 个像素颜色去计算 (0.33,0) 处的颜色,就取决于参数 MAG_FILTER
         * MAG_FILTER(放大) 有两个可选项,NEAREST 和 LINEAR。
         * 顾名思义,NEAREST 就是去取距离当前坐标最近的那个像素的颜色,而 LINEAR 则会根据距离当前坐标最近的 4 个点去内插计算出一个数值
         * NEAREST:速度快,但图片被放的比较大的时候,图片的颗粒感会比较明显
         * LINEAR: 速度慢点,但图片会显示的更顺滑一点
         * -------------对于纹理的缩小
         * MIN_FILTER(缩小) 有以下 6 个可选配置项:
         * NEAREST
         * LINEAR
         * NEAREST_MIPMAP_NEAREST
         * NEAREST_MIPMAP_LINEAR
         * LINEAR_MIPMAP_NEAREST
         * LINEAR_MIPMAP_LINEAR
         * 前两个配置项和 MAG_FILTER 的含义和作用是完全一样的。
         * 但问题是,当纹理被缩小时,原纹理中并不是每一个像素周围都会落上采样点,这就导致了某些像素,完全没有参与纹理的计算,新纹理丢失了一些信息。
         * 假设一种极端的情况,就是一个纹理彻底缩小为了一个点,那么这个点的值应当是纹理上所有像素颜色的平均值,这才比较合理。
         * 但是 NEAREST 只会从纹理中取一个点,而 LINEAR 也只是从纹理中取了四个点计算了一下而已。这时候,就该用上 MIPMAP 了
         * 
         * 为了在纹理缩小也获得比较好的效果,需要按照采样密度,选择一定数量(通常大于 LINEAR 的 4 个,极端情况下为原纹理上所有像素)的像素进行计算。
         * 实时进行计算的开销是很大的,所有有一种称为 MIPMAP(金字塔)的技术。
         * 在纹理创建之初,就为纹理创建好 MIPMAP,比如对 512x512 的纹理,依次建立 256x256(称为 1 级 Mipmap)、128x128(称为 2 级 Mipmap) 乃至 2x2、1x1 的纹理。
         * 实时渲染时,根据采样密度选择其中的某一级纹理,以此避免运行时的大量计算
         */
        // 设定参数, 放大滤镜和缩小滤镜的采样方式
        //放大
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
        //缩小
        //一旦使用(NEAREST_MIPMAP_NEAREST,NEAREST_MIPMAP_LINEAR,LINEAR_MIPMAP_NEAREST,LINEAR_MIPMAP_LINEAR)
        //说明就要使用mipmap了啊
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR_MIPMAP_LINEAR);

        // 设定参数, x 轴和 y 轴为镜面重复绘制
        //纹理的填充模式
        /**
         * gl.REPEAT
         * gl.CLAMP_TO_EDGE
         * gl.MIRRORED_REPEAT
         */
        //水平方向
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.MIRRORED_REPEAT);
        //垂直方向
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.MIRRORED_REPEAT);

        

        // 清除当前操作的贴图
        this._gl.bindTexture(this._gl.TEXTURE_2D, null);
    }

第三步在内存中干掉图像缓存数据img

public releaseCPUMemoryForImageCache(img:HTMLImageElement):void{
        img.src = "";
        img = null;
    }

第四步在显存中干掉纹理数据

public destroy() {
        if (this._glID === _nullWebGLTexture) {
          console.error('The texture already destroyed');
          return;
        }
        this._gl.deleteTexture(this._glID);
        this._glID = _nullWebGLTexture;
      }

查看工具

打开chrome浏览器:shift+esc,打开任务管理器,选中图片缓存和GPU内存,图片缓存指的是cpu内存这边从本地将图片加载到内存的纹理数据,GPU内存指的是将内存中的纹理数据发送到GPU显存重新生成的纹理数据,

查看数据在cpu还是GPU 查看cpu和gpu占用_贴图

题外话—上传到GPU的数据

顶点信息:顶点的位置,顶点的颜色,顶点的法线,顶点的uv坐标, 顶点的切线,模型对应的纹理,模型的pvm矩阵,光照
通常调用gl.createBuffer(),创建一个glID,这个是一个显存内存的标识id,我们可以把这个id绑定到GPU的操作缓冲上,然后再给这个glID绑定一个真实的数据,下次我们如果想使用这个数据的时候,只需要用这个glID来绑定这个缓冲,这个时候进行一些取值操作就可以了,下面对于GPU的读写我会做一个更为详细的说明

one:我们上传给GPU的数据,GPU那边都会创建一个buffer来存储,返回一个glID表示,注意这个glID是灵魂

this._glID = gl.createBuffer();//用于纹理
this._glID = gl.createTexture();//用于立方体纹理

two:GPU是一个强大的状态机,我们外界GPU进行读写数据的时候都会有一个骚操作,bindbuffer

//顶点 法线 uv 切线
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this._glID);
//索引
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this._glID);

three:当绑定完换冲以后,就可以开始写入操作了

//顶点 法线 uv 切线等
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.sourceData), this.gl.STATIC_DRAW);
//索引
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.sourceData), this.gl.STATIC_DRAW);

four:当渲染的时候,就会开始读取操作了,其实读取就是往shader中的变量赋值

//将buffer中数据和shader中变量建立读取联系
this._gl.bindBuffer(this._gl.ARRAY_BUFFER, glID);
this._gl.vertexAttribPointer(this.a_position_loc, itemSize, this._gl.FLOAT, false, 0, 0);

//下面是纹理的赋值
/**
  * activeTexture必须在bindTexture之前。如果没activeTexture就bindTexture,会默认绑定到0号纹理单元
*/
// 激活 0 号纹理单元
this._gl.activeTexture(this._gl[glTEXTURE_UNIT_VALID[pos]]);
// 指定当前操作的贴图
this._gl.bindTexture(this._gl.TEXTURE_2D, glID);
if (this.checklocValid(this.u_texCoord_loc)) {
    this._gl.uniform1i(this.u_texCoord_loc, pos);
}

five:开始渲染
我们可以利用事先传来的索引数据来绘制,对于绘制的类型也有很大种
其实所谓索引绘制,就是把顶点抽象出来,为每一个顶点设置一个id,然后用这个id组成一个数组,GPU会根据这个id数组按照顺序和绘制的一些参数,来绘制这些顶点

/*
 POINTS: 0,         // gl.POINTS  要绘制一系列的点
LINES: 1,          // gl.LINES   要绘制了一系列未连接直线段(单独行)
LINE_LOOP: 2,      // gl.LINE_LOOP  要绘制一系列连接的线段
LINE_STRIP: 3,     // gl.LINE_STRIP  要绘制一系列连接的线段。它还连接的第一和最后的顶点,以形成一个环
TRIANGLES: 4,      // gl.TRIANGLES  一系列单独的三角形;绘制方式:(v0,v1,v2),(v1,v3,v4)
TRIANGLE_STRIP: 5, // gl.TRIANGLE_STRIP  一系列带状的三角形
TRIANGLE_FAN: 6,   // gl.TRIANGLE_FAN  扇形绘制方式
*/
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.getGLID(SY.GLID_TYPE.INDEX));
this.gl.drawElements(this._glPrimitiveType, this.getBuffer(SY.GLID_TYPE.INDEX).itemNums, this.gl.UNSIGNED_SHORT, 0);

six:小结
我们上传到GPU的数据,GPU会生成一个glID来保存这些数据,所以如果我们没有改变数据的话,那数据是不需要重新上传到GPU的,渲染的时候,通过glID,我们就可以操作老的数据