文章目录
- 案例一 绘制点线面
- 定义Renderer
- 顶点着色器
- 片段着色器
- 内置的特殊变量
- 应用场景
- 工具ShaderHelper
- 工具 TextResourceReader
- 效果图如下
- 结论
案例一 绘制点线面
定义Renderer
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
import com.guide.opengllib.R;
import com.guide.opengllib.simple.ShaderHelper;
import com.guide.opengllib.simple.TextResourceReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_LINES;
import static android.opengl.GLES20.GL_POINTS;
import static android.opengl.GLES20.GL_TRIANGLE_FAN;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
/**
*/
public class CRenderer implements GLSurfaceView.Renderer {
private String TAG = "Qm";
//缓冲区
private final FloatBuffer vertexData;
//上下文
private Context context;
//float buffer大小
private static final int BYTES_PER_FLOAT = 4;
//程序
private int program;
//颜色着色器引用
private int aColorLocation;
//顶点着色器引用
private int aPositionLocation;
//glsl 颜色着色器坐标
private static final String A_COLOR = "a_Color";
//glsl 顶点着色器坐标
private static final String A_POSITION = "a_Position";
//顶点着色器 每行点个数
private static final int POSITION_COMOPNENT_COUNT = 2;
//颜色着色器 每行颜色个数
private static final int COLOR_COMPONENT_CONT = 3;
//每行间隔
private static final int STRIDE = (POSITION_COMOPNENT_COUNT + COLOR_COMPONENT_CONT) *
BYTES_PER_FLOAT;
/**
* 1.添加坐标点; OpenGl中,只能绘制点,直线和三角形
* 在定义三角形时,我们总是以逆时针的顺序排列顶点,这称为卷曲顺序。
*/
float[] tableVertices = {
// Order of coordinates: X, Y, R, G, B
// Triangle Fan
0f, 0f, 1f, 1f, 1f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
// Line 1
-0.5f, 0f, 1f, 0f, 0f,
0.5f, 0f, 1f, 0f, 0f,
// Mallets
0f, -0.25f, 0f, 0f, 1f,
0f, 0.25f, 1f, 0f, 0f
};
public CRenderer(Context context) {
this.context = context;
//ByteBuffer.allocateDirect 分配一块内存;参数是分配多少字节的内存块。
//order 季节缓冲区按照本地字节序组织它的内容。
vertexData = ByteBuffer.allocateDirect(tableVertices.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(tableVertices);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.w(TAG, "onSurfaceCreated");
//设置背景清除颜色为红色。
//第一个分量是红色的,第二个是绿色的,第三个是蓝色的,最后一个分量是alpha。
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//1.读取顶点着色器
String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.c_vertex_shader);
//读取片段着色器
String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.c_fragment_shader);
Log.w(TAG, "onSurfaceCreated,第1步");
//2.创建顶点着色器
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
//创建片段着色器
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
Log.w(TAG, "onSurfaceCreated,第2步");
//3.连接两个着色器
program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
Log.w(TAG, "onSurfaceCreated,第3步");
//4.验证该对象 program 是否可用
ShaderHelper.validateProgram(program);
Log.w(TAG, "onSurfaceCreated,第4步");
//5.使用自定义的程序来绘制
GLES20.glUseProgram(program);
/**
* 6.获取着色器并且为着色器赋值
*/
aColorLocation = glGetAttribLocation(program, A_COLOR);
//获取顶点着色器 a_Position 对象的指针
aPositionLocation = glGetAttribLocation(program, A_POSITION);
Log.w(TAG, "onSurfaceCreated,第5步");
/**
* 6.为着色器赋值
*/
vertexData.position(0);//将位置设置在数据的开头处
glVertexAttribPointer(aPositionLocation, POSITION_COMOPNENT_COUNT,
GL_FLOAT, false, STRIDE, vertexData);
//7.指定OpenGL在哪使用顶点数组
glEnableVertexAttribArray(aPositionLocation);
Log.w(TAG, "onSurfaceCreated,第6步");
/**
* 注释
* 1.vertexData.position
* vertexData将位置设置为2,因为读取颜色属性时,要从第一个颜色属性读取,而不是位置属性,
* 而在原数据中,颜色属性的位置是2
* 2.glVertexAttribPointer
* 把颜色数据和着色器中的a_Color关联起来,STRIDE这个参数是跨距,这个值告诉OpenGL两个
* 颜色属性质检的距离是多少,这样位置属性和颜色属性连接存储,就不会将位置属性当做颜色属性读
* 3.glEnableVertexAttribArray
* 指定OpenGL在哪使用顶点数组
*/
vertexData.position(POSITION_COMOPNENT_COUNT);
glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_CONT, GL_FLOAT,
false, STRIDE, vertexData);
glEnableVertexAttribArray(aColorLocation);
Log.w(TAG, "onSurfaceCreated,第7步");
}
/**
* 当表面发生变化时,onSurfaceChanged被调用。
* 这个函数在曲面初始化时至少被调用一次。
* 请记住,Android通常会在旋转时重启一个活动,在这种情况下,渲染器将被销毁并创建一个新的。
*
* @param gl
* @param width 新的宽度,以像素为单位。
* @param height 新的高度,以像素为单位。
*/
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.w(TAG, "onSurfaceChanged");
//设置OpenGL视口填充整个表面。
glViewport(0, 0, width, height);
}
/**
* 每当需要绘制一个新帧时,OnDrawFrame就会被调用。通常,这是在屏幕的刷新率下完成的。
*
* @param gl
*/
@Override
public void onDrawFrame(GL10 gl) {
//清除渲染表面。
glClear(GL_COLOR_BUFFER_BIT);
/**
* 8.绘制页面,画桌子,画线,点;
* glUniform4f:第一个参数指定Uniform变量的值,后面是红绿蓝和透明值
* glDrawArrays:第一个参数绘制三角形,第二个参数从数组开头开始读取顶点,第三个参数读到第六个点
*/
glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
//绘制线
glDrawArrays(GL_LINES, 6, 2);
//画点
glDrawArrays(GL_POINTS, 8, 1);
glDrawArrays(GL_POINTS, 9, 1);
}
}
顶点着色器
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main(){
v_Color = a_Color;
gl_Position = a_Position;
gl_PointSize = 10.0;
}
片段着色器
precision mediump float;
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
以下为glsl语法中摘录的一部分,可以发现gl_FragColor gl_Position gl_PointSize 均为内部变量,且必须赋值
内置的特殊变量
glsl程序使用一些特殊的内置变量与硬件进行沟通.他们大致分成两种 一种是 input
类型,他负责向硬件(渲染管线)发送数据.
另一种是output
类型,负责向程序回传数据,以便编程时需要.
在 vertex Shader 中:
output 类型的内置变量:
变量 | 说明 | 单位 |
---|---|---|
highp vec4 gl_Position ; | gl_Position 放置顶点坐标信息 | vec4 |
mediump float gl_PointSize ; | gl_PointSize 需要绘制点的大小,(只在gl.POINTS模式下有效) | float |
在 fragment Shader 中:
input 类型的内置变量:
变量 | 说明 | 单位 |
---|---|---|
mediump vec4 gl_FragCoord ; | 片元在framebuffer画面的相对位置 | vec4 |
bool gl_FrontFacing ; | 标志当前图元是不是正面图元的一部分 | bool |
mediump vec2 gl_PointCoord ; | 经过插值计算后的纹理坐标,点的范围是0.0到1.0 | vec2 |
应用场景
import android.annotation.SuppressLint;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.guide.opengllib.simple.c.CRenderer;
public class Simplectivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
private GLSurfaceView.Renderer mRenderer;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glSurfaceView = new GLSurfaceView(this);
mRenderer = new CRenderer(this);
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setRenderer(mRenderer);
setContentView(glSurfaceView);
}
@Override
protected void onResume() {
super.onResume();
glSurfaceView.onResume();
}
@Override
protected void onPause() {
super.onPause();
glSurfaceView.onPause();
}
}
工具ShaderHelper
import android.opengl.GLES20;
import android.util.Log;
import static android.opengl.GLES20.GL_COMPILE_STATUS;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_LINK_STATUS;
import static android.opengl.GLES20.GL_VALIDATE_STATUS;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glDeleteProgram;
import static android.opengl.GLES20.glDeleteShader;
import static android.opengl.GLES20.glGetProgramiv;
import static android.opengl.GLES20.glGetShaderiv;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glValidateProgram;
/**
*/
public class ShaderHelper {
private static final String TAG = "ShaderHelper";
/** 加载和编译一个顶点着色器,返回OpenGL对象ID */
public static int compileVertexShader(String shaderCode) {
return compileShader(GL_VERTEX_SHADER, shaderCode);
}
/** 加载和编译一个片段着色器,返回OpenGL对象ID */
public static int compileFragmentShader(String shaderCode) {
return compileShader(GL_FRAGMENT_SHADER, shaderCode);
}
/** 编译一个着色器,返回OpenGL对象ID */
private static int compileShader(int type, String shaderCode) {
//创建一个新的着色器对象
final int shaderObjectId = glCreateShader(type);
if (shaderObjectId == 0) {
Log.w(TAG, "ShaderHelper: Could not create new shader.");
}
//传入着色器源
glShaderSource(shaderObjectId, shaderCode);
//编译着色器
glCompileShader(shaderObjectId);
//获取编译状态
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
//验证编译状态
if (compileStatus[0] == 0) {
//如果失败,删除着色器对象
glDeleteShader(shaderObjectId);
Log.w(TAG, "ShaderHelper: Compilation of shader failed");
}
//返回着色器对象ID
return shaderObjectId;
}
/** 将顶点着色器和片段着色器连接到OpenGL中,返回OpenGL对象ID,连接失败返回0 */
public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
//创建一个新的程序对象
final int programObjectId = glCreateProgram();
if (programObjectId == 0) {
Log.w(TAG, "ShaderHelper: Could not create new program");
}
//将顶点着色器附加到程序上
glAttachShader(programObjectId, vertexShaderId);
//将片段着色器附加到程序上
glAttachShader(programObjectId, fragmentShaderId);
//将两个着色器连接到一个程序中
glLinkProgram(programObjectId);
//获取连接状态
final int[] linkStatus = new int[1];
glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
//验证链接状态
if (linkStatus[0] == 0) {
//如果失败,删除程序对象
glDeleteProgram(programObjectId);
Log.v(TAG, "Results of linking program:\n" + GLES20.glGetProgramInfoLog(programObjectId));
}
//返回程序对象ID
return programObjectId;
}
/** 验证OpenGL程序,应该只在开始应用程序调用 */
public static boolean validateProgram(int programObjectId) {
glValidateProgram(programObjectId);
final int[] validateStatus = new int[1];
glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0);
return validateStatus[0] != 0;
}
public static int buildProgram(String vertexShaderSource, String fragmentShaderSource) {
int program;
int vertexShader = compileVertexShader(vertexShaderSource);
int fragmentShader = compileFragmentShader(fragmentShaderSource);
program = linkProgram(vertexShader, fragmentShader);
validateProgram(program);
return program;
}
}
工具 TextResourceReader
public class TextResourceReader {
public static String readTextFileFromResource(Context context, int resourceId) {
StringBuilder body = new StringBuilder();
try {
InputStream inputStream = context.getResources().openRawResource(resourceId);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String nextLine;
while ((nextLine = bufferedReader.readLine()) != null) {
body.append(nextLine);
body.append('\n');
}
} catch (IOException e) {
throw new RuntimeException("Could not open resource:" + resourceId, e);
} catch (Resources.NotFoundException nfe) {
throw new RuntimeException("Resource not found :" + resourceId, nfe);
}
return body.toString();
}
}
效果图如下
glDrawArrays(GL_TRIANGLE_FAN, 0,6);
我们不妨尝试将第三个参数修改,帮助我们理解gl的绘制原理
glDrawArrays(GL_TRIANGLE_FAN, 0, 1);
glDrawArrays(GL_TRIANGLE_FAN, 0, 2);
glDrawArrays(GL_TRIANGLE_FAN, 0, 3);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDrawArrays(GL_TRIANGLE_FAN, 0, 5);
结论
PS
1. GL_TRIANGLE_FAN绘制最少三个点
2. 绘制时为逆时针方向
3. 过程:读取顶点数据->执行顶点着色器->组装图元->光栅化图元->执行片段着色器->写入帧缓冲区->显示到屏幕;
4. 关于语法可参见 OpenGL手册