前言
这是入门篇,让大家简单了解OpenGL ES,并且保证你能绘制出三角形。
能动手就不哔哔
大家都是小学生,手把手教学吧!下面直接上代码。
MainAcitivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//创建一个GLSurfaceView
GLSurfaceView glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setEGLContextClientVersion(2);
//设置自己的Render.Render 内进行图形的绘制
glSurfaceView.setRenderer(new GLRenderer());
setContentView(glSurfaceView);
}
GLRenderer
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import com.example.openglcampaign.deep.Constant;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class GLRenderer implements GLSurfaceView.Renderer {
private FloatBuffer vertexBuffer;//顶点缓冲
private final String vertexShaderCode =//顶点着色代码
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =//片元着色代码
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
//顶点的坐标系
private static float TRIANGLE_COORDS[] = {
//Order of coordinates: X, Y, Z
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
private int mProgramObjectId;
//在数组中,一个顶点需要3个来描述其位置,需要3个偏移量
private static final int COORDS_PER_VERTEX = 3;
private static final int COORDS_PER_COLOR = 0;
//在数组中,描述一个顶点,总共的顶点需要的偏移量。这里因为只有位置顶点,所以和上面的值一样
private static final int TOTAL_COMPONENT_COUNT = COORDS_PER_VERTEX + COORDS_PER_COLOR;
//一个点需要的byte偏移量。
private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constant.BYTES_PER_FLOAT;
// 颜色,rgba 更换颜色
float TRIANGLE_COLOR[] = {0.5176471f, 0.77254903f, 0.9411765f, 1.0f};
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//初始化顶点字节缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);//每个浮点数:坐标个数* 4字节
bb.order(ByteOrder.nativeOrder());//使用本机硬件设备的字节顺序
vertexBuffer = bb.asFloatBuffer();// 从字节缓冲区创建浮点缓冲区
vertexBuffer.put(TRIANGLE_COORDS);// 将坐标添加到FloatBuffer
vertexBuffer.position(0);//设置缓冲区以读取第一个坐标
//0.简单的给窗口填充一种颜色
GLES20.glClearColor(1.0f, 0f, 0f, 0f);//rgba
//在创建的时候,去创建这些着色器
//1.根据String进行编译。得到着色器id
int vertexShaderObjectId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShaderObjectId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
//3.继续套路。取得到program
mProgramObjectId = GLES20.glCreateProgram();
//将shaderId绑定到program当中
GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);
//4.最后,启动GL link program
GLES20.glLinkProgram(mProgramObjectId);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//在窗口改变的时候调用
GLES20.glViewport(0, 0, width, height);//GL视口
}
@Override
public void onDrawFrame(GL10 gl) {
//0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgramObjectId);
//1.根据我们定义的取出定义的位置
int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, "vPosition");
//2.开始启用我们的position
GLES20.glEnableVertexAttribArray(vPosition);
//3.将坐标数据放入
GLES20.glVertexAttribPointer(
vPosition, //上面得到的id
COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
GLES20.GL_FLOAT, false,
STRIDE, //一个顶点需要多少个字节的偏移量
vertexBuffer);
//取出颜色
int uColor = GLES20.glGetUniformLocation(mProgramObjectId, "vColor");
//开始绘制
//设置绘制三角形的颜色
GLES20.glUniform4fv(
uColor,
1,
TRIANGLE_COLOR,
0
);
//绘制三角形.
//draw arrays的几种方式
//GL_TRIANGLES三角形
//GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形)
//GL_TRIANGLE_FAN扇形(可以描述圆形)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, TRIANGLE_COORDS.length / 3);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
/**
* 加载作色器
*
* @param type 着色器类型
* 顶点着色 {@link GLES20.GL_VERTEX_SHADER}
* 片元着色 {@link GLES20.GL_FRAGMENT_SHADER}
* @param shaderCode 着色代码
* @return 作色器
*/
private static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);//创建着色器
if (shader == 0) {//加载失败直接返回
return 0;
}
GLES20.glShaderSource(shader, shaderCode);//加载着色器源代码
GLES20.glCompileShader(shader);//编译
return shader;
}
}
到目前位置,你的代码就可以绘制出来三角形了。
拆解
loadShader 方法。
/**
* 加载作色器
*
* @param type 着色器类型
* 顶点着色 {@link GLES20.GL_VERTEX_SHADER}
* 片元着色 {@link GLES20.GL_FRAGMENT_SHADER}
* @param shaderCode 着色代码
* @return 作色器
*/
private static int loadShader(int type, String shaderCode) {
//1.根据类型,创建着色器
int shader = GLES20.glCreateShader(type);
if (shader == 0) {
//加载失败直接返回
return 0;
}
//2.加载着色器源代码
GLES20.glShaderSource(shader, shaderCode);
//3.编译(往下还有一个验证,我们先不处理)
GLES20.glCompileShader(shader);
return shader;
}
onSurfaceCreated 方法
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//初始化顶点字节缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);//每个浮点数:坐标个数* 4字节
bb.order(ByteOrder.nativeOrder());//使用本机硬件设备的字节顺序
vertexBuffer = bb.asFloatBuffer();// 从字节缓冲区创建浮点缓冲区
vertexBuffer.put(TRIANGLE_COORDS);// 将坐标添加到FloatBuffer
vertexBuffer.position(0);//设置缓冲区以读取第一个坐标
//0.简单的给窗口填充一种颜色
GLES20.glClearColor(1.0f, 0f, 0f, 0f);//rgba
//在创建的时候,去创建这些着色器
//1.根据String进行编译。得到着色器id
int vertexShaderObjectId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShaderObjectId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
//3.继续套路。取得到program
mProgramObjectId = GLES20.glCreateProgram();
//将shaderId绑定到program当中
GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);
//4.最后,启动GL link program
GLES20.glLinkProgram(mProgramObjectId);
}
总结:初始化相应的顶点数据,创建一个程序对象并链接着色器。
onSurfaceChanged 方法
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//在窗口改变的时候调用
GLES20.glViewport(0, 0, width, height);//GL视口
}
onDrawFrame 方法
@Override
public void onDrawFrame(GL10 gl) {
//0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgramObjectId);
//1.根据我们定义的取出定义的位置
int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, "vPosition");
//2.开始启用我们的position
GLES20.glEnableVertexAttribArray(vPosition);
//3.将坐标数据放入
GLES20.glVertexAttribPointer(
vPosition, //上面得到的id
COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
GLES20.GL_FLOAT, false,
STRIDE, //一个顶点需要多少个字节的偏移量
vertexBuffer);
//取出颜色
int uColor = GLES20.glGetUniformLocation(mProgramObjectId, "vColor");
//开始绘制
//设置绘制三角形的颜色
GLES20.glUniform4fv(
uColor,
1,
TRIANGLE_COLOR,
0
);
//绘制三角形.
//draw arrays的几种方式
//GL_TRIANGLES三角形
//GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形)
//GL_TRIANGLE_FAN扇形(可以描述圆形)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, TRIANGLE_COORDS.length / 3);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
MainAcitivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//创建一个GLSurfaceView
GLSurfaceView glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setEGLContextClientVersion(2);
//设置自己的Render.Render 内进行图形的绘制
glSurfaceView.setRenderer(new GLRenderer());
setContentView(glSurfaceView);
}
GLRenderer
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import com.example.openglcampaign.deep.Constant;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class GLRenderer implements GLSurfaceView.Renderer {
private FloatBuffer vertexBuffer;//顶点缓冲
private final String vertexShaderCode =//顶点着色代码
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =//片元着色代码
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
//顶点的坐标系
private static float TRIANGLE_COORDS[] = {
//Order of coordinates: X, Y, Z
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
private int mProgramObjectId;
//在数组中,一个顶点需要3个来描述其位置,需要3个偏移量
private static final int COORDS_PER_VERTEX = 3;
private static final int COORDS_PER_COLOR = 0;
//在数组中,描述一个顶点,总共的顶点需要的偏移量。这里因为只有位置顶点,所以和上面的值一样
private static final int TOTAL_COMPONENT_COUNT = COORDS_PER_VERTEX + COORDS_PER_COLOR;
//一个点需要的byte偏移量。
private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constant.BYTES_PER_FLOAT;
// 颜色,rgba 更换颜色
float TRIANGLE_COLOR[] = {0.5176471f, 0.77254903f, 0.9411765f, 1.0f};
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//初始化顶点字节缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);//每个浮点数:坐标个数* 4字节
bb.order(ByteOrder.nativeOrder());//使用本机硬件设备的字节顺序
vertexBuffer = bb.asFloatBuffer();// 从字节缓冲区创建浮点缓冲区
vertexBuffer.put(TRIANGLE_COORDS);// 将坐标添加到FloatBuffer
vertexBuffer.position(0);//设置缓冲区以读取第一个坐标
//0.简单的给窗口填充一种颜色
GLES20.glClearColor(1.0f, 0f, 0f, 0f);//rgba
//在创建的时候,去创建这些着色器
//1.根据String进行编译。得到着色器id
int vertexShaderObjectId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShaderObjectId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
//3.继续套路。取得到program
mProgramObjectId = GLES20.glCreateProgram();
//将shaderId绑定到program当中
GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);
//4.最后,启动GL link program
GLES20.glLinkProgram(mProgramObjectId);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//在窗口改变的时候调用
GLES20.glViewport(0, 0, width, height);//GL视口
}
@Override
public void onDrawFrame(GL10 gl) {
//0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgramObjectId);
//1.根据我们定义的取出定义的位置
int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, "vPosition");
//2.开始启用我们的position
GLES20.glEnableVertexAttribArray(vPosition);
//3.将坐标数据放入
GLES20.glVertexAttribPointer(
vPosition, //上面得到的id
COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
GLES20.GL_FLOAT, false,
STRIDE, //一个顶点需要多少个字节的偏移量
vertexBuffer);
//取出颜色
int uColor = GLES20.glGetUniformLocation(mProgramObjectId, "vColor");
//开始绘制
//设置绘制三角形的颜色
GLES20.glUniform4fv(
uColor,
1,
TRIANGLE_COLOR,
0
);
//绘制三角形.
//draw arrays的几种方式
//GL_TRIANGLES三角形
//GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形)
//GL_TRIANGLE_FAN扇形(可以描述圆形)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, TRIANGLE_COORDS.length / 3);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
/**
* 加载作色器
*
* @param type 着色器类型
* 顶点着色 {@link GLES20.GL_VERTEX_SHADER}
* 片元着色 {@link GLES20.GL_FRAGMENT_SHADER}
* @param shaderCode 着色代码
* @return 作色器
*/
private static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);//创建着色器
if (shader == 0) {//加载失败直接返回
return 0;
}
GLES20.glShaderSource(shader, shaderCode);//加载着色器源代码
GLES20.glCompileShader(shader);//编译
return shader;
}
}
到目前位置,你的代码就可以绘制出来三角形了。
拆解
loadShader 方法。
/**
* 加载作色器
*
* @param type 着色器类型
* 顶点着色 {@link GLES20.GL_VERTEX_SHADER}
* 片元着色 {@link GLES20.GL_FRAGMENT_SHADER}
* @param shaderCode 着色代码
* @return 作色器
*/
private static int loadShader(int type, String shaderCode) {
//1.根据类型,创建着色器
int shader = GLES20.glCreateShader(type);
if (shader == 0) {
//加载失败直接返回
return 0;
}
//2.加载着色器源代码
GLES20.glShaderSource(shader, shaderCode);
//3.编译(往下还有一个验证,我们先不处理)
GLES20.glCompileShader(shader);
return shader;
}
onSurfaceCreated 方法
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//初始化顶点字节缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * 4);//每个浮点数:坐标个数* 4字节
bb.order(ByteOrder.nativeOrder());//使用本机硬件设备的字节顺序
vertexBuffer = bb.asFloatBuffer();// 从字节缓冲区创建浮点缓冲区
vertexBuffer.put(TRIANGLE_COORDS);// 将坐标添加到FloatBuffer
vertexBuffer.position(0);//设置缓冲区以读取第一个坐标
//0.简单的给窗口填充一种颜色
GLES20.glClearColor(1.0f, 0f, 0f, 0f);//rgba
//在创建的时候,去创建这些着色器
//1.根据String进行编译。得到着色器id
int vertexShaderObjectId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShaderObjectId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
//3.继续套路。取得到program
mProgramObjectId = GLES20.glCreateProgram();
//将shaderId绑定到program当中
GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);
//4.最后,启动GL link program
GLES20.glLinkProgram(mProgramObjectId);
}
总结:初始化相应的顶点数据,创建一个程序对象并链接着色器。
onSurfaceChanged 方法
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//在窗口改变的时候调用
GLES20.glViewport(0, 0, width, height);//GL视口
}
onDrawFrame 方法
@Override
public void onDrawFrame(GL10 gl) {
//0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgramObjectId);
//1.根据我们定义的取出定义的位置
int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, "vPosition");
//2.开始启用我们的position
GLES20.glEnableVertexAttribArray(vPosition);
//3.将坐标数据放入
GLES20.glVertexAttribPointer(
vPosition, //上面得到的id
COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
GLES20.GL_FLOAT, false,
STRIDE, //一个顶点需要多少个字节的偏移量
vertexBuffer);
//取出颜色
int uColor = GLES20.glGetUniformLocation(mProgramObjectId, "vColor");
//开始绘制
//设置绘制三角形的颜色
GLES20.glUniform4fv(
uColor,
1,
TRIANGLE_COLOR,
0
);
//绘制三角形.
//draw arrays的几种方式
//GL_TRIANGLES三角形
//GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形)
//GL_TRIANGLE_FAN扇形(可以描述圆形)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, TRIANGLE_COORDS.length / 3);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
总结:设置窗口和清除颜色缓冲区,加载几何形状和绘制图元。
扩展
openGL使用右手坐标。Z轴朝向屏幕外。我们看一下X和Y轴。
坐标轴
//顶点的坐标系
private static float TRIANGLE_COORDS[] = {
//Order of coordinates: X, Y, Z
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
原本认为是等腰的三角形,最后发现不是,就是因为坐标系的问题。
我们看一下归一化坐标系:
归一化坐标系
我们下一步借助矩阵也可以在手机上绘制出这样的三角形。
知识延伸
private final String vertexShaderCode =//顶点着色代码
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
为什么 vPosition是使用vec4 ,这是一个比较有意思的问题,一下是百度的答案。
由于3d图形用到了 4x4的矩阵(4行4列),矩阵乘法要求 nxm * mxp(n行m列 乘 m行p列)才能相乘,注意m是相同的,所以 1x4 * 4x4 才能相乘。
所以是vec4而不是vec3。
至于为什么 4x4 看下那些 投影矩阵的演算过程就知道了。
至于你说的多出来的那一位,
如果是点坐标的话是 1.0
position 是位置所以应该是 (x,y,z,1.0f)
如果是 方向向量 ,也就是 代表的不是点 而是一个方向 则是 0.0 ,也就是 (x,y,z,0.0f)。
这也是 要与矩阵进行乘法所决定的。
建议学一些矩阵的运算,既然要用到 3d ,学矩阵是很有必要的,很多计算用到了矩阵
而且现在的cpu 也提供了 mmx来做矩阵的运算,可见矩阵对3d的作用很大。