前段时间一直在看opengl es2.0游戏开发的知识 ,这几天买了本opengl es3.0游戏开发的书
源代码点击打开链接
打算一边学习一边整理学习笔记,我的开发环境是Android studio 2.1.3,不过有个问题是Android studio自带的模拟器只能支持es2.0
无法使用es3.0 所以3.0只能在真机上调试,手机必须是4.3或者是以上系统,我试过4.3的三星s3和 6.0的华为mates。下面是效果图
首先是各个类的功能
LoggerConfig这个类是控制错误日志输出的,这个其实很简单里面就一句话,在错误日志输出时判断它里面是不是true
MyActivity这个类是主活动的类,创建了视图,在这里把显示的视图设置为opengl的视窗口。
MyTDView这个类是继承自GLSurfaceView 实现opengl的窗口和 一个渲染器,我是这么理解的
shaderUtil是着色器的帮助类 实现读取着色器和编译连接
Tringle是绘制三角形
我个人感觉整体过过程是这样的
下边是LoggerConfig的代码
package com.opengl.a3_1_triangle;
/**
* Created by admin on 2016/11/12.
*/
public class LoggerConfig {
public static final boolean ON = true; //在后面输出错误日志时判断是不是true
}
下边是从shaderUtil帮助类的代码
首先得实现的是把着色器代码从文本中读取出来并且进行编译,书中作者使用的是把着色器代码写在sh脚本中然后读取,Android studio中有glsl着色器的插件,安装后可以使用glsl格式文本,glsl格式额能提示关键字有个好处是防止着色器程序写错。
代码
package com.opengl.a3_1_triangle;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.util.Log;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Created by admin on 2016/11/12.
*/
//加载顶点shader与片元shader的工具类
public class ShaderUtil {
//加载定制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 [] compile=new int[1];
//获取Shader的编译情况
GLES30.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,compile,0);
if(compile[0]==0){
//若编译失败则显示错误的日志并删除shader
Log.e("ES20_ERROR","不能编译着色器"+shaderType+":");
Log.e("ES20_ERROR",GLES30.glGetShaderInfoLog(shader));
GLES30.glDeleteShader(shader);
shader=0;
}
}
return shader;
}
//创建着色器程序的方法
public static 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);
//向程序中加入片元着色器
GLES30.glAttachShader(program,pixelShader);
checkGlError("glAttachShader");
//连接程序
GLES30.glLinkProgram(program);
//存放连接成功program数量的数组
int [] linkStatus=new int[1];
//获取连接的情况
GLES30.glGetProgramiv(program,GLES30.GL_LINK_STATUS,linkStatus,0);
//若链接失败则报错并删除程序
if (linkStatus[0] != GLES30.GL_TRUE)
{
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES30.glGetProgramInfoLog(program));
GLES30.glDeleteProgram(program);
program = 0;
}
}
return program;
}
//检查每一步操作是否有错误的方法
@SuppressLint("NewApi")
public static void checkGlError(String op)
{
int error;
while ((error = GLES30.glGetError()) != GLES30.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;
}
//从raw文件夹中读取glsl文件
public static String loadFromRawFile(Context context ,int resourceId){
StringBuilder body = new StringBuilder();
try {
InputStream inputStream =
context.getResources().openRawResource(resourceId);
InputStreamReader inputStreamReader =
new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String nextLine;
while ((nextLine = bufferedReader.readLine()) != null) {
body.append(nextLine);
body.append('\n');
}
} catch (IOException e) {
throw new RuntimeException(
"不能加载资源: " + resourceId, e);
} catch (Resources.NotFoundException nfe) {
throw new RuntimeException("资源没有发现: " + resourceId, nfe);
}
return body.toString();
}
}
Triangle类代码
package com.opengl.a3_1_triangle;
import android.annotation.SuppressLint;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Created by admin on 2016/11/13.
*/
public class Triangle {
Context context;
public static float [] mProjMatrix=new float[16];//4*4投影矩阵
public static float [] mVMatrix=new float[16];//摄像机位置朝向的参数矩阵
public static float[] mMVPMatrix;//最后起作用的总的变换矩阵
int mProgram;//自定义的渲染管线程序id
int muMVPMatrixHandle;//总的变换矩阵引用
int maPositionHandle;//顶点位置属性的应用
int maColorHandle;//顶点颜色属性的引用
String mVertexShader;//顶点着色器的代码脚本
String mFragmentShader;//片元着色器代码脚本
static float[] mMMatrix=new float[16];//具体物体的移动旋转矩阵,包括旋转,平移,缩放
FloatBuffer mVertexBuffer;//顶点坐标数据缓冲
FloatBuffer mColorBuffer;//顶点着色数据缓冲
int vCount=0;
float xAngle=0;//绕x轴旋转的角度
public Triangle(MyTDView mv, Context context){
this.context=context;
//调用初始化顶点数据的initVertexData的方法
initVertexData();
//调用初始化着色器的initShader的方法
// initShader(mv);
initShaderGLSL(mv);
}
public void initVertexData(){
//顶点坐标数据的初始化
vCount=3;
final float UNIT_SIZE=0.2f;
float vertices[]=new float[]{
-4*UNIT_SIZE,0,0,
0,-4*UNIT_SIZE,0,
4*UNIT_SIZE,0,0,
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());//设置字节顺序为本地操作系统顺序
mVertexBuffer = vbb.asFloatBuffer();//转换为浮点(Float)型缓冲
mVertexBuffer.put(vertices);//在缓冲区内写入数据
mVertexBuffer.position(0);//设置缓冲区起始位置
float colors[]=new float[]//顶点颜色数组
{
1,1,1,0,//白色
0,0,1,0,//蓝
0,1,0,0//绿
};
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
cbb.order(ByteOrder.nativeOrder());//设置字节顺序为本地操作系统顺序
mColorBuffer = cbb.asFloatBuffer();//转换为浮点(Float)型缓冲
mColorBuffer.put(colors);//在缓冲区内写入数据
mColorBuffer.position(0);//设置缓冲区起始位置
}
/*
//如果使用从sh脚本中读取着色器代码用这个代码
//初始化着色器的方法
@SuppressLint("NewApi")
public void initShader(MyTDView mv)
{
//加载顶点着色器的脚本内容
mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
//加载片元着色器的脚本内容
mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
//基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
//获取程序中顶点位置属性引用
maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
//获取程序中顶点颜色属性引用
maColorHandle= GLES30.glGetAttribLocation(mProgram, "aColor");
//获取程序中总变换矩阵引用
muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
}
*/
//从指定的glsl格式读取代码
public void initShaderGLSL(MyTDView mv)
{
// 加载顶点着色器的脚本内容
mVertexShader = ShaderUtil.loadFromRawFile(context,
R.raw.vertex);
// 加载片元着色器的脚本内容
mFragmentShader = ShaderUtil.loadFromRawFile(context,
R.raw.frag);
//基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
//获取程序中顶点位置属性引用
maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
//获取程序中顶点颜色属性引用
maColorHandle= GLES30.glGetAttribLocation(mProgram, "aColor");
//获取程序中总变换矩阵引用
muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
}
@SuppressLint("NewApi")
public void drawSelf(){
//指定使用某套shader程序
GLES30.glUseProgram(mProgram);
//指定使用某套shader程序
GLES30.glUseProgram(mProgram);
//初始化变换矩阵
Matrix.setRotateM(mMMatrix,0,0,0,1,0);
//设置沿Z轴正向位移1
Matrix.translateM(mMMatrix,0,0,0,1);
//设置绕x轴旋转
Matrix.rotateM(mMMatrix,0,xAngle,1,0,0);
//将变换矩阵传入渲染管线
GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFianlMatrix(mMMatrix), 0);
//将顶点位置数据传送进渲染管线
GLES30.glVertexAttribPointer(
maPositionHandle,
3,
GLES30.GL_FLOAT,
false,
3*4,
mVertexBuffer
);
//将顶点颜色数据传送进渲染管线
GLES30.glVertexAttribPointer
(
maColorHandle,
4,
GLES30.GL_FLOAT,
false,
4*4,
mColorBuffer
);
GLES30.glEnableVertexAttribArray(maPositionHandle);//启用顶点位置数据
GLES30.glEnableVertexAttribArray(maColorHandle);//启用顶点着色数据
//绘制三角形
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount);
}
public static float[] getFianlMatrix(float[] spec)
{
mMVPMatrix=new float[16];
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
return mMVPMatrix;
}
}
MyTDView代码
package com.opengl.a3_1_triangle;
import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Created by admin on 2016/11/13.
*/
public class MyTDView extends GLSurfaceView{
final float ANGLE_SPAN=0.375f;
RotateThread rthread;
SceneRenderer mRenderer;//自定义渲染器的引用
public MyTDView(Context context){
super(context);
this.setEGLContextClientVersion(3); //设置opengl es的版本为3
mRenderer=new SceneRenderer(context);
this.setRenderer(mRenderer);
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//持续渲染模式 还有一种模式是当有请求时才渲染
}
private class SceneRenderer implements GLSurfaceView.Renderer
{
Context context;
Triangle tle;
public SceneRenderer(Context context) {
this.context=context;
}
public void onDrawFrame(GL10 gl) //绘制
{
//清除深度缓冲与颜色缓冲
GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
//绘制三角形对象
tle.drawSelf();
}
public void onSurfaceChanged(GL10 gl, int width, int height) //当窗口更改的时候调用
{
//设置视窗大小及位置
GLES30.glViewport(0, 0, width, height);
//计算GLSurfaceView的宽高比
float ratio = (float) width / height;
//调用此方法计算产生透视投影矩阵
Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
//调用此方法产生摄像机9参数位置矩阵
Matrix.setLookAtM(Triangle.mVMatrix, 0, 0,0,3,0f,0f,0f,0f,1.0f,0.0f);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) //创建窗口
{
//设置屏幕背景色RGBA
GLES30.glClearColor(0,0,0,1.0f);
//创建三角形对对象
tle=new Triangle(MyTDView.this,context);
//打开深度检测
GLES30.glEnable(GLES30.GL_DEPTH_TEST);
rthread=new RotateThread();
rthread.start();
}
}
public class RotateThread extends Thread //自定义的内部类线程 重新发发起一个线程让绕x轴旋转
{
public boolean flag=true;
@Override
public void run()//重写run方法
{
while(flag){
mRenderer.tle.xAngle=mRenderer.tle.xAngle+ANGLE_SPAN;
try {
Thread.sleep(20);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
顶点着色器的代码
#version 300 es
uniform mat4 uMVPMatrix;//总的变换矩阵
layout (location=0) in vec3 aPosition;//顶点的位置
layout (location=1) in vec4 aColor;//顶点颜色
out vec4 vColor;//用于传递给片元着色器的变量
void main()
{
gl_Position=uMVPMatrix*vec4(aPosition,1);
vColor=aColor;
}
片元着色器代码
#version 300 es
precision mediump float;
in vec4 vColor;//接受从顶点着色器过来的参数
out vec4 fragColor;//输出到片元这颜色
void main(){
fragColor=vColor;//此片元的颜色值
}
MyActivity类代码
package com.opengl.a3_1_triangle; import android.app.Activity; import android.content.pm.ActivityInfo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MyActivity extends Activity { //创建继承Activity的主控制类 MyTDView mview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);//调用父类 //设置为竖屏模式 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mview=new MyTDView(this);//创建MyTDView类而对象 mview.requestFocus(); mview.setFocusableInTouchMode(true); setContentView(mview); } @Override public void onResume()//继承Activity后重写的onResume方法 { super.onResume(); mview.onResume();//通过MyTDView类的对象调用onResume方法 } @Override public void onPause()//继承Activity后重写的onPause方法 { super.onPause(); mview.onPause();//通过MyTDView类的对象调用onPause方法 } }