前言

对于openGL不熟悉的小伙伴,这里介绍一下绘制的流程:

  • 计算投影矩阵,物体变化矩阵,视角矩阵
  • 传入物体的顶点缓存,
  • 将参数传入着色器(包括上述矩阵)
  • 在顶点着色器中确定点在屏幕上的位置,一般来说就是顶点坐标和矩阵相乘
  • 在片段著着色器中确定点的颜色,包括光照之类的

openGL ES使用起来说起来容易,但是实际使用起来也比较麻烦。主要原因是openGL ES封装性太差,而我们希望的是开发者能够专注于设计逻辑,而不是在如何绘制上望而却步。

下面介绍几个封装类,可以大大降低使用难度。

本文参考借鉴了吴亚峰老师的《OpenGL ES 3.x游戏开发 上下卷》,后文不再指出。

1 Shader类

Shader类是封装好的工具类,主要的使用方式:

public Shader(String mVertexShaderFile,String mFragmentShaderFile,Resources resources)

从给定的脚本语言中构造一个Shader

参数

含义

String

vertexPath

顶点着色器的路径

String

fragPath

片段着色器的路径

Resources

resources

上下文,用于文件读取

Shader

返回值

返回构造的Shader类

使用方法:

String vertexPath = "vertexShader.shader";
String fragPath = "fragShader.shader";
Shader shader = new Shader(vertexPath, fragPath, this.context);

public void use()

指定使用该着色器,在着色器传输数据时使用。

使用方法:

Shader = new Shader(v,f,r);
shader.use();

public void setFloat(String name, float v)

参数

含义

String

name

uniform的变量名

float

v

变量值

传入一个float 类型的uniform变量

这里给不懂的同学解释一下:
Shader里面相对固定的值通过类似全局变量的方式传入,称为unifom,只要在使用时指定变量名称,在java中传入,即可在着色器中使用。

public void setInt(String name, int v)

传入一个int 类型的uniform变量

public void setMat4f(String name, float[] mat4)

传入一个矩阵类型的unifrom变量

public void setVec3f(String name, FloatBuffer buffer)

传入一个3维的向量

public void setPointer3f(String name, boolean normalized, FloatBuffer buffer)

指定3维的float类型缓存, 主要用于顶点或者法向量传入

public void setPointer2f(String name, boolean normalized, FloatBuffer buffer)

指定2维的float类型缓存,主要用于纹理坐标传入

给定一个操作的方法:

Shader shader = new Shader(v,f,r);
shader.use();
shader.setMat4f("uMVPMatrix", mMVPMatrix);
//将位置、旋转变换矩阵传入着色器程序
shader.setMat4f("uMMatrix", currMatrix);
// 将顶点位置数据传入渲染管线
shader.setPointer3f("aPosition",false, mVertexBuffer);
//将顶点法向量数据传入渲染管线
shader.setPointer3f("aNormal",false, mNormalBuffer);
//将顶点纹理坐标数据传入渲染管线
shader.setPointer2f("aTexCoor", false, mTexCoorBuffer);

2 Object3D类

Object3D主要记录了物体的位置,包括平移旋转等。

public Object3D()

建立一个Object3D类型的物体,默认在(0,0,0)位置

public Object3D(float x, float y, float z)

建立一个Object3D类型的物体, 指定世界坐标系下的坐标

public void scale(float x,float y,float z)

在x,y,z轴按照指定的倍数进行放缩

public void translate(float x,float y,float z)

沿着世界坐标系的x,y,z轴运动。

public void rotate(float angle,float x,float y,float z)

绕着轴(x,y,z)旋转角度angle.

public void moveForward(float x)

沿着物体的z方向移动

public void moveLeft(float x)

沿着物体的x方向(左侧)移动

public void rotateRight(float angle)

沿着物体的x轴方向旋转,相当于改变俯角和仰角

public void rotateCameraUp(float angle)

沿着物体的y轴方向(垂直向上)旋转,相当于向左右旋转

3 Model类

model类继承Object3D,在此基础上提供了加载模型(obj)和绘制纹理的方法

public Model(String fname, int drawableId, Resources r)

变量

含义

String

fname

文件路径

int

drawable

纹理图案id

Resources

r

上下文,用来读取文件

public Model(float[] vertices, float[] normals, float texCoors[], int drawableId, Resources resources)

变量

含义

float[]

vertices

顶点数组

float[]

normals

法向量数组

float[]

texCoors

纹理坐标数组

int

drawable

纹理图案id

Resources

r

上下文,用来读取文件

public void draw(Shader shader, Camera camera)

变量

含义

Shader

shader

使用的着色器

Camera

camera

使用的相机

4 Camera 类

继承了Object3D,作为漫游的主体

public Camera(float left, float right, float bottom, float top, float near, float far, float x, float y, float z)

变量

含义

float

left

near面的left

float

right

near面的right

float

bottom

near面的bottom

float

top

near面的top

flaot

near

near面的距离

float

far

far面的距离

float

x

初始位置x坐标

float

y

初始位置y坐标

float

z

初始位置z坐标

public void translate(float x,float y,float z)

沿着世界坐标系的x,y,z轴运动。

public void rotate(float angle,float x,float y,float z)

绕着轴(x,y,z)旋转角度angle.

public void moveForward(float x)

沿着物体的z方向移动

public void moveLeft(float x)

沿着物体的x方向(左侧)移动

public void rotateRight(float angle)

沿着物体的x轴方向旋转,相当于改变俯角和仰角

public void rotateCameraUp(float angle)

沿着物体的y轴方向(垂直向上)旋转,相当于向左右旋转

5 Light类

继承了Camera类,略。

6 源码

完整的工程见:

https://github.com/HGGshiwo/OpenGLES.example

Shader类源码:

package com.example.myapplication.Shader;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.FloatBuffer;

import android.content.res.Resources;
import android.opengl.GLES30;
import android.util.Log;

//加载顶点Shader与片元Shader的工具类
public class Shader
{
    private final int id;

    public Shader(String vertexSource, String fragmentSource) {
        id =  createProgram(vertexSource, fragmentSource);
   }

   public Shader(
           String mVertexShaderFile,//顶点着色器代码脚本
           String mFragmentShaderFile,//片元着色器代码脚本
           Resources resources
   ) {
        String mVertexShader = loadFromAssetsFile(mVertexShaderFile, resources);
        String mFragmentShader = loadFromAssetsFile(mFragmentShaderFile, resources);
        id = createProgram(mVertexShader, mFragmentShader);
   }

    //加载制定shader的方法
    public static int loadShader(
            int shaderType, //shader的类型  GLES30.GL_VERTEX_SHADER   GLES30.GL_FRAGMENT_SHADER
            String source   //shader的脚本字符串
    ) {
        //创建一个新shader
        int shader = GLES30.glCreateShader(shaderType);
        //若创建成功则加载shader
        if (shader != 0)
        {
            //加载shader的源代码
            GLES30.glShaderSource(shader, source);
            //编译shader
            GLES30.glCompileShader(shader);
            //存放编译成功shader数量的数组
            int[] compiled = new int[1];
            //获取Shader的编译情况
            GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0)
            {//若编译失败则显示错误日志并删除此shader
                Log.e("ES30_ERROR", "Could not compile shader " + shaderType + ":");
                Log.e("ES30_ERROR", GLES30.glGetShaderInfoLog(shader));
                GLES30.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

   //创建shader程序的方法
   public int createProgram(String vertexSource, String fragmentSource) {
	    //加载顶点着色器
        int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) 
        {
            return 0;
        }
        
        //加载片元着色器
        int pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) 
        {
            return 0;
        }

        //创建程序
        int program = GLES30.glCreateProgram();
        //若程序创建成功则向程序中加入顶点着色器与片元着色器
        if (program != 0) 
        {
        	//向程序中加入顶点着色器
            GLES30.glAttachShader(program, vertexShader);
            checkGlError("glAttachShader");
            //向程序中加入片元着色器
            GLES30.glAttachShader(program, pixelShader);
            checkGlError("glAttachShader");
            //链接程序
            GLES30.glLinkProgram(program);
            //存放链接成功program数量的数组
            int[] linkStatus = new int[1];
            //获取program的链接情况
            GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0);
            //若链接失败则报错并删除程序
            if (linkStatus[0] != GLES30.GL_TRUE) 
            {
                Log.e("ES30_ERROR", "Could not link program: ");
                Log.e("ES30_ERROR", GLES30.glGetProgramInfoLog(program));
                GLES30.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }
    
   //检查每一步操作是否有错误的方法 
   public static void checkGlError(String op) {
        int error;
        while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR) 
        {
            Log.e("ES30_ERROR", op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }
   }
   
   //从sh脚本中加载shader内容的方法
   public static String loadFromAssetsFile(String fname,Resources r) {
   	String result=null;    	
   	try
   	{
   		InputStream in=r.getAssets().open(fname);
			int ch=0;
		    ByteArrayOutputStream baos = new ByteArrayOutputStream();
		    while((ch=in.read())!=-1)
		    {
		      	baos.write(ch);
		    }      
		    byte[] buff=baos.toByteArray();
		    baos.close();
		    in.close();
   		result=new String(buff,"UTF-8"); 
   		result=result.replaceAll("\\r\\n","\n");
   	}
   	catch(Exception e)
   	{
   		e.printStackTrace();
   	}    	
   	return result;
   }

   public void use(){
       GLES30.glUseProgram(id);
   }

   public void setFloat(String name, float v){
       int location = GLES30.glGetUniformLocation(id, name);
       GLES30.glUniform1f(location, v);
   }

   public void setInt(String name, int v){
       int location = GLES30.glGetUniformLocation(id, name);
       GLES30.glUniform1i(location, v);
   }

   public void setMat4f(String name, float[] mat4){
       int location = GLES30.glGetUniformLocation(id, name);
       GLES30.glUniformMatrix4fv(location, 1, false, mat4, 0);
   }

   public void setVec3f(String name, FloatBuffer buffer){
       int location = GLES30.glGetUniformLocation(id, name);
       GLES30.glUniform3fv(location, 1, buffer);
   }

   public void setPointer3f(String name, boolean normalized, FloatBuffer buffer){
       int location = GLES30.glGetAttribLocation(id, name);
       GLES30.glVertexAttribPointer
               (
                       location,
                       3,
                       GLES30.GL_FLOAT,
                       normalized,
                       3*4,
                       buffer
               );
       GLES30.glEnableVertexAttribArray(location);
   }

   public void setPointer2f(String name, boolean normalized, FloatBuffer buffer){
       int location = GLES30.glGetAttribLocation(id, name);
       GLES30.glVertexAttribPointer
               (
                       location,
                       2,
                       GLES30.GL_FLOAT,
                       normalized,
                       2*4,
                       buffer
               );
       GLES30.glEnableVertexAttribArray(location);
   }
}

Object3D类源码:

package com.example.myapplication.Object;

import android.opengl.Matrix;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class Object3D {

    public float[] currMatrix;//当前变换矩阵
    public float[] up; //相机lookAt使用
    public float[] front; //相机lookAt使用
    public float[] position; //相机lookAt使用

    public Object3D(){
        up = new float[]{0f,1f,0f};
        front = new float[]{0f,0f,-1f};
        position = new float[]{0,0,0};
        currMatrix = new float[16];
        Matrix.setRotateM(currMatrix,0,0,0,1,0);//注意这里是set
    }

    public Object3D(
            float x,
            float y,
            float z

    ){
        up = new float[]{0f,1f,0f};
        front = new float[]{0f,0f,-1f};
        position = new float[]{x,y,z};

        currMatrix = new float[16];
        Matrix.translateM(currMatrix, 0, x, y, z);//不能调用translate,因为子类重载
    }

    public void translate(float x,float y,float z){//设置沿xyz轴移动
        Matrix.translateM(currMatrix, 0, x, y, z);
        position[0]+=x;
        position[1]+=y;
        position[2]+=z;
    }

    public void moveForward(float x){
        float [] front = this.front;
        this.translate(-1*x*front[0],0,-1*x*front[2]);
    }

    public void moveLeft(float x){
        float [] rightDistance = Model.vectorNormal(
                Model.getCrossProduct(
                        up[0],
                        up[1],
                        up[2],
                        front[0],
                        front[1],
                        front[2]
                ));

        translate(
                x*rightDistance[0],
                0,
                x*rightDistance[2]
        );
    }

    public void rotateRight(float angle){
        float [] right = Model.vectorNormal(
                Model.getCrossProduct(
                        up[0],
                        up[1],
                        up[2],
                        front[0],
                        front[1],
                        front[2]
                ));
        rotate(angle, right[0], right[1], right[2]);
    }

    public void rotateUp(float angle){
        float radians = (float) Math.toRadians(angle);
        rotate(angle, 0, 1, 0);
    }


    public void scale(float x,float y,float z){
        Matrix.scaleM(currMatrix,0,x,y,z);
    }

    public void rotate(float angle,float x,float y,float z){//设置绕xyz轴移动
        Matrix.rotateM(currMatrix,0,angle,x,y,z);
        float[] rotateMatrix = new float[16];
        float[] rotateFront =  new float[]{front[0],front[1],front[2],1f};
        float[] rotateUp = new float[]{up[0],up[1],up[2],1.0f};
        Matrix.setRotateM(rotateMatrix,0,angle,x,y,z);
        Matrix.multiplyMV(
                rotateFront,
                0,
                rotateMatrix,
                0,
                rotateFront,
                0
        );
        float[] rotateFrontNormalized = Model.vectorNormal(rotateFront);
        front[0]=rotateFrontNormalized[0];
        front[1]=rotateFrontNormalized[1];
        front[2]=rotateFrontNormalized[2];

        Matrix.multiplyMV(
                rotateUp,
                0,
                rotateMatrix,
                0,
                rotateUp,
                0
        );
        float[] rotateUpNormalized = Model.vectorNormal(rotateUp);
        up[0]=rotateUpNormalized[0];
        up[1]=rotateUpNormalized[1];
        up[2]=rotateUpNormalized[2];
    }
}

Model类源码

package com.example.myapplication.Object;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES30;
import android.opengl.GLUtils;
import android.opengl.Matrix;

import com.example.myapplication.Camera.Camera;
import com.example.myapplication.Shader.Shader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class Model extends Object3D{
    int vCount=0;

    public FloatBuffer mVertexBuffer;//顶点坐标数据缓冲
    public FloatBuffer mNormalBuffer;//顶点法向量数据缓冲
    public FloatBuffer mTexCoorBuffer;//顶点纹理坐标数据缓冲

    int texId;//纹理

    public float shininess;//光滑度

    public Model(
            float[] vertices,
            float[] normals,
            float texCoors[],
            int drawableId,
            Resources resources
    ){
        super();
        shininess = 50;
        initTexture(drawableId, resources);
        initVertexData(vertices, normals, texCoors);
    }


    public Model(String fname, int drawableId, Resources r){
        super();
        //原始顶点坐标列表--直接从obj文件中加载
        ArrayList<Float> alv=new ArrayList<Float>();
        //顶点组装面索引列表--根据面的信息从文件中加载
        ArrayList<Integer> alFaceIndex=new ArrayList<Integer>();
        //结果顶点坐标列表--按面组织好
        ArrayList<Float> alvResult=new ArrayList<Float>();
        //平均前各个索引对应的点的法向量集合Map
        //此HashMap的key为点的索引, value为点所在的各个面的法向量的集合
        HashMap<Integer, HashSet<Normal>> hmn=new HashMap<Integer,HashSet<Normal>>();
        //原始纹理坐标列表
        ArrayList<Float> alt=new ArrayList<Float>();
        //结果纹理坐标列表
        ArrayList<Float> altResult=new ArrayList<Float>();

        try {
            InputStream in = r.getAssets().open(fname);
            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")) {//顶点坐标行
                    //若为顶点坐标行则提取出此顶点的XYZ坐标添加到原始顶点坐标列表中
                    alv.add(Float.parseFloat(tempsa[1]));
                    alv.add(Float.parseFloat(tempsa[2]));
                    alv.add(Float.parseFloat(tempsa[3]));
                } else if (tempsa[0].trim().equals("vt")) {//纹理坐标行
                    //若为纹理坐标行则提取ST坐标并添加进原始纹理坐标列表中
                    alt.add(Float.parseFloat(tempsa[1]));//提取出S纹理坐标
                    alt.add(1 - Float.parseFloat(tempsa[2]));    //提取出T纹理坐标
                } else if (tempsa[0].trim().equals("f")) {//面数据行
                    /*
                     *若为三角形面行则根据 组成面的顶点的索引从原始顶点坐标列表中
                     *提取相应的顶点坐标值添加到结果顶点坐标列表中,同时根据三个
                     *顶点的坐标计算出此面的法向量并添加到平均前各个索引对应的点
                     *的法向量集合组成的Map中
                     */

                    int[] index = new int[3];//三个顶点索引值的数组

                    //计算第0个顶点的索引,并获取此顶点的XYZ三个坐标
                    index[0] = Integer.parseInt(tempsa[1].split("/")[0]) - 1;
                    float x0 = alv.get(3 * index[0]);
                    float y0 = alv.get(3 * index[0] + 1);
                    float z0 = alv.get(3 * index[0] + 2);
                    alvResult.add(x0);
                    alvResult.add(y0);
                    alvResult.add(z0);

                    //计算第1个顶点的索引,并获取此顶点的XYZ三个坐标
                    index[1] = Integer.parseInt(tempsa[2].split("/")[0]) - 1;
                    float x1 = alv.get(3 * index[1]);
                    float y1 = alv.get(3 * index[1] + 1);
                    float z1 = alv.get(3 * index[1] + 2);
                    alvResult.add(x1);
                    alvResult.add(y1);
                    alvResult.add(z1);

                    //计算第2个顶点的索引,并获取此顶点的XYZ三个坐标
                    index[2] = Integer.parseInt(tempsa[3].split("/")[0]) - 1;
                    float x2 = alv.get(3 * index[2]);
                    float y2 = alv.get(3 * index[2] + 1);
                    float z2 = alv.get(3 * index[2] + 2);
                    alvResult.add(x2);
                    alvResult.add(y2);
                    alvResult.add(z2);

                    //记录此面的顶点索引
                    alFaceIndex.add(index[0]);
                    alFaceIndex.add(index[1]);
                    alFaceIndex.add(index[2]);

                    //通过三角形面两个边向量0-1,0-2求叉积得到此面的法向量
                    //求0号点到1号点的向量
                    float vxa = x1 - x0;
                    float vya = y1 - y0;
                    float vza = z1 - z0;
                    //求0号点到2号点的向量
                    float vxb = x2 - x0;
                    float vyb = y2 - y0;
                    float vzb = z2 - z0;
                    //通过求两个向量的叉积计算法向量
                    float[] vNormal = vectorNormal(getCrossProduct
                            (
                                    vxa, vya, vza, vxb, vyb, vzb
                            ));
                    for (int tempInxex : index) {//记录每个索引点的法向量到平均前各个索引对应的点的法向量集合组成的Map中
                        //获取当前索引对应点的法向量集合
                        HashSet<Normal> hsn = hmn.get(tempInxex);
                        if (hsn == null) {//若集合不存在则创建
                            hsn = new HashSet<Normal>();
                        }
                        //将此点的法向量添加到集合中
                        //由于Normal类重写了equals方法,因此同样的法向量不会重复出现在此点
                        //对应的法向量集合中
                        hsn.add(new Normal(vNormal[0], vNormal[1], vNormal[2]));
                        //将集合放进HsahMap中
                        hmn.put(tempInxex, hsn);
                    }

                    //将三角形3个顶点的纹理坐标数据组织到结果纹理坐标列表中
                    int indexTex = Integer.parseInt(tempsa[1].split("/")[1]) - 1;//获取纹理坐标编号
                    //第0个顶点的纹理坐标
                    altResult.add(alt.get(indexTex * 2));
                    altResult.add(alt.get(indexTex * 2 + 1));

                    indexTex = Integer.parseInt(tempsa[2].split("/")[1]) - 1;//获取纹理坐标编号
                    //第1个顶点的纹理坐标
                    altResult.add(alt.get(indexTex * 2));
                    altResult.add(alt.get(indexTex * 2 + 1));

                    indexTex = Integer.parseInt(tempsa[3].split("/")[1]) - 1;//获取纹理坐标编号
                    //第2个顶点的纹理坐标
                    altResult.add(alt.get(indexTex * 2));
                    altResult.add(alt.get(indexTex * 2 + 1));
                }
            }

            //生成顶点数组
            int size = alvResult.size();
            float[] vXYZ = new float[size];
            for (int i = 0; i < size; i++) {
                vXYZ[i] = alvResult.get(i);
            }

            //生成法向量数组
            float[] nXYZ = new float[alFaceIndex.size() * 3];
            int c = 0;
            for (Integer i : alFaceIndex) {
                //根据当前点的索引从Map中取出一个法向量的集合
                HashSet<Normal> hsn = hmn.get(i);
                //求出平均法向量
                float[] tn = Normal.getAverage(hsn);
                //将计算出的平均法向量存放到法向量数组中
                nXYZ[c++] = tn[0];
                nXYZ[c++] = tn[1];
                nXYZ[c++] = tn[2];
            }

            //生成纹理数组
            size = altResult.size();
            float[] tST = new float[size];//用于存放结果纹理坐标数据的数组
            for (int i = 0; i < size; i++) {//将纹理坐标数据存入数组
                tST[i] = altResult.get(i);
            }
            initVertexData(vXYZ,nXYZ,tST);
            initTexture(drawableId, r);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void initVertexData(float[] vertices,float[] normals,float texCoors[]) {
        //顶点坐标数据的初始化
        vCount=vertices.length/3;
        //创建顶点坐标数据缓冲
        //vertices.length*4是因为一个整数四个字节
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());//设置字节顺序
        mVertexBuffer = vbb.asFloatBuffer();//转换为Float型缓冲
        mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据
        mVertexBuffer.position(0);//设置缓冲区起始位置

        //顶点法向量数据的初始化
        ByteBuffer cbb = ByteBuffer.allocateDirect(normals.length*4);
        cbb.order(ByteOrder.nativeOrder());//设置字节顺序
        mNormalBuffer = cbb.asFloatBuffer();//转换为Float型缓冲
        mNormalBuffer.put(normals);//向缓冲区中放入顶点法向量数据
        mNormalBuffer.position(0);//设置缓冲区起始位置

        //顶点纹理坐标数据的初始化
        ByteBuffer tbb = ByteBuffer.allocateDirect(texCoors.length*4);
        tbb.order(ByteOrder.nativeOrder());//设置字节顺序
        mTexCoorBuffer = tbb.asFloatBuffer();//转换为Float型缓冲
        mTexCoorBuffer.put(texCoors);//向缓冲区中放入顶点纹理坐标数据
        mTexCoorBuffer.position(0);//设置缓冲区起始位置
    }

    public void initTexture(int drawableId, Resources resources){
        //生成纹理ID
        int[] textures = new int[1];
        GLES30.glGenTextures
                (
                        1,          //产生的纹理id的数量
                        textures,   //纹理id的数组
                        0           //偏移量
                );
        int textureId=textures[0];
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER,GLES30.GL_NEAREST);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,GLES30.GL_TEXTURE_MAG_FILTER,GLES30.GL_LINEAR);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S,GLES30.GL_REPEAT);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T,GLES30.GL_REPEAT);

        //通过输入流加载图片===============begin===================
        InputStream is = resources.openRawResource(drawableId);
        Bitmap bitmapTmp;
        try
        {
            bitmapTmp = BitmapFactory.decodeStream(is);
        }
        finally
        {
            try
            {
                is.close();
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }
        //通过输入流加载图片===============end=====================
        GLUtils.texImage2D
                (
                        GLES30.GL_TEXTURE_2D, //纹理类型
                        0,
                        GLUtils.getInternalFormat(bitmapTmp),
                        bitmapTmp, //纹理图像
                        GLUtils.getType(bitmapTmp),
                        0 //纹理边框尺寸
                );
        bitmapTmp.recycle(); 		  //纹理加载成功后释放图片
        texId = textureId;
    }

    public void draw(Shader shader, Camera camera) {
        //将最终变换矩阵传入着色器程序
        float[] mMVPMatrix=new float[16];
        Matrix.multiplyMM(mMVPMatrix, 0, camera.mVMatrix, 0, currMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, camera.mProjMatrix, 0, mMVPMatrix, 0);
        shader.setMat4f("uMVPMatrix", mMVPMatrix);

        //将位置、旋转变换矩阵传入着色器程序
        shader.setMat4f("uMMatrix", currMatrix);
        // 将顶点位置数据传入渲染管线
        shader.setPointer3f("aPosition",false, mVertexBuffer);
        //将顶点法向量数据传入渲染管线
        shader.setPointer3f("aNormal",false, mNormalBuffer);
        //将顶点纹理坐标数据传入渲染管线
        shader.setPointer2f("aTexCoor", false, mTexCoorBuffer);
        //将粗糙度传入着色器程序
        shader.setFloat("uShininess", shininess);
        //绑定纹理
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);//启用0号纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);//绑定纹理
        //绘制加载的物体
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount);
    }

    //求两个向量的叉积
    public static float[] getCrossProduct(float x1,float y1,float z1,float x2,float y2,float z2)
    {
        //求出两个矢量叉积矢量在XYZ轴的分量ABC
        float A=y1*z2-y2*z1;
        float B=z1*x2-z2*x1;
        float C=x1*y2-x2*y1;

        return new float[]{A,B,C};
    }

    //向量规格化
    public static float[] vectorNormal(float[] vector)
    {
        //求向量的模
        float module=(float)Math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]+vector[2]*vector[2]);
        return new float[]{vector[0]/module,vector[1]/module,vector[2]/module};
    }
}

Camera类源码

package com.example.myapplication.Camera;

import android.opengl.GLES30;
import android.opengl.Matrix;

import com.example.myapplication.Object.Object3D;
import com.example.myapplication.Shader.Shader;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class Camera extends Object3D {

    public float[] mVMatrix;//摄像机位置朝向9参数矩阵
    public float[] mProjMatrix;//4x4矩阵 投影用
    public FloatBuffer positionBuffer;

    public Camera(
            float left,		//near面的left
            float right,    //near面的right
            float bottom,   //near面的bottom
            float top,      //near面的top
            float near,		//near面距离
            float far,       //far面距离
            float x,
            float y,
            float z
    ){
        super(x,y,z);
        mVMatrix  = new float[16];
        mProjMatrix = new float[16];
        setProjectFrustum(left,right,bottom,top,near,far);
        setLookAt();
        setPositionBuffer();
    }

    public void setLookAt() {
        Matrix.setLookAtM
                (
                        mVMatrix,
                        0,
                        position[0],
                        position[1],
                        position[2],
                        position[0]+front[0],
                        position[1]+front[1],
                        position[2]+front[2],
                        up[0],
                        up[1],
                        up[2]
                );
    }

    protected void setPositionBuffer(){
        ByteBuffer llbb = ByteBuffer.allocateDirect(3*4);
        llbb.order(ByteOrder.nativeOrder());//设置字节顺序
        positionBuffer=llbb.asFloatBuffer();
        positionBuffer.put(position);
        positionBuffer.position(0);
    }

    public void setProjectFrustum
            (
                    float left,		//near面的left
                    float right,    //near面的right
                    float bottom,   //near面的bottom
                    float top,      //near面的top
                    float near,		//near面距离
                    float far       //far面距离
            ) {
        Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    //设置正交投影参数
    public void setProjectOrtho
    (
            float left,		//near面的left
            float right,    //near面的right
            float bottom,   //near面的bottom
            float top,      //near面的top
            float near,		//near面距离
            float far       //far面距离
    ) {
        Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    @Override
    public void rotate(float angle,float x,float y,float z){
        super.rotate(angle,x,y,z);
        setLookAt();
    }

    @Override
    public void translate(float x,float y,float z){
        super.translate(x,y,z);
        setPositionBuffer();
        setLookAt();
    }

    @Override
    public void moveForward(float x){
        super.moveForward(x);
        setPositionBuffer();
        setLookAt();
    }

    @Override
    public void moveLeft(float x){
        super.moveLeft(x);
        setPositionBuffer();
        setLookAt();
    }

    @Override
    public void rotateRight(float angle){
        super.rotateRight(angle);
        setPositionBuffer();
        setLookAt();
    }

    @Override
    public void rotateUp(float angle){
        super.rotateUp(angle);
        setPositionBuffer();
        setLookAt();
    }
}