|
Android OpenGL ES2.0学习:基础三角形案例 OpenGL ES 2.0与OpenGL ES 1.x的一个最大不同就是用可编程渲染管线代替了原有的固定渲染管线。在学习OpenGL ES 2.0之前,有必要先了解一下OpenGL ES 1.x渲染管线的工作原理。
渲染管线有时也被称为渲染流水线,一般是由显示芯片内部的处理图形信号的并行处理单元组成。与普通应用程序通过CPU串行执行不同,将渲染工作通过渲染管线中多个相互独立的处理单元进行并行处理后,渲染效率可以得到极大提升。这个过程中输入的是待渲染3D物体的相关描述信息数据,经过渲染管线,输出的是一帧想要的图像。该过程的主要工作可以分为:1.基本处理,2.生成顶点缓冲对象,3.变化和光照,4.图元装配,5.光栅化,6.纹理环境和颜色求和,7.雾化,8.Alpha测试,9.剪裁测试,10.深度测试等等。
OpenGL ES 1.x 只对开发人员开放了其中的一些API接口,在整个渲染管线的运行过程中开发人员不能直接干预,所以留给编程人员发挥的空间不大,很多特效难以开发,OpenGL ES 2.0则用可编程渲染管线(包括顶点着色器和片元着色器)为编程人员提供了更多的发挥空间。2.0中使用“顶点着色器”取代了1.x渲染管线中“变换和光照”的阶段,这使得开发3D场景时对顶点的变换,法向量的计算,纹理坐标的变换,光照与材质的应用等均由开发者使用着色器代码完成,灵活性大大提高;另外,使用“片元着色器”取代了OpenGL ES 1.x渲染管线中“纹理环境和颜色求和”,“雾化”,“Alpha测试”等阶段,这使得纹理处理,颜色求和和雾效果均由开发者自行开发,大大增强了程序对片元的处理能力。
下面就通过一个基础的绘制三角形的案例来对OpenGL ES 2.0的开发过程有个初步的认识。
首先,我们开发一个工具类ShaderUtil,对常用的加载着色器的代码进行封装,主要功能就是将着色器(Shader)脚本加载进显卡并进行编译,其中loadFromAssets方法从Assets文件夹下加载着色器代码脚本,checkGlError方法检查过程中每一步是否出错,loadShader方法加载指定着色器,createProgram方加载顶点和片元着色器,创建着色器程序,并返回着色器程序id。
import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* 加载顶点与片元着色器的类
*/
public class ShaderUtil {
/**
* 加载指定着色器的方法
* @param shaderType 着色器的类型
* @param source 着色器的脚本字符串
*/
public static int loadShader( int shaderType , String source ){
//创建一个shader,并记录其类型
int shader = GLES20.glCreateShader(shaderType);
//若创建成功则加载着色器
if( shader != 0 ){
//加载着色器的源代码
GLES20.glShaderSource(shader,source);
//编译
GLES20.glCompileShader(shader);
int[] compiled = new int[ 1];
//获取Shader的编译情况
GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,compiled, 0);
//若编译失败则显示错误日志并删除此shader
if( compiled[ 0] == 0 ){
Log.e( "ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e( "ES20_ERROR",GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0 ;
}
}
return 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];
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;
}
/**
* 检查每一步操作是否有错误的方法
* @param op 发生错误的方法名
*/
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 脚本中加载着色器内容的方法
* @param fname 文件名
* @param r 资源文件
* @return 结果字符串
*/
public static String loadFromAssetsFile(String fname , Resources r){
String result = null ;
try {
//从assets文件夹中读取信息
InputStream in = r.getAssets().open(fname);
//定义一个int型变量
int ch = 0 ;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while( ( ch = in.read()) != - 1 ){
baos.write(ch);
}
byte[] buff = baos.toByteArray();
//关闭输出流
baos.close();
in.close();
//转换为UTF-8编码
result = new String( buff, "UTF-8");
result = result.replaceAll( "\\r\
", "
");
} catch ( Exception e){
e.printStackTrace();
}
return result;
}
} |
下面创建一个类继承GLSurfaceView,并实现GLSurfaceView.Renderer接口。
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Created by Administrator on 2017/9/6.
*/
public class MyGLSurfaceView extends GLSurfaceView {
//每次三角形旋转的角度
final float ANGLE_SPAN = 0.375f ;
//自定义线程类RotateThread的引用
SceneRenderer.RotateThread rthread ;
//自定义渲染器的引用
SceneRenderer mRenderer ;
//构造器
public MyGLSurfaceView(Context context){
super(context);
//使用OpenGL ES 2.0 需设置该值为2
this.setEGLContextClientVersion( 2);
//创建SceneRenderer类的对象
mRenderer = new SceneRenderer();
//设置渲染器
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
public class SceneRenderer implements GLSurfaceView. Renderer{
//声明Triangle类的引用
Triangle tle ;
//重写onDrawFrame方法
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
tle.drawSelf();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置视口
GLES20.glViewport( 0, 0,width,height);
//计算屏幕的宽度和高度比例
float ratio = ( float) width/height;
//设置透视投影
android.opengl.Matrix.frustumM(Triangle.mProjMatrix, 0,-ratio,ratio,- 1, 1, 1, 10);
//设置摄像机
android.opengl.Matrix.setLookAtM(Triangle.mVMatrix, 0, 0, 0, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置屏幕背景色
GLES20.glClearColor( 0, 0, 0, 1.0f);
//创建Triangle类的对象
tle = new Triangle(MyGLSurfaceView. this);
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
//创建RotateThread类的对象
rthread = new RotateThread();
//开启线程
rthread.start();
}
//自定义的内部类线程
public class RotateThread extends Thread{
public boolean flag = true ;
@Override
public void run() {
while (flag){
mRenderer.tle.xAngle = mRenderer.tle.xAngle + ANGLE_SPAN ;
try {
Thread.sleep( 20);
} catch (Exception e){
e.printStackTrace();
}
}
}
}
}
} |
下面编写代表三角形的类Triangle
import android.opengl.GLES20;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Created by Administrator on 2017/9/6.
*/
public class Triangle {
//4x4投影矩阵
public static float[] mProjMatrix = new float[ 16] ;
//摄像机位置朝向的参数矩阵
public static float[] mVMatrix = new float[ 16] ;
//总变换矩阵
public static float[] mMVPMatrix ;
//自定义渲染管线着色器程序id
int mProgram ;
//总变换矩阵引用
int muMVPMatrixHandle ;
//顶点位置属性引用
int maPositionHandle ;
//顶点颜色属性引用
int maColorHandle ;
//顶点着色器代码脚本
String mVertexShader ;
//片元着色器代码脚本
String mFragmentShader ;
//具体物体的3D变换矩阵,包括旋转,平移,缩放
static float[] mMMatrix = new float[ 16];
//顶点坐标数据缓冲
FloatBuffer mVertexBuffer ;
//顶点着色数据缓冲
FloatBuffer mColorBuffer ;
//顶点数量
int vCount ;
//绕x轴旋转的角度
float xAngle = 0 ;
//构造函数
public Triangle(MyGLSurfaceView mv ){
//初始化顶点数据
initVertexData();
//初始化着色器
initShader(mv);
}
//自定义的初始化顶点数据的方法
public void initVertexData(){
//顶点数量为3
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());
//转换为float型缓冲
mVertexBuffer = vbb.asFloatBuffer();
//在缓冲区内写入数据
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();
mColorBuffer.put(colors);
mColorBuffer.position( 0);
}
//产生最终变换矩阵的方法
public static float[] getFinalMatrix( float[] spec ){
mMVPMatrix = new float[ 16];
Matrix.multiplyMM(mMVPMatrix, 0,mVMatrix, 0,spec, 0);
Matrix.multiplyMM(mMVPMatrix, 0,mProjMatrix, 0,mMVPMatrix, 0);
//返回总变换矩阵
return mMVPMatrix ;
}
public void initShader(MyGLSurfaceView mv ){
mVertexShader = ShaderUtil.loadFromAssetsFile( "vertex.sh",mv.getResources());
mFragmentShader = ShaderUtil.loadFromAssetsFile( "frag.sh" , mv.getResources());
mProgram = ShaderUtil.createProgram(mVertexShader,mFragmentShader);
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
public void drawSelf(){
GLES20.glUseProgram(mProgram);
//初始化变换矩阵
Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);
//设置沿z轴正向位移
Matrix.translateM(mMMatrix, 0, 0, 0, 1);
//设置绕x轴旋转
Matrix.rotateM(mMMatrix, 0,xAngle, 1, 0, 0);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false,Triangle.getFinalMatrix(mMMatrix), 0);
//将顶点数据传送进渲染管线
GLES20.glVertexAttribPointer(maPositionHandle, 3,GLES20.GL_FLOAT, false, 3* 4,mVertexBuffer);
//将顶点着色数据传送进渲染管线
GLES20.glVertexAttribPointer(maColorHandle, 4,GLES20.GL_FLOAT, false, 4* 4,mColorBuffer);
//启用顶点位置数据
GLES20.glEnableVertexAttribArray(maPositionHandle);
//启用着色数据
GLES20.glEnableVertexAttribArray(maColorHandle);
//执行绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,vCount);
}
} |
最后在MainActivity中设置显示的视图为自定义的GLSurfaceview,并设置Renderer渲染器即可。
import android.content.pm.ActivityInfo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
MyGLSurfaceView mView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//创建MySurfaceView对象
mView = new MyGLSurfaceView( this);
//获取焦点
mView.requestFocus();
//设为可触控
mView.setFocusableInTouchMode( true);
setContentView(mView);
}
@Override
protected void onResume() {
super.onResume();
//调用GLSurfaceview的onResume方法
mView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//调用GLSurfaceview的onPause方法
mView.onPause();
}
} |
最终运行效果如下
----------------------------
原文链接:https://blog.csdn.net/SakuraMashiro/article/details/78252279
程序猿的技术大观园:www.javathinker.net
[这个贴子最后由 flybird 在 2020-03-11 10:59:47 重新编辑]
|
|