Android多媒体之GLES2战记第三集--圣火之光
前情回顾
旁边: 勇者们为求黑龙宝藏,集结起来共闯黑龙副本,经历重重艰辛,
终于获得立方开启了黑龙之门
,这也只是新征程的起点,后面将有更大的挑战等着他们
张风捷特烈
打开了门之后,看到了什么?让我们继续收看
副本九:黑暗之渊
world-black.png在打开门后,光芒全部消失,眼中一团黑暗,
张风捷特烈
踏出一步
便立刻下坠,仿佛是无尽的深渊,地面?地面在那里?我还要下坠多久?
1.第一关卡:创造世界
简单的世界.pngNPC:
This is the world without anything,you must create everything by yourself.
我:好吧,总结一下流程吧,顺便该封的封一下
1.1.常量:
public class Cons {
//维度:独立参数的数目
public static final int DIMENSION_2 = 2;//2维度
public static final int DIMENSION_3 = 3;//3维度
public static final int DIMENSION_4 = 4;//4维度
}
1.2.显示的世界:World.java
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/13/013:10:46<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:GL的世界
*/
public class World extends GLSurfaceView {
private WorldRenderer mRenderer;
public World(Context context) {
this(context,null);
}
public World(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setEGLContextClientVersion(2);//设置OpenGL ES 2.0 context
mRenderer = new WorldRenderer(getContext());
setRenderer(mRenderer);//设置渲染器
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
}
1.3.世界的渲染器WorldRenderer
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/9 0009:18:56<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:GL世界渲染类
*/
public class WorldRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "GLRenderer";
//Model View Projection Matrix--模型视图投影矩阵
private static float[] mMVPMatrix = new float[16];
//投影矩阵 mProjectionMatrix
private static final float[] mProjectionMatrix = new float[16];
//视图矩阵 mViewMatrix
private static final float[] mViewMatrix = new float[16];
//变换矩阵
private float[] mOpMatrix = new float[16];
private Context mContext;
private RendererAble mWorldShape;
public WorldRenderer(Context context) {
mContext = context;
}
private int currDeg = 0;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);//rgba
mWorldShape = new WorldShape(mContext);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);//GL视口
float ratio = (float) width / height;
//透视投影矩阵--截锥
Matrix.frustumM(mProjectionMatrix, 0,
-ratio, ratio, -1, 1,
3, 9);
// 设置相机位置(视图矩阵)
Matrix.setLookAtM(mViewMatrix, 0,
2f, 2f, -6.0f,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
}
/**
* 此方法会不断执行 {@link GLSurfaceView.RENDERMODE_CONTINUOUSLY}
* 此方法执行一次 {@link GLSurfaceView.RENDERMODE_WHEN_DIRTY}
*
* @param gl
*/
@Override
public void onDrawFrame(GL10 gl) {
//清除颜色缓存和深度缓存
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//初始化变换矩阵
Matrix.setRotateM(mOpMatrix, 0, currDeg, 0, 1, 0);
Matrix.multiplyMM(mMVPMatrix, 0,
mViewMatrix, 0,
mOpMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0,
mProjectionMatrix, 0,
mMVPMatrix, 0);
mWorldShape.draw(mMVPMatrix);
//打开深度检测
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
}
}
2.第二关卡:打开圣火之光(画点)
一点.png黑暗中应该先出现一个点,代表希望之光
2.1--片元着色代码:world.frag
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
2.2--顶点着色代码:world.frag
注意这里要设置点的大小,否则默认为0
attribute vec3 vPosition;//顶点坐标
uniform mat4 uMVPMatrix; //总变换矩阵
attribute vec4 aColor;//顶点颜色
varying vec4 vColor;//片元颜色
void main() {
gl_Position = uMVPMatrix*vec4(vPosition,1);
vColor = aColor;//将顶点颜色传给片元
gl_PointSize=10.0;//设置点的大小,默认为0
}
2.3--点形状绘制
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/13/013:8:39<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:世界的形状
*/
public class WorldShape extends RendererAble {
private int mProgram;//OpenGL ES 程序
private int mPositionHandle;//位置句柄
private int mColorHandle;//颜色句柄
private int muMVPMatrixHandle;//顶点变换矩阵句柄
private FloatBuffer mColorBuffer;//颜色缓冲
private final int vertexColorStride = Cons.DIMENSION_4 * 4; // 4*4=16
private FloatBuffer mVertexBuffer;//顶点缓冲
private final int vertexStride = Cons.DIMENSION_3 * 4; // 3*4=12
private float[] mVertex = new float[]{
0.0f,0.0f,0.0f
};
private float[] mColor = new float[]{
1.0f, 1.0f, 1.0f, 1.0f,
};
public WorldShape(Context context) {
super(context);
mColorBuffer = GLUtil.getFloatBuffer(mColor);
mVertexBuffer = GLUtil.getFloatBuffer(mVertex);
initProgram();
}
private void initProgram() {
//顶点着色
int vertexShader = GLUtil.loadShaderAssets(mContext,
GLES20.GL_VERTEX_SHADER, "world.vert");
//片元着色
int fragmentShader = GLUtil.loadShaderAssets(mContext,
GLES20.GL_FRAGMENT_SHADER, "world.frag");
mProgram = GLES20.glCreateProgram();//创建空的OpenGL ES 程序
GLES20.glAttachShader(mProgram, vertexShader);//加入顶点着色器
GLES20.glAttachShader(mProgram, fragmentShader);//加入片元着色器
GLES20.glLinkProgram(mProgram);//创建可执行的OpenGL ES项目
//获取顶点着色器的vPosition成员的句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//获取片元着色器的vColor成员的句柄
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
//获取程序中总变换矩阵uMVPMatrix成员的句柄
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
@Override
public void draw(float[] mvpMatrix) {
// 将程序添加到OpenGL ES环境中
GLES20.glUseProgram(mProgram);
//启用顶点的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//启用顶点颜色的句柄
GLES20.glEnableVertexAttribArray(mColorHandle);
//顶点矩阵变换
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
//准备顶点坐标数据
GLES20.glVertexAttribPointer(
mPositionHandle,//int indx, 索引
Cons.DIMENSION_3,//int size,大小
GLES20.GL_FLOAT,//int type,类型
false,//boolean normalized,//是否标准化
vertexStride,// int stride,//跨度
mVertexBuffer);// java.nio.Buffer ptr//缓冲
//准备顶点颜色数据
GLES20.glVertexAttribPointer(
mColorHandle,
Cons.DIMENSION_4,
GLES20.GL_FLOAT,
false,
vertexColorStride,
mColorBuffer);
int count = mVertex.length / Cons.DIMENSION_3;
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, count);
}
}
NPC:很好,获取技能
GLES20.GL_POINTS
,勇者,继续展现你的创造力吧!
3.第三关卡:绘制四点
四点.pngprivate float[] mVertex = new float[]{
-1.0f, 0.0f, -1.0f,
-1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 0.0f, -1.0f,
};
private float[] mColor = new float[]{
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
};
经过ps的精确测量10px.png
张风捷特烈
在黑暗之渊
中踩在四个点上,停止了下落,经过测量,发现点的单位是px
副本十:萦龙之丝
1.第一关卡:坐标系体系
World.png接下来我们将使用以下视角进行世界的构建
现在的视角.png现在将D点变色:可见视角和坐标系不一致
private float[] mVertex = new float[]{
-1.0f, 0.0f, -1.0f,//A
-1.0f, 0.0f, 1.0f,//B
1.0f, 0.0f, 1.0f,//C
1.0f, 0.0f, -1.0f,//D
};
private float[] mColor = new float[]{
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
0.21960784f,0.56078434f,0.92156863f,1.0f,
};
2.第二关卡:调整视角,符合ps画的坐标系
旋转视角.png为了视觉上好些,也为了ps里画图方便,这里讲视角逆时针旋转130°
Matrix.setRotateM(mOpMatrix, 0, currDeg+130, 0, 1, 0);
点的旋转.gif
3.第三关卡:画线
画线直接把画点改成画线就行了,看一下GLES20几个常量的区别
GLES20.glLineWidth(10);//设置线的宽度
int count = mVertex.length / Cons.DIMENSION_3;
//GLES20.glDrawArrays(GLES20.GL_POINTS, 0, count);
//GLES20.glDrawArrays(GLES20.GL_LINES, 0, count);
//GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, count);
GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 0, count);
旋转线
为了使用方便,封装一下绘制简单图形的代码,就是把变量抽取一下
虽然只能画些简单的东西,但画画辅助线还是蛮方便的,一个SimpleShape
类
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/13/013:17:37<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:形状类
*/
public class Shape {
private float[] mVertex;//顶点
private float[] mColor;//颜色
private int mDrawType;//绘制类型
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/13/013:8:39<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:简单的形状
*/
public class SimpleShape extends RendererAble {
//略...
private Shape mShape;
public SimpleShape(Context context, Shape shape) {
super(context);
mShape = shape;
mColorBuffer = GLUtil.getFloatBuffer(mShape.getColor());
mVertexBuffer = GLUtil.getFloatBuffer(mShape.getVertex());
initProgram();
}
//略...
副本十一:The World
目的,形象地认识这个世界
1.第一关卡:坐标系的绘制
1.1:确定坐标和颜色(由于不怎么变动,所以放在常量类Cons里了)
世界坐标系.png记住三个轴的颜色(
Z轴:蓝色
,X轴:黄色
,Y轴:绿色
)
public static final float[] VERTEX_COO = {//坐标轴
0.0f, 0.0f, 0.0f,//Z轴
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f,//X轴
1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f,//Y轴
0.0f, 1.0f, 0.0f,
};
public static final float[] COLOR_COO = {//坐标轴颜色
0.0f, 0.0f, 1.0f, 1.0f,//Z轴:蓝色
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,//X轴:黄色
1.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,//Y轴:绿色
0.0f, 1.0f, 0.0f, 1.0f,
};
1.2:使用SimpleShape
---->[WorldRenderer#onSurfaceCreated]--------
Shape shape = new Shape(Cons.VERTEX_COO, Cons.COLOR_COO, GLES20.GL_LINES);
mCoo = new SimpleShape(mContext, shape);
---->[WorldRenderer#onDrawFrame]--------
mCoo.draw(mMVPMatrix);
world.gif
2.第二关卡:简单封装
简单封装.png如果图形创建在
WorldRenderer
中,感觉很不舒服,毕竟会有很多形状,
WorldRenderer
的本意只是为了渲染以及视角的控制,并不希望图形掺杂其中
WorldShape
可以专门绘制形状,由它统一向WorldRenderer输出形状
既然WorldShape
总管图形,那么操作图形,在所难免,建一个OP接口,目前只放两个方法
2.1:操作接口
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/13/013:19:27<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:操作接口
*/
public interface OP<T> {
/**
* 添加
* @param ts 若干对象
*/
void add(T... ts);
/**
* 根据id移除元素
* @param id 索引
*/
void remove(int id);
}
2.2:世界的形状:WorldShape
/**
* 作者:张风捷特烈<br/>
* 时间:2019/1/13/013:8:39<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:世界的形状
*/
public class WorldShape extends RendererAble implements OP<RendererAble>{
List<RendererAble> mRendererAbles;
private float[] mVertex = new float[]{
-1.0f, 0.0f, -1.0f,//A
-1.0f, 0.0f, 1.0f,//B
1.0f, 0.0f, 1.0f,//C
1.0f, 0.0f, -1.0f,//D
};
private float[] mColor = new float[]{
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
0.21960784f, 0.56078434f, 0.92156863f, 1.0f,
};
public WorldShape(Context ctx) {
super(ctx);
mRendererAbles = new ArrayList<>();
Shape coo = new Shape(Cons.VERTEX_COO, Cons.COLOR_COO, GLES20.GL_LINES);
Shape ground = new Shape(mVertex, mColor, GLES20.GL_LINE_LOOP);
add(
new SimpleShape(mContext,coo),
new SimpleShape(mContext,ground),
}
@Override
public void draw(float[] mvpMatrix) {
for (RendererAble rendererAble : mRendererAbles) {
rendererAble.draw(mvpMatrix);
}
}
@Override
public void add(RendererAble... rendererAbles) {
for (RendererAble rendererAble : rendererAbles) {
mRendererAbles.add(rendererAble);
}
}
@Override
public void remove(int id) {
if (id>=mRendererAbles.size()) {
return;
}
mRendererAbles.remove(id);
}
}
2.3:使用WorldShape
现在工作重心移入WorldShape,避免对WorldRenderer造成负担
---->[WorldRenderer#onSurfaceCreated]--------
mWorldShape = new WorldShape(mContext);
---->[WorldRenderer#onDrawFrame]--------
mWorldShape.draw(mMVPMatrix);
3.Shape的强化,移动与移动创建
关于深拷贝和浅拷贝我就不废话了,移动创建中需要深拷贝(成员变量有引用数据类型)
Shape implements Cloneable
3.1:深拷贝
/**
* 深拷贝
* @return 形状副本
*/
public Shape clone() {
Shape clone = null;
try {
clone = (Shape) super.clone();
float[] vertex = new float[mVertex.length];
float[] color = new float[mColor.length];
System.arraycopy(mVertex, 0, vertex, 0, mVertex.length);
System.arraycopy(mColor, 0, color, 0, mColor.length);
clone.mVertex = vertex;
clone.mColor = color;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
3.2:移动与移动拷贝
/**
* 移动并创建新图形
* @param x
* @param y
* @param z
* @return
*/
public Shape moveAndCreate(float x, float y, float z) {
Shape clone = clone();
clone.move(x, y, z);
return clone;
}
/**
* 仅移动图形
* @param x
* @param y
* @param z
*/
public void move(float x, float y, float z) {
for (int i = 0; i < mVertex.length; i++) {
if (i % 3 == 0) {//x
mVertex[i] += x;
}
if (i % 3 == 1) {//y
mVertex[i] += y;
}
if (i % 3 == 2) {//y
mVertex[i] += z;
}
}
}
3.3:移动创建图形
移动复制.gif两行代码搞定,我都佩服我自己,感觉可以用矩阵变换,现在还不是进击矩阵的时候
---->[WorldShape#WorldShape]------------
Shape coo = new Shape(Cons.VERTEX_COO, Cons.COLOR_COO, GLES20.GL_LINES);
Shape ground = new Shape(mVertex, mColor, GLES20.GL_LINE_LOOP);
Shape top = ground.moveAndCreate(0, 1, 0);
Shape bottom = ground.moveAndCreate(0, -1, 0);
add(
new SimpleShape(mContext,coo),
new SimpleShape(mContext,top),
new SimpleShape(mContext,bottom),
new SimpleShape(mContext,ground));
3.4:再加四根线(感觉有点low...)
private float[] mVertex2 = new float[]{
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
};
private float[] mColor2 = new float[]{
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
};
Shape side = new Shape(mVertex2, mColor2, GLES20.GL_LINES);
线立方.gif
世界的坐标已经映入眼帘,yes!
副本十二:黑龙之瞳LEVEL 2
在明确世界坐标之后,现在可以再来看一下视线了
相信你会觉得恍然大悟,原来如此,just so so
在此之前再说一遍:Z轴:蓝色
,X轴:黄色
,Y轴:绿色
,正对红线
1.第一关卡:移动相机 Z轴
视线点注意:
现在将视角转回(0,0,-6),旋转角度归0
为了不遮挡视线,将ground
四条线隐藏
看红线在后面,说明我们是从后面开始看的,Z轴:蓝色
无法看到,说明视点在Z轴
即:现在视点在Z轴上,值为-6,绝对值的大小即离物体的远近,近大远小没毛病
but,移到-8时,可见后面已经消失了,说明视野是有限制的
// 设置相机位置(视图矩阵)
Matrix.setLookAtM(mViewMatrix, 0,
0f, 0f, -6.0f,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
2.第二关卡:移动相机 X轴
x轴转将X每次向x负方向移动0.3f,想一下你拿着相机站在后面,看你的X轴方向
或者直接看黄线,黄线所指方向为X轴正方向,你应该可以感觉相机是怎么移动的吧!
// 设置相机位置(视图矩阵)
Matrix.setLookAtM(mViewMatrix, 0,
-1.5f, 0f, -6,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
3.第三关卡:移动相机 Y轴
y轴转.png将Y每次向Y负方向移动0.3f,想一下你拿着相机站在后面,看你的X轴方向
或者直接看黄线,黄线所指方向为X轴正方向,你应该可以感觉相机是怎么移动的吧!
// 设置相机位置(视图矩阵)
Matrix.setLookAtM(mViewMatrix, 0,
-1.5f, 1.5f, -6,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
操作.gifGLSurfaceView再怎么牛,也是个View,我们便可以添加事件
下面一个小练习,相信上面的理解了,对你来说不会太难
NPC:恭喜完成十二个
新手副本
,下面将进入普通副本
,祝君顺利
本集结束,下集--移形换影,敬请期待
后记:捷文规范
1.本文成长记录及勘误表
项目源码 | 日期 | 备注 |
---|---|---|
V0.1-github | 2018-1-14 | Android多媒体之GL-ES战记第三集--圣火之光 |
2.更多关于我
笔名 | 微信 | 爱好 | |
---|---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 | 语言 |
我的github | 我的简书 | 我的掘金 | 个人网站 |
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
icon_wx_200.png