OpenGL ES之一——概念扫盲
OpenGL ES之二——Android中的OpenGL ES概述
OpenGL ES之三——绘制纯色背景
OpenGL ES之四——绘制点,线,三角形
OpenGL ES之五——相机和投影,绘制等腰三角形
OpenGL ES之六——绘制矩形和圆形
OpenGL ES之七——着色器语言GLSL
OpenGL ES之八——GLES20类和Matrix类
OpenGL ES之九——相机和投影
OpenGL ES之十——纹理贴图(展示一张图片)
OpenGL ES之十一——绘制3D图形
OpenGL ES之十二——地球仪和VR图
上一篇中介绍了使用OpenGL ES的使用流程,完成了纯色背景的绘制。但是其中没有涉及具体几何形状所以并没有使用到顶点着色器和片段着色器
,这篇文章中将着重介绍
。
1.配置OpenGL ES环境:在AndroidManifest.xml文件中设置使用的OpenGL ES的版本:
2.GLSurfaceView作为展示View,图形的具体渲染工作都是在Render中完成。
3.实现GLSurfaceView的Render,在Render中完成点,线,三角形的绘制,具体行为有:
所以本文重点是:了解顶点着色器和片段着色器
AndroidManifest.xml文件中设置使用的OpenGL ES的版本
public class SimpleShapeActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_simple_shape);
initView();
}
private void initView() {
glSurfaceView = findViewById(R.id.simple_shape_gls);
glSurfaceView.setEGLContextClientVersion(3);
GLSurfaceView.Renderer renderer = new SimpleShapeRender();
glSurfaceView.setRenderer(renderer);
}
}
顶点着色器和片段着色器
。这里先不详细的介绍GLSL语言,只是介绍一下AndroidStudio中配置GLSL环境和简单的介绍涉及到的一些api,后面会单独介绍一些重要的特性和api
。想要在Android Studio中开发GLSL需要安装相应的插件,因为默认是不支持的,所以Android Studio中不支持关键字高亮和智能提示。下面看一下如何安装插件:
如下图,进入Settings页面后找到Plugins选项,在上方输入框中搜索GLSL,从而找到相应插件。如果从未安装过,那么右侧的Update按钮处会是一个Install按钮。我们点击Install安装,最后点击ok完成即可。
我们在开发时候一般将GLSL文件放在raw文件夹或者Assets文件夹中,这里放在raw中。在raw中新建一个GLSl文件如下,和生成一个其他文件一样,名称为GLSL Shader。这里分为顶点着色器,片段着色器,内容按照GLSL规则编写。
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
out vec4 vColor;
void main() {
gl_Position = vPosition;
gl_PointSize = 10.0;
vColor = aColor;
}
第一行:着色器的版本3.0,OpenGL ES 2.0版本可以不写。
第二行:输入一个名为vPosition的4分量向量,layout (location = 0)表示这个变量的位置是顶点属性0。
第三行:输入一个名为aColor的4分量向量,layout (location = 1)表示这个变量的位置是顶点属性1。
第四行:输出一个名为vColor的4分量向量,后面输入到片段着色器中。
第五行:main函数
第六行:gl_Position赋值为vPosition
第七行:gl_PointSize 绘制点的直径10
第八行:将输入数据aColor拷贝到vColor的变量中。
gl_Position,gl_PointSize为Shader内置变量,分别为顶点位置,点的直径。
#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor;
}
第一行:着色器的版本,OpenGL ES 2.0版本可以不写。
第二行:声明着色器中浮点变量的默认精度。
第三行: 声明一个输入名为vColor的4分向量,来自上面的顶点着色器。
第四行:着色器声明一个输出变量fragColor,这个是一个4分量的向量。
第五行:mian函数
第六行:表示将输入的颜色值数据拷贝到fragColor变量中,输出到颜色缓冲区。
//三个顶点的位置参数
private float triangleCoords[] = {
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
//三个顶点的颜色参数
private float color[] = {
1.0f, 0.0f, 0.0f, 1.0f,// top
0.0f, 1.0f, 0.0f, 1.0f,// bottom left
0.0f, 0.0f, 1.0f, 1.0f// bottom right
};
public SimpleShapeRender() {
//顶点位置相关
//分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
vertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
//顶点颜色相关
colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorBuffer.put(color);
colorBuffer.position(0);
}
/**
* 编译
*
* @param type 顶点着色器:GLES30.GL_VERTEX_SHADER
* 片段着色器:GLES30.GL_FRAGMENT_SHADER
* @param shaderCode 着色器语言编写的着色器程序
* @return
*/
private static int compileShader(int type, String shaderCode) {
//创建一个着色器
final int shaderId = GLES30.glCreateShader(type);
if (shaderId != 0) {
GLES30.glShaderSource(shaderId, shaderCode);
GLES30.glCompileShader(shaderId);
//检测状态
final int[] compileStatus = new int[1];
GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0) {
String logInfo = GLES30.glGetShaderInfoLog(shaderId);
System.err.println(logInfo);
//创建失败
GLES30.glDeleteShader(shaderId);
return 0;
}
return shaderId;
} else {
//创建失败
return 0;
}
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//将背景设置为白色
GLES20.glClearColor(1.0f,1.0f,1.0f,1.0f);
//编译顶点着色程序
String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
//编译片段着色程序
String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
//连接程序
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
//在OpenGLES环境中使用程序
GLES30.glUseProgram(mProgram);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置绘制窗口
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//把颜色缓冲区设置为我们预设的颜色
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//准备坐标数据
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//启用顶点位置句柄
GLES30.glEnableVertexAttribArray(0);
//准备颜色数据
GLES30.glVertexAttribPointer(1, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
//启用顶点颜色句柄
GLES30.glEnableVertexAttribArray(1);
//绘制三个点
//GLES30.glDrawArrays(GLES30.GL_POINTS, 0, POSITION_COMPONENT_COUNT);
//绘制三条线
GLES30.glLineWidth(3);//设置线宽
GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, POSITION_COMPONENT_COUNT);
//绘制三角形
//GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
//禁止顶点数组的句柄
GLES30.glDisableVertexAttribArray(0);
GLES30.glDisableVertexAttribArray(1);
}
下面是涉及到的完整类
public class SimpleShapeRender implements GLSurfaceView.Renderer {
//一个Float占用4Byte
private static final int BYTES_PER_FLOAT = 4;
//三个顶点
private static final int POSITION_COMPONENT_COUNT = 3;
//顶点位置缓存
private final FloatBuffer vertexBuffer;
//顶点颜色缓存
private final FloatBuffer colorBuffer;
//渲染程序
private int mProgram;
//三个顶点的位置参数
private float triangleCoords[] = {
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
//三个顶点的颜色参数
private float color[] = {
1.0f, 0.0f, 0.0f, 1.0f,// top
0.0f, 1.0f, 0.0f, 1.0f,// bottom left
0.0f, 0.0f, 1.0f, 1.0f// bottom right
};
public SimpleShapeRender() {
//顶点位置相关
//分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
vertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
//顶点颜色相关
colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorBuffer.put(color);
colorBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//将背景设置为白色
GLES20.glClearColor(1.0f,1.0f,1.0f,1.0f);
//编译顶点着色程序
String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
//编译片段着色程序
String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
//连接程序
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
//在OpenGLES环境中使用程序
GLES30.glUseProgram(mProgram);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置绘制窗口
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//把颜色缓冲区设置为我们预设的颜色
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//准备坐标数据
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//启用顶点位置句柄
GLES30.glEnableVertexAttribArray(0);
//准备颜色数据
GLES30.glVertexAttribPointer(1, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
//启用顶点颜色句柄
GLES30.glEnableVertexAttribArray(1);
//绘制三个点
//GLES30.glDrawArrays(GLES30.GL_POINTS, 0, POSITION_COMPONENT_COUNT);
//绘制三条线
GLES30.glLineWidth(3);//设置线宽
GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, POSITION_COMPONENT_COUNT);
//绘制三角形
//GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
//禁止顶点数组的句柄
GLES30.glDisableVertexAttribArray(0);
GLES30.glDisableVertexAttribArray(1);
}
}
public class ShaderUtils {
private static final String TAG = "ShaderUtils";
/**
* 编译顶点着色器
* @param shaderCode
*/
public static int compileVertexShader(String shaderCode) {
return compileShader(GLES30.GL_VERTEX_SHADER, shaderCode);
}
/**
* 编译片段着色器
* @param shaderCode
*/
public static int compileFragmentShader(String shaderCode) {
return compileShader(GLES30.GL_FRAGMENT_SHADER, shaderCode);
}
/**
* 编译
* @param type 顶点着色器:GLES30.GL_VERTEX_SHADER
* 片段着色器:GLES30.GL_FRAGMENT_SHADER
* @param shaderCode
*/
private static int compileShader(int type, String shaderCode) {
//创建一个着色器
final int shaderId = GLES30.glCreateShader(type);
if (shaderId != 0) {
GLES30.glShaderSource(shaderId, shaderCode);
GLES30.glCompileShader(shaderId);
//检测状态
final int[] compileStatus = new int[1];
GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0) {
String logInfo = GLES30.glGetShaderInfoLog(shaderId);
System.err.println(logInfo);
//创建失败
GLES30.glDeleteShader(shaderId);
return 0;
}
return shaderId;
} else {
//创建失败
return 0;
}
}
/**
* 链接小程序
* @param vertexShaderId 顶点着色器
* @param fragmentShaderId 片段着色器
*/
public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
//创建一个空的OpenGLES程序
final int programId = GLES30.glCreateProgram();
if (programId != 0) {
//将顶点着色器加入到程序
GLES30.glAttachShader(programId, vertexShaderId);
//将片元着色器加入到程序中
GLES30.glAttachShader(programId, fragmentShaderId);
//链接着色器程序
GLES30.glLinkProgram(programId);
final int[] linkStatus = new int[1];
GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] == 0) {
String logInfo = GLES30.glGetProgramInfoLog(programId);
System.err.println(logInfo);
GLES30.glDeleteProgram(programId);
return 0;
}
return programId;
} else {
//创建失败
return 0;
}
}
/**
* 验证程序片段是否有效
* @param programObjectId
*/
public static boolean validProgram(int programObjectId) {
GLES30.glValidateProgram(programObjectId);
final int[] programStatus = new int[1];
GLES30.glGetProgramiv(programObjectId, GLES30.GL_VALIDATE_STATUS, programStatus, 0);
return programStatus[0] != 0;
}
}
public class ResReadUtils {
/**
* 读取资源
* @param resourceId
*/
public static String readResource(int resourceId) {
StringBuilder builder = new StringBuilder();
try {
InputStream inputStream = App.getInstance().getResources().openRawResource(resourceId);
InputStreamReader streamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(streamReader);
String textLine;
while ((textLine = bufferedReader.readLine()) != null) {
builder.append(textLine);
builder.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
return builder.toString();
}
}
着重说明一个地方
在onDrawFrame中的glVertexAttribPointer和glEnableVertexAttribArray中参数比较不好理解,下面着重说一下。
//准备坐标数据
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//启用顶点位置句柄
GLES30.glEnableVertexAttribArray(0);
//准备颜色数据
GLES30.glVertexAttribPointer(1, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
//启用顶点颜色句柄
GLES30.glEnableVertexAttribArray(1);
public static void glVertexAttribPointer(
int indx,
int size,
int type,
boolean normalized,
int stride,
java.nio.Buffer ptr
)
layout (location = 0) in vec4 vPosition; layout (location = 1) in vec4 aColor;
我们会发现按照顶点数据如下
//三个顶点的位置参数
private float triangleCoords[] = {
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
理应绘制成的是等腰直角三角形啊,但是现在怎么不是。这个和第一篇OpenGL ES文中提到的“使用的是虚拟坐标”有关。
同时我们的手机如果横过来的话,以三角形为例会变成如下的样子。
我们发现形状和竖屏的时候完全不同了(如果是三个点,或者是绘制的三条线也会出项相应的变化,因为三个顶点的位置在不同屏幕状态下“发生了变化”)。要想解决上面的问题就要了解坐标系,投影等知识放在下一篇中详细介绍。