一、概要
上节中了解了OpenGLES最简单的使用OpenGLES2.0基础,这节在深入一个层次了解怎么去画一个三角形。对于OpenGL绘图,最主要的就是Renderer的处理。因为GLSurfaceView相当于一个View,只是显示图像,而Renderer就是GLSurfaceView要显示的图像。在Renderer中主要有三个函数onSurfaceCreated(),onSurfaceCreated()和onDrawFrame()。其中前两个函数主要用于初始化逻辑,绘图主要是在onDrawFrame()方法里面。
二、相关要点
1.绘制方式
OpenGLES图元的绘制方式有GL_POINTS 、GL_LINES、 GL_LINE_LOOP 、GL_LINE_STRIP、GL_TRIANGLES 、GL_TRIANGLE_STRIP 、GL_TRIANGLE_FAN 这几种,每个都有自己独特的作用。在使用时根据自己的需求选择不同的绘制方式进行绘制。
绘制方式解释
GL_POINTS点GL_LINES线段GL_LINE_STRIP多段线GL_LINE_LOOP线圈GL_TRIANGLES三角形GL_TRIANGLE_STRIP三角形条带GL_TRIANGLE_FAN三角形扇
1. 非索引法: GLES20.glDrawArrays(GLES20.GL_POINTS, 0, vCount); 2. 索引法 :GLES20.glDrawElements(GLES20.GL_POINTS, iCount, GLES20.GL_UNSIGNED_BYTE, mIndexBuffer);
(1).GL_LINES(绘制直线)
以(v0,v1),(v2,v3),(v4,v5)的形式绘制直线。(当绘制的点只有两个时,GL_LINES与GL_LINE_STRIP绘线方式没有差异)
GLES20
.glDrawArrays(GLES20
.GL_LINES,
0, count)
(2).GL_LINE_STRIP(绘制直线)
以(v0,v1),(v1,v2),(v2,v3),(v3,v4),(v4,v5)的形式绘制直线。GL_LINE_LOOP以(v0,v1),(v1,v2),(v2,v3),(v3,v4),(v4,v5),(v5,v0)的形式绘制直线,比GL_LINE_STRIP多一条(v5,v0)的直线。
GLES20
.glDrawArrays(GLES20
.GL_LINE_STRIP,
0, count)
(3).GL_POINTS(绘制点)
GLES20
.glDrawArrays(GLES20
.GL_POINTS,
0, count)
(4).GL_TRIANGLES(绘制三角形)
每三个顶之间绘制三角形,之间不连接。
GLES20
.glDrawArrays(GLES20
.GL_TRIANGLES,
0, count)
(5).GL_TRIANGLE_STRIP(绘制三角形)
它是由最少3个点构成(正好3个点就是一个GL_TRIANGLES)每增加1个点,新增的点会和之前已有的两个点构成新的三角形,依次类推。
GLES20
.glDrawArrays(GLES20
.GL_TRIANGLE_STRIP,
0, count)
(6).GL_TRIANGLE_FAN(绘制三角形)
以(v0,v1,v2),(v0,v2,v3),(v0,v3,v4)的形式绘制三角形。
GLES20
.glDrawArrays(GLES20
.GL_TRIANGLE_FAN,
0, count)
2.OpenGL矩阵变换
通过模拟人眼观察物体, 可能引起屏幕图像改变的几种行为和分别对应的几种OpenGL变换,每种变换都可以由相应的Matrix静态方法生成相应的变换矩阵。 坐标系原点在屏幕中央,z轴正方向垂直屏幕向外。
行为变换记录变换方式的放法记录变换方式的[4*4]矩阵
眼睛相对物体的位置改变视图变换Matrix.setLookAtM(mVMatrix,0(偏移量),cx(相机位置x), cy, cz,tx(观察点位置x), ty, tz,upx(顶部朝向x), upy, upz)mVMatrix[16]物体平移模型变换Matrix.translateM(mMMatrix,0(偏移量),x(平移量x), y, z)mMMatrix[16]物体按坐标缩放比例缩放模型变换Matrix.scaleM(mMMatrix,sx(缩放因子),sy, sz)mMMatrix[16]物体旋转模型变换Matrix.rotateM(mMMatrix,0(偏移量),angle(旋转角度),x(需要旋转的轴), y, z)mMMatrix[16]凸透镜眼睛投影变换Matrix.frustumM(mPMatrix,0(偏移量),left,right,bottom,top,near,far//near>0)mPMatrix[16]平面透镜眼睛投影变换Matrix.orthoM(mPMatrix,0(偏移量),left,right,bottom,top,near,far//near>0)mPMatrix[16]
1. 变换技巧: 利用栈存储变换矩阵的算法可实现复杂场景的图形变换和提高渲染效率。 2. 变换顺序: 矩阵相乘是讲究顺序的, 比如先平移再缩放 和 先缩放再平移的效果是不同的。
三、绘制流程
定义物体顶点信息编辑着色器代码编译连接着色器程序物体顶点信息传入着色器GLSurfaceView加载RendererActivity加载GLSurfaceView渲染物体输出屏幕
1.定义物体顶点信息
物体的顶点信息一般包括:(1)三维坐标信息; (2)三维法向量信息;(3)颜色信息或二维贴图坐标信息;复杂物体的坐标通常通过特定的软件绘制生成顶点信息文件,然后进行解读;
2.编辑着色器代码
着色器语言编写的代码, 通常后缀名.sh, 语法和C相似95%以上, 是OpenGLES2.0的可编程渲染通道, 增加了编程难度, 但是能实现更绚丽多彩的效果。一般编写好的着色器代码放在android工程目录下的assets文件, assets常用轻量资源的读写。
3.编译连接着色器
从assets获取着色器脚本, 并返回脚本字符串的过程
// 加载顶点着色器的脚本内容
mVertexShader = ShaderUtil
.loadFromAssetsFile(
"graph_color_and_texture_vertex.shl",mContext
.getResources())
// 加载片元着色器的脚本内容
mFragmentShader = ShaderUtil
.loadFromAssetsFile(
"graph_color_and_texture_frag.shl",mContext
.getResources())
加载片元着色器和顶点着色器
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);
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;
}
}
4. 物体顶点信息传入着色器
生成顶点信息, 用FloatBuffer类存储
ByteBuffer bbv = ByteBuffer
.allocateDirect(triangleCoords
.length *
4)
bbv
.order(ByteOrder
.nativeOrder())
mVertexBuffer = bbv
.asFloatBuffer()
mVertexBuffer
.put(triangleCoords)
mVertexBuffer
.position(
0)
获取着色器参数ID
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
传入数据
GLES20
.glVertexAttribPointer(maPositionHandle,
3, GLES20
.GL_FLOAT, false,
3 *
4, mVertexBuffer)
使用数据
GLES20
.glEnableVertexAttribArray(maPositionHandle)
5. GLSurfaceView加载Renderer
初始化GLSurfaceView
setEGLContextClientVersion(
2);
mRenderer =
new MyRenderer();
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
初始化Renderer
/**
* 仅调用一次,用于设置view的OpenGL ES环境
*/
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(
0.0f,
0.0f,
0.0f,
1.0f);
triangle =
new Triangle();
}
/**
* 当view的几何形状发生变化时调用,比如设备从竖屏变为横屏
*/
@Override
public void onSurfaceChanged(GL10 gl,
int width,
int height) {
GLES20.glViewport(
0,
0, width, height);
float ratio = (
float) width / height;
Matrix.frustumM(mProjectionMatrix,
0, -ratio, ratio, -
1,
1,
3,
7);
}
/**
* 每次重绘view时调用
*/
@Override
public void onDrawFrame(GL10 gl) {
float[] scratch =
new float[
16];
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
Matrix.setLookAtM(mViewMatrix,
0,
0,
0, -
3,
0f,
0f,
0f,
0f,
1.0f,
0.0f);
Matrix.multiplyMM(mMVPMatrix,
0, mProjectionMatrix,
0, mViewMatrix,
0);
Matrix.setRotateM(mRotationMatrix,
0, mAngle,
0,
0,
1.0f);
Matrix.multiplyMM(scratch,
0, mMVPMatrix,
0, mRotationMatrix,
0);
triangle.draw(scratch);
}
7. 渲染物体输出屏幕
ProgramFactory.initFactory(getApplicationContext());
mySurfaceView =
new MySurfaceView(
this);
setContentView(mySurfaceView);
四、运行效果
五、全部代码
1.在Manifests中添加
<uses
-feature android:glEsVersion
="0x00020000" android:required
="true" />
<supports
-gl-texture android:name
="GL_OES_compressed_ETC1_RGB8_texture" />
<supports
-gl-texture android:name
="GL_OES_compressed_paletted_texture" />
2.MyGLSurfaceView继承自SurfaceView
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
/**
* Created by Administrator on 2017/3/8.
* description :
*/
public class MyGLSurfaceView extends GLSurfaceView {
MyRenderer mRenderer;
public MyGLSurfaceView(Context context) {
super(context);
init();
}
private void init(){
setEGLContextClientVersion(
2);
mRenderer =
new MyRenderer();
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
private final float TOUCH_SCALE_FACTOR =
180.0f /
320;
private float mPreviousX;
private float mPreviousY;
@Override
public boolean onTouchEvent(MotionEvent e) {
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = x - mPreviousX;
float dy = y - mPreviousY;
if (y > getHeight() /
2) {
dx = dx * -
1 ;
}
if (x < getWidth() /
2) {
dy = dy * -
1 ;
}
mRenderer.setAngle(mRenderer.getAngle() +((dx + dy) * TOUCH_SCALE_FACTOR));
requestRender();
}
mPreviousX = x;
mPreviousY = y;
return true;
}
}
3.MyRenderer实现了GLSurfaceView.Renderer接口
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Created by Administrator on 2017/3/8.
* description :
*/
public class MyRenderer implements GLSurfaceView.Renderer {
Triangle triangle;
private final float[] mMVPMatrix =
new float[
16];
private final float[] mProjectionMatrix =
new float[
16];
private final float[] mViewMatrix =
new float[
16];
private final float[] mRotationMatrix =
new float[
16];
private float mAngle;
/**
* 仅调用一次,用于设置view的OpenGL ES环境
*/
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(
0.0f,
0.0f,
0.0f,
1.0f);
triangle =
new Triangle();
}
/**
* 当view的几何形状发生变化时调用,比如设备从竖屏变为横屏
*/
@Override
public void onSurfaceChanged(GL10 gl,
int width,
int height) {
GLES20.glViewport(
0,
0, width, height);
float ratio = (
float) width / height;
Matrix.frustumM(mProjectionMatrix,
0, -ratio, ratio, -
1,
1,
3,
7);
}
/**
* 每次重绘view时调用
*/
@Override
public void onDrawFrame(GL10 gl) {
float[] scratch =
new float[
16];
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
Matrix.setLookAtM(mViewMatrix,
0,
0,
0, -
3,
0f,
0f,
0f,
0f,
1.0f,
0.0f);
Matrix.multiplyMM(mMVPMatrix,
0, mProjectionMatrix,
0, mViewMatrix,
0);
Matrix.setRotateM(mRotationMatrix,
0, mAngle,
0,
0,
1.0f);
Matrix.multiplyMM(scratch,
0, mMVPMatrix,
0, mRotationMatrix,
0);
triangle.draw(scratch);
}
public void setAngle(
float angle) {
this.mAngle = angle;
}
public float getAngle() {
return mAngle;
}
}
3.Triangle主要处理画三角形逻辑
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Created by Administrator on 2017/3/8.
* description :
*/
public class Triangle {
private FloatBuffer mVertexBuffer;
private int mProgram;
private int mMVPMatrixHandle;
private int maPositionHandle;
private int maColorHandle;
static final int COORDS_PER_VERTEX =
3;
static float triangleCoords[] = {
0.0f,
0.622008459f,
0.0f,
-
0.5f, -
0.311004243f,
0.0f,
0.5f, -
0.311004243f,
0.0f
};
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX *
4;
static float color[] = {
0.63671875f,
0.76953125f,
0.22265625f,
1.0f};
public Triangle() {
initVertexData();
initShader();
}
public void initVertexData() {
ByteBuffer bbv = ByteBuffer.allocateDirect(triangleCoords.length *
4);
bbv.order(ByteOrder.nativeOrder());
mVertexBuffer = bbv.asFloatBuffer();
mVertexBuffer.put(triangleCoords);
mVertexBuffer.position(
0);
}
public void initShader() {
mProgram = ProgramFactory.shared().getProgram();
maPositionHandle = GLES20.glGetAttribLocation(mProgram,
"vPosition");
maColorHandle = GLES20.glGetUniformLocation(mProgram,
"vColor");
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
"uMVPMatrix");
}
public void draw(
float[] mvpMatrix) {
GLES20.glUseProgram(mProgram);
GLES20.glEnableVertexAttribArray(maPositionHandle);
GLES20.glVertexAttribPointer(maPositionHandle,
3, GLES20.GL_FLOAT,
false, vertexStride, mVertexBuffer);
GLES20.glUniform4fv(maColorHandle,
1, color,
0);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
"uMVPMatrix");
GLES20.glUniformMatrix4fv(mMVPMatrixHandle,
1,
false, mvpMatrix,
0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,
0, vertexCount);
GLES20.glDisableVertexAttribArray(maPositionHandle);
}
}
4.在MainActivity中使用
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
/**
* Created by Administrator on 2017/3/8.
* description :
*/
public class MainActivity extends AppCompatActivity {
MyGLSurfaceView mySurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ProgramFactory.initFactory(getApplicationContext());
mySurfaceView =
new MyGLSurfaceView(
this);
setContentView(mySurfaceView);
}
@Override
protected void onPause() {
super.onPause();
mySurfaceView.onPause();
}
@Override
protected void onResume() {
super.onResume();
mySurfaceView.onResume();
}
}
5.以及在assets目录下的两个着色器脚本
graph_color_and_texture_frag.shl
precision mediump float;
uniform vec4 vColor;
void main() {
gl_FragColor = vColor;
}
graph_color_and_texture_vertex.shl
uniform mat4 uMVPMatrix;
attribute vec4 vPosition;
void main() {
gl_Position = uMVPMatrix * vPosition;
}
6.下面贴上两个OpenGL工具类
ProgramFactory
import android.content.Context;
/**
* Created by Administrator on 2017/3/8.
* description :
*/
public class ProgramFactory {
private static Context mContext =
null;
String mVertexShader;
String mFragmentShader;
int mMixProgram;
public static void initFactory(Context context){
mContext = context;
}
private ProgramFactory() {
mVertexShader = ShaderUtil.loadFromAssetsFile(
"graph_color_and_texture_vertex.shl",
mContext.getResources());
mFragmentShader = ShaderUtil.loadFromAssetsFile(
"graph_color_and_texture_frag.shl",
mContext.getResources());
mMixProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
}
private static ProgramFactory mInstance;
public static ProgramFactory
shared() {
if(mContext ==
null){
throw new IllegalStateException(
"ProgramFactory.initFactory() method is not transfer : mContext = null");
}
if (mInstance ==
null) {
mInstance =
new ProgramFactory();
}
return mInstance;
}
public static void recycle() {
mInstance =
null;
}
public int getProgram() {
return mMixProgram;
}
}
ShaderUtil
import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* Created by Administrator on 2017/3/8.
* description :加载顶点Shader与片元Shader的工具类
*/
public class ShaderUtil {
/**
* 加载制定shader的方法
*
* @param shaderType
* shader的类型 GLES20.GL_VERTEX_SHADER GLES20.GL_FRAGMENT_SHADER
* @param source
* shader的脚本字符串
* @return
*/
public static int loadShader(
int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader !=
0) {
if (source !=
null) {
GLES20.glShaderSource(shader, source);
}
GLES20.glCompileShader(shader);
int[] compiled =
new int[
1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled,
0);
if (compiled[
0] ==
0) {
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);
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;
}
public static void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
throw new RuntimeException(op +
": glError " + error);
}
}
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;
}
}
以上即为使用OpenGLES20画三角形的全部代码
相关链接:
源码下载
Shader相关介绍
matrix详解