美文网首页Android开发Android开发Android技术知识
Android多媒体之GLES2战记第五集--宇宙之光

Android多媒体之GLES2战记第五集--宇宙之光

作者: e4e52c116681 | 来源:发表于2019-01-29 10:50 被阅读8次

    你以为我的封面图只是吸引眼球?

    上集说到:用矩阵的变换来操作顶点,使图形产生相应的变化(移动,选择,缩放)
    这一集将点亮世界之光,让你对OpenGL的世界有更深的了解


    普通副本五:黑龙之珠

    本副本参照《Android 3D游戏开发技术宝典 OpenGL ES 2.0》
    但是分析的要详细一些,书中绘制的方法只是一笔带过,感觉球面还是需要挖挖的
    而且书中源码绘制部分写的也挺乱的,该抽的我抽了一下,看着好看些

    球面的拼接.gif
    1.第一关卡:球面的顶点计算

    也就是经纬取分割点,再将这些点拼成三角形形成曲面效果
    下面应该很形象的说明了渐变的过程

    增加切割点数.png 经纬度
    /**
     * 初始化顶点坐标数据的方法
     *
     * @param r          半径
     * @param splitCount 赤道分割点数
     */
    public void initVertex(float r, int splitCount) {
        // 顶点坐标数据的初始化================begin============================
        ArrayList<Float> vertixs = new ArrayList<>();// 存放顶点坐标的ArrayList
        final float dθ = 360.f / splitCount;// 将球进行单位切分的角度
        //垂直方向angleSpan度一份
        for (float α = -90; α < 90; α = α + dθ) {
            // 水平方向angleSpan度一份
            for (float β = 0; β <= 360; β = β + dθ) {
                // 纵向横向各到一个角度后计算对应的此点在球面上的坐标
                float x0 = r * cos(α) * cos(β);
                float y0 = r * cos(α) * sin(β);
                float z0 = r * sin(α);
                float x1 = r * cos(α) * cos(β + dθ);
                float y1 = r * cos(α) * sin(β + dθ);
                float z1 = r * sin(α);
                float x2 = r * cos(α + dθ) * cos(β + dθ);
                float y2 = r * cos(α + dθ) * sin(β + dθ);
                float z2 = r * sin(α + dθ);
                float x3 = r * cos(α + dθ) * cos(β);
                float y3 = r * cos(α + dθ) * sin(β);
                float z3 = r * sin(α + dθ);
                // 将计算出来的XYZ坐标加入存放顶点坐标的ArrayList
                vertixs.add(x1);vertixs.add(y1);vertixs.add(z1);//p1
                vertixs.add(x3);vertixs.add(y3);vertixs.add(z3);//p3
                vertixs.add(x0);vertixs.add(y0);vertixs.add(z0);//p0
                vertixs.add(x1);vertixs.add(y1);vertixs.add(z1);//p1
                vertixs.add(x2);vertixs.add(y2);vertixs.add(z2);//p2
                vertixs.add(x3);vertixs.add(y3);vertixs.add(z3);//p3
            }
        }
        verticeCount = vertixs.size() / 3;// 顶点的数量为坐标值数量的1/3,因为一个顶点有3个坐标
        // 将vertices中的坐标值转存到一个float数组中
        float vertices[] = new float[verticeCount * 3];
        for (int i = 0; i < vertixs.size(); i++) {
            vertices[i] = vertixs.get(i);
        }
        vertexBuffer = GLUtil.getFloatBuffer(vertices);
    }
    
    /**
     * 求sin值
     *
     * @param θ 角度值
     * @return sinθ
     */
    private float sin(float θ) {
        return (float) Math.sin(Math.toRadians(θ));
    }
    
    /**
     * 求cos值
     *
     * @param θ 角度值
     * @return cosθ
     */
    private float cos(float θ) {
        return (float) Math.cos(Math.toRadians(θ));
    }
    

    2.第二关卡:着色器的代码及使用
    2.1:片元着色代码:ball.frag

    添加uR句柄,vPosition获取顶点坐标,根据坐标来进行着色

    precision mediump float;
     uniform float uR;
     varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
     void main(){
        vec3 color;
        float n = 8.0;//一个坐标分量分的总份数
        float span = 2.0*uR/n;//每一份的长度
        //每一维在立方体内的行列数
        int i = int((vPosition.x + uR)/span);
        int j = int((vPosition.y + uR)/span);
        int k = int((vPosition.z + uR)/span);
        //计算当点应位于白色块还是黑色块中
        int whichColor = int(mod(float(i+j+k),2.0));
        if(whichColor == 1) {//奇数时
                color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
        } else {//偶数时为白色
                color = vec3(1.0,1.0,1.0);//白色
        }
        //将计算出的颜色给此片元
        gl_FragColor=vec4(color,0);
     }
    

    2.2:顶点着色代码:ball.vert
    uniform mat4 uMVPMatrix; 
    attribute vec3 aPosition;
    varying vec3 vPosition;
    void main(){
       gl_Position = uMVPMatrix * vec4(aPosition,1);
       vPosition = aPosition;
    }
    

    2.3:着色器的使用
    //声明句柄
    private int mPositionHandle;//位置句柄
    private int muMVPMatrixHandle;//顶点变换矩阵句柄
    private int muRHandle;//半径的句柄
    
    //获取句柄
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    muRHandle = GLES20.glGetUniformLocation(mProgram, "uR");
    
    //使用句柄
    GLES20.glEnableVertexAttribArray(mPositionHandle);//启用顶点的句柄
    //顶点矩阵变换
    GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
    //准备顶点坐标数据
    GLES20.glVertexAttribPointer(
            mPositionHandle,//int indx, 索引
            COORDS_PER_VERTEX,//int size,大小
            GLES20.GL_FLOAT,//int type,类型
            false,//boolean normalized,//是否标准化
            vertexStride,// int stride,//跨度
            vertexBuffer);// java.nio.Buffer ptr//缓冲
    // 将半径尺寸传入shader程序
    GLES20.glUniform1f(muRHandle, mR * Cons.UNIT_SIZE);
    

    3.第三关卡:关于UNIT_SIZE

    就是一个尺寸的伸缩量而已,定义成常量,方便放大与缩小,没别的

    UNIT_SIZE.png
    普通副本六宇宙之光

    OpenGL ES 中只有三种光:

    环境光:光照的作用全体
    散射光:单点光源
    镜面光:镜面反射
    

    1.第一关卡:环境光

    就像太阳光,我们身处的环境,环境中,光照的作用结果一致
    修改起来也比较方便,环境光说白了就是对片元颜色的运算而已

    0.35 0.75 1.00

    1.1.片元着色器ball.frag
    precision mediump float;
     uniform float uR;
     varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
     varying vec4 vAmbient;//接收从顶点着色器过来的环境光分量
     void main(){
        vec3 color;
        float n = 8.0;//一个坐标分量分的总份数
        float span = 2.0*uR/n;//每一份的长度
        //每一维在立方体内的行列数
        int i = int((vPosition.x + uR)/span);
        int j = int((vPosition.y + uR)/span);
        int k = int((vPosition.z + uR)/span);
        //计算当点应位于白色块还是黑色块中
        int whichColor = int(mod(float(i+j+k),2.0));
        if(whichColor == 1) {//奇数时
                color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
        } else {//偶数时为白色
                color = vec3(1.0,1.0,1.0);//白色
        }
        //最终颜色
        vec4 finalColor=vec4(color,0);
        //给此片元颜色值
        gl_FragColor=finalColor*vAmbient;
     }
    

    1.2.顶点着色器ball.vert
    uniform mat4 uMVPMatrix; //总变换矩阵
    attribute vec3 aPosition;  //顶点位置
    varying vec3 vPosition;//用于传递给片元着色器的顶点位置
    uniform vec4 uAmbient;
    varying vec4 vAmbient;//用于传递给片元着色器的环境光分量
    
    void main(){
       //根据总变换矩阵计算此次绘制此顶点位置
       gl_Position = uMVPMatrix * vec4(aPosition,1);
       //将顶点的位置传给片元着色器
       vPosition = aPosition;//将原始顶点位置传递给片元着色器
       //将的环境光分量传给片元着色器
       vAmbient = vec4(uAmbient);
    }
    

    1.3.使用:句柄拿到传值而已,也没什么难的
    private int muAmbientHandle;//环境光句柄
    //获取环境光句柄
    muAmbientHandle = GLES20.glGetUniformLocation(mProgram, "uAmbient");
    //使用环境光
    GLES20.glUniform4f(muAmbientHandle, 0.5f,0.5f,0.5f,1f);
    

    1.第二关卡:散射光

    忙活了好一会,总算搞定了,一个api用错了,一直崩溃...
    相当于打个灯,灯的位置是固定不动的

    -1,1,-1 1,1,-1

    看下图的点光源在(1,1,-1) 你应该知道灯在哪了吧,注意看轴色

    散射光.png
    2.1:顶点着色器:ball.vert

    代码有点复杂,做好心理准备

    uniform mat4 uMVPMatrix; //总变换矩阵
    attribute vec3 aPosition;  //顶点位置
    varying vec3 vPosition;//用于传递给片元着色器的顶点位置
    
    varying vec4 uAmbient;//环境光分量
    varying vec4 vAmbient;//用于传递给片元着色器的环境光分量
    
    uniform mat4 uMMatrix;//变换矩阵(包括平移、旋转、缩放)
    uniform vec3 uLightLocation;//光源位置
    attribute vec3 aNormal;//顶点法向量
    varying vec4 vDiffuse;  //用于传递给片元着色器的散射光分量
    
    //散射光光照计算的方法(法向量,散射光计算结果,光源位置,散射光强度)
    void pointLight (in vec3 normal,inout vec4 diffuse,in vec3 lightLocation,in vec4 lightDiffuse){
      vec3 normalTarget=aPosition+normal;//计算变换后的法向量
      vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
      newNormal=normalize(newNormal);//对法向量规格化
       //计算从表面点到光源位置的向量vp
      vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
      vp=normalize(vp);//单位化vp
      float nDotViewPosition=max(0.0,dot(newNormal,vp));//求法向量与vp向量的点积与0的最大值
      diffuse=lightDiffuse*nDotViewPosition;//计算散射光的最终强度
    }
    
    void main(){
       //根据总变换矩阵计算此次绘制此顶点位置
       gl_Position = uMVPMatrix * vec4(aPosition,1);
       //将顶点的位置传给片元着色器
       vPosition = aPosition;//将原始顶点位置传递给片元着色器
    
        vec4 diffuseTemp=vec4(0.0,0.0,0.0,0.0);
        pointLight(normalize(aNormal), diffuseTemp, uLightLocation, vec4(0.8,0.8,0.8,1.0));
        vDiffuse=diffuseTemp;//将散射光最终强度传给片元着色器
    
       //将的环境光分量传给片元着色器
       vAmbient = vec4(uAmbient);
    }
    

    2.2:片元着色器:ball.frag
    precision mediump float;
       uniform float uR;
       varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
       varying vec4 vAmbient;//接收从顶点着色器过来的环境光分量
       varying vec4 vDiffuse;//接收从顶点着色器过来的散射光分量
       void main(){
          vec3 color;
          float n = 8.0;//一个坐标分量分的总份数
          float span = 2.0*uR/n;//每一份的长度
          //每一维在立方体内的行列数
          int i = int((vPosition.x + uR)/span);
          int j = int((vPosition.y + uR)/span);
          int k = int((vPosition.z + uR)/span);
          //计算当点应位于白色块还是黑色块中
          int whichColor = int(mod(float(i+j+k),2.0));
          if(whichColor == 1) {//奇数时
                color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
          } else {//偶数时为白色
                color = vec3(1.0,1.0,1.0);//白色
          }
          //最终颜色
          vec4 finalColor=vec4(color,0);
          //给此片元颜色值
    //      gl_FragColor=finalColor*vAmbient;//环境光
          gl_FragColor=finalColor*vDiffuse;
       }
    

    2.3:使用

    新添了三个句柄,用法也是写烂了...
    这里用一个GLState类管理全局的状态

    ---->[Ball#initProgram]-----------
    //获取顶点法向量的句柄
    maNormalHandle = GLES20.glGetAttribLocation(mProgram, "aNormal");
    //获取程序中光源位置引用
    maLightLocationHandle = GLES20.glGetUniformLocation(mProgram, "uLightLocation");
    //获取位置、旋转变换矩阵引用
    muMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");
    
    ---->[Ball#draw]-----------
    //将位置、旋转变换矩阵传入着色器程序
    GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, MatrixStack.getOpMatrix(), 0);
    //将光源位置传入着色器程序
    GLES20.glUniform3fv(maLightLocationHandle, 1, GLState.lightPositionFB);
    //将顶点法向量数据传入渲染管线
    GLES20.glVertexAttribPointer(maNormalHandle, 3, GLES20.GL_FLOAT, false,
                    3 * 4, vertexBuffer);
                    
    ---->[GLState.java]-----------
    ////////----------设置光源
    private static float[] lightLocation = new float[]{0, 0, 0};//定位光光源位置
    public static FloatBuffer lightPositionFB;
    //设置灯光位置的方法
    public static void setLightLocation(float x, float y, float z) {
        lightLocation[0] = x;
        lightLocation[1] = y;
        lightLocation[2] = z;
        lightPositionFB = getFloatBuffer(lightLocation);
    }
    
    ---->[WorldRenderer#onDrawFrame]-----------
    GLState.setLightLocation(-1, 1, -1);
    

    3.第三关卡:镜面光

    真的有些hold不住了...
    同一束光,照在粗糙度不同的物体上,越光滑,我们可以看到的部分就越多
    单独写了一个Ball_M.java的类,以及两个ball_m.vert,ball_m.frag着色器
    平面光就够喝一壶的了,升级到三维...还是先用着吧,原理等百无聊赖的时候再分析吧

    镜面反射.png
    3.1:顶点着色器:ball_m.vert
    uniform mat4 uMVPMatrix;    //总变换矩阵
    uniform mat4 uMMatrix;      //变换矩阵
    uniform vec3 uLightLocation;    //光源位置
    uniform vec3 uCamera;       //摄像机位置
    attribute vec3 aPosition;   //顶点位置
    attribute vec3 aNormal;     //法向量
    varying vec3 vPosition;     //用于传递给片元着色器的顶点位置
    varying vec4 vSpecular;     //用于传递给片元着色器的镜面光最终强度
    void pointLight(                //定位光光照计算的方法
      in vec3 normal,           //法向量
      inout vec4 specular,      //镜面反射光分量
      in vec3 lightLocation,        //光源位置
      in vec4 lightSpecular     //镜面光强度
    ){
      vec3 normalTarget=aPosition+normal;   //计算变换后的法向量
      vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
      newNormal=normalize(newNormal);   //对法向量规格化
      //计算从表面点到摄像机的向量
      vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
      //计算从表面点到光源位置的向量vp
      vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
      vp=normalize(vp);//格式化vp
      vec3 halfVector=normalize(vp+eye);    //求视线与光线的半向量
      float shininess=5.0;              //粗糙度,越小越光滑
      float nDotViewHalfVector=dot(newNormal,halfVector);           //法线与半向量的点积
      float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess));     //镜面反射光强度因子
      specular=lightSpecular*powerFactor;    //最终的镜面光强度
    }
    void main()  {
       gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点的位置
       vec4 specularTemp=vec4(0.0,0.0,0.0,0.0);
       pointLight(normalize(aNormal), specularTemp, uLightLocation, vec4(0.7,0.7,0.7,1.0));//计算镜面光
       vSpecular=specularTemp;  //将最终镜面光强度传给片元着色器
       vPosition = aPosition;       //将顶点的位置传给片元着色器
    } 
    

    3.2:片元着色器:ball_m.vert
    precision mediump float;
    uniform float uR;
    varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
    varying vec4 vSpecular;//接收从顶点着色器过来的镜面反射光分量
    void main(){
       vec3 color;
       float n = 8.0;//一个坐标分量分的总份数
       float span = 2.0*uR/n;//每一份的长度
       //每一维在立方体内的行列数
       int i = int((vPosition.x + uR)/span);
       int j = int((vPosition.y + uR)/span);
       int k = int((vPosition.z + uR)/span);
       //计算当点应位于白色块还是黑色块中
       int whichColor = int(mod(float(i+j+k),2.0));
       if(whichColor == 1) {//奇数时为红色
            color = vec3(0.678,0.231,0.129);//红色
       }
       else {//偶数时为白色
            color = vec3(1.0,1.0,1.0);//白色
       }
       //最终颜色
       vec4 finalColor=vec4(color,0);
       //给此片元颜色值
       gl_FragColor=finalColor*vSpecular;
    }
    

    3.使用

    增加了一个uCamera句柄,增加相机位置的状态,在MatrixStack#lookAt里初始化

    maCameraHandle = GLES20.glGetUniformLocation(mProgram, "uCamera");
    ---->[Ball_M#draw]-------
    GLES20.glUniform3fv(maCameraHandle, 1, GLState.cameraFB);
    
    ---->[GLState]-------
    ////////----------设置相机位置
    static float[] cameraLocation = new float[3];//摄像机位置
    public static FloatBuffer cameraFB;
    //设置灯光位置的方法
    public static void setCameraLocation(float x, float y, float z) {
        cameraLocation[0] = x;
        cameraLocation[1] = y;
        cameraLocation[2] = z;
        cameraFB = GLUtil.getFloatBuffer(cameraLocation);
    }
    
    ---->[MatrixStack#lookAt]-------
    GLState.setCameraLocation(cx, cy, cz);//设置相机位置
    

    4.第四关卡:三光同时作用

    就是综合一下而已...,跟书中小不同,这里我把粗糙度和环境光提出来了
    基本上代码里没有什么变化,终点在着色器里

    三光.gif 三光.png
    ---->[ball.vert]---------------------
    uniform mat4 uMVPMatrix;        //总变换矩阵
    uniform mat4 uMMatrix;          //变换矩阵
    uniform vec3 uLightLocation;        //光源位置
    uniform vec3 uCamera;           //摄像机位置
    uniform float uShininess;           //摄像机位置
    uniform vec4 uAmbient;//环境光
    attribute vec3 aPosition;       //顶点位置
    attribute vec3 aNormal;         //法向量
    varying vec3 vPosition;         //用于传递给片元着色器的顶点位置
    varying vec4 vAmbient;          //用于传递给片元着色器的环境光最终强度
    varying vec4 vDiffuse;          //用于传递给片元着色器的散射光最终强度
    varying vec4 vSpecular;         //用于传递给片元着色器的镜面光最终强度
    
    void pointLight(                    //定位光光照计算的方法
      in vec3 normal,               //法向量
      inout vec4 ambient,           //环境光最终强度
      inout vec4 diffuse,               //散射光最终强度
      inout vec4 specular,          //镜面光最终强度
      in vec3 lightLocation,            //光源位置
      in vec4 lightAmbient,         //环境光强度
      in vec4 lightDiffuse,         //散射光强度
      in vec4 lightSpecular         //镜面光强度
    ){
      ambient=lightAmbient;         //直接得出环境光的最终强度
      vec3 normalTarget=aPosition+normal;   //计算变换后的法向量
      vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
      newNormal=normalize(newNormal);   //对法向量规格化
      //计算从表面点到摄像机的向量
      vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
      //计算从表面点到光源位置的向量vp
      vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
      vp=normalize(vp);//格式化vp
      vec3 halfVector=normalize(vp+eye);    //求视线与光线的半向量
      float shininess=uShininess;               //粗糙度,越小越光滑
      float nDotViewPosition=max(0.0,dot(newNormal,vp));    //求法向量与vp的点积与0的最大值
      diffuse=lightDiffuse*nDotViewPosition;                //计算散射光的最终强度
      float nDotViewHalfVector=dot(newNormal,halfVector);   //法线与半向量的点积
      float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess));     //镜面反射光强度因子
      specular=lightSpecular*powerFactor;               //计算镜面光的最终强度
    }
    void main(){
       gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
       vec4 ambientTemp,diffuseTemp,specularTemp;     //用来接收三个通道最终强度的变量
       pointLight(normalize(aNormal),ambientTemp,diffuseTemp,specularTemp,uLightLocation,
       uAmbient,vec4(0.8,0.8,0.8,1.0),vec4(0.7,0.7,0.7,1.0));
       vAmbient=ambientTemp;        //将环境光最终强度传给片元着色器
       vDiffuse=diffuseTemp;        //将散射光最终强度传给片元着色器
       vSpecular=specularTemp;      //将镜面光最终强度传给片元着色器
       vPosition = aPosition;  //将顶点的位置传给片元着色器
    }
    
    ---->[ball.frag]---------------------
    precision mediump float;
    uniform float uR;
    varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
    varying vec4 vAmbient;//接收从顶点着色器过来的环境光分量
    varying vec4 vDiffuse;//接收从顶点着色器过来的散射光分量
    varying vec4 vSpecular;//接收从顶点着色器过来的镜面反射光分量
    void main()
    {
       vec3 color;
       float n = 8.0;//一个坐标分量分的总份数
       float span = 2.0*uR/n;//每一份的长度
       //每一维在立方体内的行列数
       int i = int((vPosition.x + uR)/span);
       int j = int((vPosition.y + uR)/span);
       int k = int((vPosition.z + uR)/span);
       //计算当点应位于白色块还是黑色块中
       int whichColor = int(mod(float(i+j+k),2.0));
       if(whichColor == 1) {//奇数时
            color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
       }
       else {//偶数时为白色
            color = vec3(1.0,1.0,1.0);//白色
       }
       //最终颜色
       vec4 finalColor=vec4(color,0);
       //给此片元颜色值
       gl_FragColor=finalColor*vAmbient + finalColor*vDiffuse + finalColor*vSpecular;
    }
    
    ---->[GLState.java]---------------------
    ////////----------环境光
    static float[] eviLight = new float[4];//摄像机位置
    public static FloatBuffer eviLightFB;
    //设置灯光位置的方法
    public static void setEviLight(float r, float g, float b,float a) {
        eviLight[0] = r;
        eviLight[1] = g;
        eviLight[2] = b;
        eviLight[3] = a;
        eviLightFB = GLUtil.getFloatBuffer(eviLight);
    }
    
    ---->[Ball.java]---------------------
    //粗糙度
    muShininessHandle = GLES20.glGetUniformLocation(mProgram, "uShininess");
    
    GLES20.glUniform1f(muShininessHandle, 30);
    

    普通副本七:龙之盛装LEVEL2

    新手副本龙之盛装LEVEL1中已经简单知道了纹理的贴图
    这个副本将来深入了解一下贴图

    贴图展示.gif
    1.第一关卡:纹理坐标系

    纹理坐标系(右侧)是一个二维坐标,方向和Android中的屏幕坐标系一致
    书上说贴图的宽高像素数必须是2的n次方,但是我试了不是也可以。为免争议,这里用的是2的n次方

    点位坐标与纹理坐标.png 贴图1.png
    static float vertexs[] = {   //以逆时针顺序
            -1, 1, 0,
            -1, -1, 0,
            1, -1, 0,
    };
    
    private final float[] textureCoo = {
            0.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
    };
    

    2.第二关卡:矩形

    先用三点矩形来画,比较形象一些,就是两个三角形拼合

    矩形.png
    static float vertexs[] = {   //以逆时针顺序
            -1, 1, 0,
            -1, -1, 0,
            1, -1, 0,
            
            1, -1, 0,
            1, 1, 0,
            -1, 1, 0
    };
    
    private final float[] textureCoo = {
            0, 0,
            0, 1,
            1, 1,
            
            1, 1,
            1, 0,
            0, 0
    };
    

    3.第三关卡:纹理的裁剪与拉伸
    剪裁.png
    3.1:添加两个系数控制纹理坐标的大小
    ---->[TextureRectangle]---------
    float s = 1;//s纹理坐标系数
    float t = 1f;//t纹理坐标系数
    
    private final float[] textureCoo = {
            0, 0,
            0, t,
            s, t,
            
            s, t,
            s, 0,
            0, 0
    };
    

    3.2:加载纹理的工具

    其中s和t的包裹方式:GL_CLAMP_TO_EDGE

    //---------------纹理加载工具--GLUtil.java-----
    
    /**
     * 资源id 加载纹理
     *
     * @param ctx   上下文
     * @param resId 资源id
     * @return 纹理id
     */
    public static int loadTexture(Context ctx, int resId) {
        Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), resId);
        return loadTexture(ctx, bitmap);
    }
    
    /**
     * bitmap 加载纹理
     *
     * @param ctx    上下文
     * @param bitmap bitmap
     * @return 纹理id
     */
    public static int loadTexture(Context ctx, Bitmap bitmap) {
        //生成纹理ID
        int[] textures = new int[1];
        //(产生的纹理id的数量,纹理id的数组,偏移量)
        GLES20.glGenTextures(1, textures, 0);
        int textureId = textures[0];
        //绑定纹理id
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        //采样方式MIN
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    
        //设置s轴包裹方式---截取
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        //设置t轴包裹方式---截取
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        //实际加载纹理(纹理类型,纹理的层次,纹理图像,纹理边框尺寸)
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();          //纹理加载成功后释放图片
        return textureId;
    }
    

    4.第四关卡:纹理的重复

    这和css的重复方式挺像的,看一眼就应该明白,我就不废话了
    要改的就两局代码:GLUtil#loadTexture

    重复
    //设置s轴拉伸方式---重复
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,  GLES20.GL_REPEAT);
    //设置t轴拉伸方式---重复
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,  GLES20.GL_REPEAT);
    

    5.第五关卡:重复模式的封装
    5.1:重复模式的枚举
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/16/016:9:31<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:重复方式枚举
     */
    public enum RepeatType {
        NONE,//不重复
        REPEAT_X,//仅x轴重复
        REPEAT_Y,//仅y轴重复
        REPEAT//x,y重复
    }
    

    5.1:加载纹理方法的封装
    //---------------纹理加载工具--GLUtil.java-----
    /**
     * 资源id 加载纹理,默认重复方式:RepeatType.REPEAT
     *
     * @param ctx   上下文
     * @param resId 资源id
     * @return 纹理id
     */
    public static int loadTexture(Context ctx, int resId) {
        return loadTexture(ctx, resId, RepeatType.REPEAT);
    }
    
    /**
     * 资源id 加载纹理
     *
     * @param ctx        上下文
     * @param resId      资源id
     * @param repeatType 重复方式 {@link com.toly1994.picture.world.bean.RepeatType}
     * @return 纹理id
     */
    public static int loadTexture(Context ctx, int resId, RepeatType repeatType) {
        Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), resId);
        return loadTexture(bitmap, repeatType);
    }
    
    /**
     * bitmap 加载纹理
     *
     * @param bitmap     bitmap
     * @param repeatType 重复方式 {@link com.toly1994.picture.world.bean.RepeatType}
     * @return 纹理id
     */
    public static int loadTexture(Bitmap bitmap, RepeatType repeatType) {
        //生成纹理ID
    
        int[] textures = new int[1];
        //(产生的纹理id的数量,纹理id的数组,偏移量)
        GLES20.glGenTextures(1, textures, 0);
        int textureId = textures[0];
        //绑定纹理id
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        //采样方式MIN
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    
        int wrapS = 0;
        int wrapT = 0;
        switch (repeatType) {
            case NONE:
                wrapS = GLES20.GL_CLAMP_TO_EDGE;
                wrapT = GLES20.GL_CLAMP_TO_EDGE;
                break;
            case REPEAT_X:
                wrapS = GLES20.GL_REPEAT;
                wrapT = GLES20.GL_CLAMP_TO_EDGE;
                break;
            case REPEAT_Y:
                wrapS = GLES20.GL_CLAMP_TO_EDGE;
                wrapT = GLES20.GL_REPEAT;
                break;
            case REPEAT:
                wrapS = GLES20.GL_REPEAT;
                wrapT = GLES20.GL_REPEAT;
                break;
        }
    
        //设置s轴拉伸方式---重复
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, wrapS);
        //设置t轴拉伸方式---重复
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, wrapT);
    
        //实际加载纹理(纹理类型,纹理的层次,纹理图像,纹理边框尺寸)
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();          //纹理加载成功后释放图片
        return textureId;
    }
    
    封装重复方式.png

    当然这也仅是纹理的简单认识,跟高级的龙之盛装副本,敬请期待


    普通副本八:黑龙之型 LEVEL1

    你以为我的封面图只是吸引眼球?

    效果.gif
    1.第一关卡:3DMAX与.obj文件

    3DMAX大学的时候用过,知道OpenGL ES 可以加载3DMAX的模型,激动之心无法言表
    模型自己去网上下,3DMAX装软件我也不废话了,安装教程一大堆

    导出obj.png

    可见都是点的数据,现在要开始解析数据了,Are you ready?

    内容.png
    2.第二关卡:加载与解析点:

    参见《Android 3D游戏开发技术宝典 OpenGL ES 2.0》

    //-------------加载obj点集----------------
    //从obj文件中加载仅携带顶点信息的物体
    public static float[] loadPosInObj(String name, Context ctx) {
        ArrayList<Float> alv = new ArrayList<>();//原始顶点坐标列表
        ArrayList<Float> alvResult = new ArrayList<>();//结果顶点坐标列表
        try {
            InputStream in = ctx.getAssets().open(name);
            InputStreamReader isr = new InputStreamReader(in);
            BufferedReader br = new BufferedReader(isr);
            String temps = null;
            while ((temps = br.readLine()) != null) {
                String[] tempsa = temps.split("[ ]+");
                if (tempsa[0].trim().equals("v")) {//此行为顶点坐标
                    alv.add(Float.parseFloat(tempsa[1]));
                    alv.add(Float.parseFloat(tempsa[2]));
                    alv.add(Float.parseFloat(tempsa[3]));
                } else if (tempsa[0].trim().equals("f")) {//此行为三角形面
                    int index = Integer.parseInt(tempsa[1].split("/")[0]) - 1;
                    alvResult.add(alv.get(3 * index));
                    alvResult.add(alv.get(3 * index + 1));
                    alvResult.add(alv.get(3 * index + 2));
                    index = Integer.parseInt(tempsa[2].split("/")[0]) - 1;
                    alvResult.add(alv.get(3 * index));
                    alvResult.add(alv.get(3 * index + 1));
                    alvResult.add(alv.get(3 * index + 2));
                    index = Integer.parseInt(tempsa[3].split("/")[0]) - 1;
                    alvResult.add(alv.get(3 * index));
                    alvResult.add(alv.get(3 * index + 1));
                    alvResult.add(alv.get(3 * index + 2));
                }
            }
        } catch (Exception e) {
            Log.d("load error", "load error");
            e.printStackTrace();
        }
        //生成顶点数组
        int size = alvResult.size();
        float[] vXYZ = new float[size];
        for (int i = 0; i < size; i++) {
            vXYZ[i] = alvResult.get(i);
        }
        return vXYZ;
    }
    

    3.第三关卡:绘制:ObjShape.java
    3.1:绘制无果

    激动人心的时刻到了...点集在手天下我有
    然后果然不出所料...没有出现,我就想不会这么简单吧

    /**
     * 缓冲数据
     */
    private void bufferData() {
        float[] vertexs = GLUtil.loadPosInObj("obj.obj", mContext);
        mVertexCount = vertexs.length / COORDS_PER_VERTEX;
        vertexBuffer = GLUtil.getFloatBuffer(vertexs);
    }
    

    3.2:全体缩放

    碰到问题怎么办? 废话,debug 啊。然后秒发现坐标是200多,晕,怪不得
    聪明的你肯定能想到,缩小呗,总算出来了,but违和感十足,坐标系都没了。怎么办?

    缩小100倍.png
    MatrixStack.save();
    MatrixStack.rotate(currDeg, 0, 1, 0);
    MatrixStack.scale(0.01f,0.01f,0.01f);
    mWorldShape.draw(MatrixStack.peek());
    MatrixStack.restore();
    

    3.3:截胡

    我在ObjShape里用个缩放矩阵,截胡不就行了吗?

    截胡缩小.png

    然后再移动一下放在中间

    ---->[WorldRenderer]----------
    MatrixStack.save();
    MatrixStack.rotate(currDeg, 0, 1, 0);
    // MatrixStack.scale(0.01f,0.01f,0.01f);
    mWorldShape.draw(MatrixStack.peek());
    MatrixStack.restore();
    
    ---->[ObjShape]----------
    private static float[] mMVPMatrix = new float[16];//最终矩阵
    
    ---->[ObjShape#draw]----------
    Matrix.scaleM(mMVPMatrix, 0, mvpMatrix, 0, 0.02f, 0.02f, 0.02f);
    Matrix.translateM(mMVPMatrix,0,-230,-50,30);
    GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
    
    
    移动.png
    本集结束,下一集:九层之台 敬请期待

    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 备注
    V0.1-github 2018-1-16 Android多媒体之GLES2战记第五集--宇宙之光
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的掘金 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    相关文章

      网友评论

        本文标题:Android多媒体之GLES2战记第五集--宇宙之光

        本文链接:https://www.haomeiwen.com/subject/hlatsqtx.html