这个例子的实现是,把2D纹理贴到一个矩形上,然后伪造出3D效果。
里面的树,来源是一张png图。贴在矩形上后,这个矩形要保持一直面对着摄像头。
这个例子来源是《Android3D游戏开发技术宝典》中的第11章的第一节,但是我修改了下包结构。本意能通过这个例子进行更多的拓展。
类图
Activity和MySurfaceView的关系这里不再说明。如果需要请看上一篇。
这里的代码关系大致是这样:
“MySurfaceView->TreeGroup->Image_2D->TextureRect”
“MySurfaceView->Desert”
TreeGroup是个“树组”,里面定义了不同坐标2D图像作为纹理的“树”。
Image_2D是用x坐标和z坐标形容位置的2D图像类。它主要关心图像的位置和纹理。而图像的长宽是由通过构造函数传入的TextureRect类对象决定。所以TextureRect主要关心图像的长宽。
Desert是“地面”。这个类的说明,请看上一篇关于Triangle的说明。
public class MySurfaceView extends GLSurfaceView
{
public static final float UNIT_SIZE=1f;
static float direction=0;//视线方向
public static float cx=0;//摄像机x坐标
public static float cz=15;//摄像机z坐标
static final float DEGREE_SPAN=(float)(3.0/180.0f*Math.PI);//摄像机每次转动的角度
//线程循环的标志位
boolean flag=true;
float x;
float y;
float Offset=15;
SceneRenderer mRender;
float preX;
float preY;
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event)
{
x = event.getX();
y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
flag = true;
new Thread() {
@Override
public void run() {
while (flag) {
if (x > 0 && x < WIDTH / 2 && y > 0 && y < HEIGHT / 2) {// 向前
Offset = Offset - 0.5f;
} else if (x > WIDTH / 2 && x < WIDTH && y > 0 && y < HEIGHT / 2) {// 向后
Offset = Offset + 0.5f;
} else if (x > 0 && x < WIDTH / 2 && y > HEIGHT / 2 && y < HEIGHT) {
direction = direction + DEGREE_SPAN;
} else if (x > WIDTH / 2 && x < WIDTH && y > HEIGHT / 2 && y < HEIGHT) {
direction = direction - DEGREE_SPAN;
}
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
break;
case MotionEvent.ACTION_UP:
flag = false;
break;
}
//设置新的观察目标点XZ坐标
cx=(float)(Math.sin(direction)*Offset);//观察目标点x坐标
cz=(float)(Math.cos(direction)*Offset);//观察目标点z坐标
//计算所有树的朝向
mRender.group.calculateBillboardDirection();
//给树按照离视点的距离排序
Collections.sort(mRender.group.trees);
//设置新的摄像机位置
MatrixState.setCamera(cx,0,cz,0,0,0,0,1,0);
return true;
}
public MySurfaceView(Context context)
{
super(context);
this.setEGLContextClientVersion(2); //设置使用OPENGL ES2.0
mRender = new SceneRenderer(); //创建场景渲染器
setRenderer(mRender); //设置渲染器
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染
}
private class SceneRenderer implements GLSurfaceView.Renderer
{
int treeTextureId;
TreeGroup group;
Desert desert;
int desertId;
@Override
public void onDrawFrame(GL10 gl)
{
//清除深度缓冲与颜色缓冲
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
MatrixState.pushMatrix();
MatrixState.translate(0, -2, 0);
desert.drawSelf(desertId);
MatrixState.popMatrix();
//开启混合
GLES20.glEnable(GLES20.GL_BLEND);
//设置混合因子
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
MatrixState.pushMatrix();
MatrixState.translate(0, -2, 0);
group.drawSelf();
MatrixState.popMatrix();
//关闭混合
GLES20.glDisable(GLES20.GL_BLEND);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
//设置视窗大小及位置
GLES20.glViewport(0, 0, width, height);
//计算GLSurfaceView的宽高比
float ratio = (float) width / height;
//调用此方法计算产生透视投影矩阵
MatrixState.setProjectFrustum(-ratio, ratio, -1, 1, 1, 100);
//调用此方法产生摄像机9参数位置矩阵
MatrixState.setCamera(cx,0,cz,0,0,0,0f,1.0f,0.0f);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
//设置屏幕背景色RGBA
GLES20.glClearColor(1.0f,1.0f,1.0f,1.0f);
//打开深度检测
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
MatrixState.setInitStack();
treeTextureId=initTexture(R.drawable.tree);
desert=new Desert
(
MySurfaceView.this,
new float[]
{
0,0, 0,6, 6,6,
6,6, 6,0, 0,0
} ,
30,
20
);
desertId=initTexture(R.drawable.desert);
group=new TreeGroup(MySurfaceView.this,treeTextureId);
Collections.sort(mRender.group.trees);
}
}
//生成纹理的id
public int initTexture(int drawableId)
{
//生成纹理ID
int[] textures = new int[1];
GLES20.glGenTextures
(
1, //产生的纹理id的数量
textures, //纹理id的数组
0 //偏移量
);
int textureId=textures[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
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);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_REPEAT);
//通过输入流加载图片
InputStream is = this.getResources().openRawResource(drawableId);
Bitmap bitmapTmp;
try
{
bitmapTmp = BitmapFactory.decodeStream(is);
}
finally
{
try
{
is.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
//实际加载纹理
GLUtils.texImage2D
(
GLES20.GL_TEXTURE_2D, //纹理类型,在OpenGL ES中必须为GL10.GL_TEXTURE_2D
0, //纹理的层次,0表示基本图像层,可以理解为直接贴图
bitmapTmp, //纹理图像
0 //纹理边框尺寸
);
bitmapTmp.recycle(); //纹理加载成功后释放图片
return textureId;
}
}
请注意代码中的onDrawFrame方法,只有调用group.drawSelf()
方法时,前后才有开启混合的代码。而在绘制desert即地面时,并不开启混合的代码。
public class TreeGroup
{
TextureRect rect;
public List<Image_2D> trees=new ArrayList<Image_2D>();
public TreeGroup(MySurfaceView mv,int texId)
{
rect=new TextureRect(mv, 3, 5, 0);
trees.add(new Image_2D(0,0,0,rect,texId));
trees.add(new Image_2D(8*UNIT_SIZE,0,0,rect,texId));
trees.add(new Image_2D(5.7f*UNIT_SIZE,5.7f*UNIT_SIZE,0,rect,texId));
trees.add(new Image_2D(0,-8*UNIT_SIZE,0,rect,texId));
trees.add(new Image_2D(-5.7f*UNIT_SIZE,5.7f*UNIT_SIZE,0,rect,texId));
trees.add(new Image_2D(-8*UNIT_SIZE,0,0,rect,texId));
trees.add(new Image_2D(-5.7f*UNIT_SIZE,-5.7f*UNIT_SIZE,0,rect,texId));
trees.add(new Image_2D(0,8*UNIT_SIZE,0,rect,texId));
trees.add(new Image_2D(5.7f*UNIT_SIZE,-5.7f*UNIT_SIZE,0,rect,texId));
}
public void calculateBillboardDirection()
{
//计算列表中每个树木的朝向
for(int i=0;i<trees.size();i++)
{
trees.get(i).calculateBillboardDirection();
}
}
public void drawSelf()
{//绘制列表中的每个树木
for(int i=0;i<trees.size();i++)
{
trees.get(i).drawSelf();
}
}
}
注意里面所有树都是用同一个TextureRect对象的,因为他们的宽高都相同。这个类主要是为了绘制树木时比较方便。
//单个的图片类
public class Image_2D implements Comparable<Image_2D>
{
public float x;
public float z;
public float yAngle;
TextureRect rect;
public int texId;
public Image_2D(float x, float z, float yAngle, TextureRect rect,int texId) {
this.x = x;
this.z = z;
this.yAngle = yAngle;
this.rect = rect;
this.texId=texId;
}
public void drawSelf() {
MatrixState.pushMatrix();
MatrixState.translate(x, 0, z);
MatrixState.rotate(yAngle, 0, 1, 0);
rect.drawSelf(texId);
MatrixState.popMatrix();
}
// 把图片朝向摄像机位置
public void calculateBillboardDirection() {
float xspan = x - cx;
float zspan = z - cz;
if (zspan <= 0) {
yAngle = (float) Math.toDegrees(Math.atan(xspan / zspan));
} else {
yAngle = 180 + (float) Math.toDegrees(Math.atan(xspan / zspan));
}
}
@Override
public int compareTo(Image_2D another) {
// 重写的比较两个树木离摄像机距离的方法
float xs = x - cx;
float zs = z - cz;
float xo = another.x - cx;
float zo = another.z - cz;
float disA = (float) Math.sqrt(xs * xs + zs * zs);
float disB = (float) Math.sqrt(xo * xo + zo * zo);
return ((disA - disB) == 0) ? 0 : ((disA - disB) > 0) ? -1 : 1;
}
}
如上所说,这个类关心的是图像的x轴和z轴的位置和图像的纹理。这里的yAngle是图像在y轴的旋转角度。calculateBillboardDirection()
方法保证它一直面对着摄像机。
public class TextureRect
{
int mProgram;//自定义渲染管线程序id
int muMVPMatrixHandle;//总变换矩阵引用id
int maPositionHandle; //顶点位置属性引用id
int maTexCoorHandle; //顶点纹理坐标属性引用id
FloatBuffer mVertexBuffer;//顶点坐标数据缓冲
FloatBuffer mTexCoorBuffer;//顶点纹理坐标数据缓冲
int vCount=0;
int width=1;
int height=1;
float bottom=0;
public TextureRect(GLSurfaceView mv,int width,int height,float bottom)
{
this.width=width;
this.height=height*2;
this.bottom=bottom;
initVertexData();
initShader(mv);
}
//初始化顶点数据的方法
public void initVertexData()
{
vCount=6;
float vertices[]=new float[]
{
-UNIT_SIZE*width,bottom,0,
UNIT_SIZE*width,bottom,0,
UNIT_SIZE*width,UNIT_SIZE*height+bottom,0,
UNIT_SIZE*width,UNIT_SIZE*height+bottom,0,
-UNIT_SIZE*width,UNIT_SIZE*height+bottom,0,
-UNIT_SIZE*width,bottom,0,
};
//创建顶点坐标数据缓冲
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());//设置字节顺序
mVertexBuffer = vbb.asFloatBuffer();//转换为Float型缓冲
mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据
mVertexBuffer.position(0);//设置缓冲区起始位置
float[] texcoor=new float[]
{
0,1, 1,1,
1,0, 1,0,
0,0, 0,1
};
ByteBuffer tbb = ByteBuffer.allocateDirect(texcoor.length*4);
tbb.order(ByteOrder.nativeOrder());//设置字节顺序
mTexCoorBuffer = tbb.asFloatBuffer();//转换为Float型缓冲
mTexCoorBuffer.put(texcoor);//向缓冲区中放入顶点坐标数据
mTexCoorBuffer.position(0);//设置缓冲区起始位置
}
//初始化shader
public void initShader(GLSurfaceView mv)
{
//加载顶点着色器的脚本内容
String mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
//加载片元着色器的脚本内容
String mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
//基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
//获取程序中顶点位置属性引用id
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
//获取程序中顶点纹理坐标属性引用id
maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");
//获取程序中总变换矩阵引用id
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
//主绘制方法
public void drawSelf(int texId)
{
//指定使用某套shader程序
GLES20.glUseProgram(mProgram);
//将最终变换矩阵传入shader程序
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
//传送顶点位置数据
GLES20.glVertexAttribPointer
(
maPositionHandle,
3,
GLES20.GL_FLOAT,
false,
3*4,
mVertexBuffer
);
//传送顶点纹理坐标数据
GLES20.glVertexAttribPointer
(
maTexCoorHandle,
2,
GLES20.GL_FLOAT,
false,
2*4,
mTexCoorBuffer
);
//允许顶点位置、纹理坐标数据数组
GLES20.glEnableVertexAttribArray(maPositionHandle);
GLES20.glEnableVertexAttribArray(maTexCoorHandle);
//绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
//绘制纹理矩形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
}
}
这个类与着色器代码绑定,是绘制纹理矩形的代码。它关心矩形的长宽。所以这里面定义着矩形的顶点坐标。至于矩形的位置,它在Image_2D中通过矩阵变换而得到正确的位置。
传入的参数是,矩形宽,高,以及矩阵的底部在z轴上的坐标。
public class Desert
{
int mProgram;//自定义渲染管线程序id
int muMVPMatrixHandle;//总变换矩阵引用id
int maPositionHandle; //顶点位置属性引用id
int maTexCoorHandle; //顶点纹理坐标属性引用id
String mVertexShader;//顶点着色器
String mFragmentShader;//片元着色器
static float[] mMMatrix = new float[16];//具体物体的移动旋转矩阵
FloatBuffer mVertexBuffer;//顶点坐标数据缓冲
FloatBuffer mTexCoorBuffer;//顶点纹理坐标数据缓冲
int vCount=0;
public Desert(MySurfaceView mv,float[] texCoor,int width,int height)
{
//初始化顶点坐标与着色数据
initVertexData(texCoor,width,height);
//初始化shader
initShader(mv);
}
//初始化顶点坐标与着色数据的方法
public void initVertexData(float[] texCoor,int width,int height)
{
vCount=6;
float vertices[]=new float[]
{
-UNIT_SIZE*width,0,-UNIT_SIZE*height,
UNIT_SIZE*width,0,-UNIT_SIZE*height,
UNIT_SIZE*width,0,UNIT_SIZE*height,
-UNIT_SIZE*width,0,-UNIT_SIZE*height,
UNIT_SIZE*width,0,UNIT_SIZE*height,
-UNIT_SIZE*width,0,UNIT_SIZE*height
};
//创建顶点坐标数据缓冲
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(texCoor.length*4);
cbb.order(ByteOrder.nativeOrder());//设置字节顺序
mTexCoorBuffer = cbb.asFloatBuffer();//转换为Float型缓冲
mTexCoorBuffer.put(texCoor);//向缓冲区中放入顶点着色数据
mTexCoorBuffer.position(0);//设置缓冲区起始位置
}
//初始化shader
public void initShader(MySurfaceView mv)
{
//加载顶点着色器的脚本内容
mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
//加载片元着色器的脚本内容
mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
//基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
//获取程序中顶点位置属性引用id
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
//获取程序中顶点纹理坐标属性引用id
maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");
//获取程序中总变换矩阵引用id
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
public void drawSelf(int texId)
{
//指定使用某套shader程序
GLES20.glUseProgram(mProgram);
//将最终变换矩阵传入shader程序
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
//传送顶点位置数据
GLES20.glVertexAttribPointer
(
maPositionHandle,
3,
GLES20.GL_FLOAT,
false,
3*4,
mVertexBuffer
);
//传送顶点纹理坐标数据
GLES20.glVertexAttribPointer
(
maTexCoorHandle,
2,
GLES20.GL_FLOAT,
false,
2*4,
mTexCoorBuffer
);
//允许顶点位置、纹理坐标数据数组
GLES20.glEnableVertexAttribArray(maPositionHandle);
GLES20.glEnableVertexAttribArray(maTexCoorHandle);
//绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
//绘制纹理矩形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
}
}
//存储系统矩阵状态的类
public class MatrixState
{
private static float[] mProjMatrix = new float[16];//4x4矩阵 投影用
private static float[] mVMatrix = new float[16];//摄像机位置朝向9参数矩阵
private static float[] currMatrix;//当前变换矩阵
public static float[] lightLocation=new float[]{0,0,0};//定位光光源位置
public static FloatBuffer cameraFB;
public static FloatBuffer lightPositionFB;
public static Stack<float[]> mStack=new Stack<float[]>();//保护变换矩阵的栈
public static void setInitStack()//获取不变换初始矩阵
{
currMatrix=new float[16];
Matrix.setRotateM(currMatrix, 0, 0, 1, 0, 0);
}
public static void pushMatrix()//保护变换矩阵
{
mStack.push(currMatrix.clone());
}
public static void popMatrix()//恢复变换矩阵
{
currMatrix=mStack.pop();
}
public static void translate(float x,float y,float z)//设置沿xyz轴移动
{
Matrix.translateM(currMatrix, 0, x, y, z);
}
public static void rotate(float angle,float x,float y,float z)//设置绕xyz轴移动
{
Matrix.rotateM(currMatrix,0,angle,x,y,z);
}
//设置摄像机
public static void setCamera
(
float cx, //摄像机位置x
float cy, //摄像机位置y
float cz, //摄像机位置z
float tx, //摄像机目标点x
float ty, //摄像机目标点y
float tz, //摄像机目标点z
float upx, //摄像机UP向量X分量
float upy, //摄像机UP向量Y分量
float upz //摄像机UP向量Z分量
)
{
Matrix.setLookAtM
(
mVMatrix,
0,
cx,
cy,
cz,
tx,
ty,
tz,
upx,
upy,
upz
);
float[] cameraLocation=new float[3];//摄像机位置
cameraLocation[0]=cx;
cameraLocation[1]=cy;
cameraLocation[2]=cz;
ByteBuffer llbb = ByteBuffer.allocateDirect(3*4);
llbb.order(ByteOrder.nativeOrder());//设置字节顺序
cameraFB=llbb.asFloatBuffer();
cameraFB.put(cameraLocation);
cameraFB.position(0);
}
//设置透视投影参数
public static 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 static 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);
}
//获取具体物体的总变换矩阵
public static float[] getFinalMatrix()
{
float[] mMVPMatrix=new float[16];
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, currMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
return mMVPMatrix;
}
//获取具体物体的变换矩阵
public static float[] getMMatrix()
{
return currMatrix;
}
//设置灯光位置的方法
public static void setLightLocation(float x,float y,float z)
{
lightLocation[0]=x;
lightLocation[1]=y;
lightLocation[2]=z;
ByteBuffer llbb = ByteBuffer.allocateDirect(3*4);
llbb.order(ByteOrder.nativeOrder());//设置字节顺序
lightPositionFB=llbb.asFloatBuffer();
lightPositionFB.put(lightLocation);
lightPositionFB.position(0);
}
public static void scale(float x,float y,float z){
Matrix.scaleM(currMatrix, 0, x, y, z);
}
}
//加载顶点Shader与片元Shader的工具类
public class ShaderUtil {
// 加载制定shader的方法
public static int loadShader(
int shaderType, // shader的类型
String source // shader的脚本字符串
) {
// 创建一个新shader
int shader = GLES20.glCreateShader(shaderType);
// 若创建成功则加载shader
if (shader != 0) {
// 加载shader的源代码
GLES20.glShaderSource(shader, source);
// 编译shader
GLES20.glCompileShader(shader);
// 存放编译成功shader数量的数组
int[] compiled = new int[1];
// 获取Shader的编译情况
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {// 若编译失败则显示错误日志并删除此shader
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
// 创建shader程序的方法
public static int createProgram(String vertexSource, String fragmentSource) {
// 加载顶点着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
// 加载片元着色器
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
// 创建程序
int program = GLES20.glCreateProgram();
// 若程序创建成功则向程序中加入顶点着色器与片元着色器
if (program != 0) {
// 向程序中加入顶点着色器
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
// 向程序中加入片元着色器
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
// 链接程序
GLES20.glLinkProgram(program);
// 存放链接成功program数量的数组
int[] linkStatus = new int[1];
// 获取program的链接情况
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 若链接失败则报错并删除程序
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
// 检查每一步操作是否有错误的方法
public static void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e("ES20_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;
}
}
https://github.com/henryjaxliukun/OpenGLES20Demo/tree/master/GL2DDemo1