OpenGL绘制不同形状需要先掌握OpenGL的坐标系,以及要清除shader的使用,这里我会以绘制三角形、四边形、圆形为例,来介绍OpenGL对形状的绘制
首先我们来了解一下,绘制形状的基本步骤:
- 创建shader(着色器:顶点或片元)
- 加载shader源码并编译shader
- 检查是否编译成功
- 创建一个渲染程序
- 将着色器程序添加到渲染程序中
- 链接源程序
- 检查链接源程序是否成功
- 得到着色器中的属性
- 使用源程序
- 使顶点属性数组有效
- 为顶点属性赋值
- 绘制图形
知道了基本步骤,我们就可以分步来实现。先看下效果:
1.基础知识
1.1 OpenGL着色器
OpenGL着色器Shader由glsl语言编写,我们可以通过在Java中传入参数来改变openGL的绘制细节。
以下为顶点着色器:
attribute vec4 vPosition; //4个方向变量的位置向量(xyzw)
attribute vec2 fPosition; //2个方向变量的位置向量(xy)
void main(){
//赋值,gl_Position为固定写法
gl_Position = vPosition;
}
以下为片元着色器:
precision mediump float; //中等精度
uniform vec4 f_Color; //4个变量的颜色向量(argb),可以从java层用uniform传入
void main() {
//赋值,gl_FragColor为固定的写法
gl_FragColor = f_Color;
}
1.2 OpenGL坐标系
如上所说,OpenGL着色器有顶点着色器、片元着色器,而它们对应了OpenGL的顶点坐标系、片元坐标系,绘制形状时需要了解OpenGL的坐标系是怎样的,其坐标系如下图所示:
从图中可以看出,OpenGL的顶点坐标系为归1化了的坐标系,也就是说不管显示的屏幕的大小是多大或多小,OpenGL都把屏幕的大小整成了-1到1之间;片元坐标系为0到1之间。
1.3 顶点内存数组和片元内存数组
首先,什么是顶点内存数组和片元内存数组,顶点内存数组和片元内存数组就是我们从java层传入的,如:
//顶点坐标
float[] vertexData = {
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
//纹理坐标
float[] fragmentData = {
0f, 1f,
1f, 1f,
0f, 0f,
1f, 0f
};
其次,怎么加载顶点内存数组和片元内存数组里的坐标,如下:
//读取顶点坐标
FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
vertexBuffer.position(0);
//读取纹理坐标
FloatBuffer fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(fragmentData);
fragmentBuffer.position(0);
然而,为什么要用allocateDirect,将顶点、片元的坐标存入jvm之外?
这样做,我的理解是在每次的绘制中,OpenGL都需要拿到顶点、片元的坐标,保存在JVM之外可以防止数据被java GC掉。
最后,怎么使用顶点坐标、片元坐标,如下伪代码:
//onSurfaceCreated中加载:
//加载顶点着色器 shader
String vertexSource = YShaderUtil.getRawResource(mContext, R.raw.screen_vert);
//加载片元着色器 shader
String fragmentSource = YShaderUtil.getRawResource(mContext, R.raw.screen_frag);
//获取源程序
program = YShaderUtil.createProgram(vertexSource, fragmentSource);
//从渲染程序中得到着顶点色器中的属性
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//从渲染程序中得到片元着色器中的属性
fPosition = GLES20.glGetAttribLocation(program, "fPosition");
//onDrawFrame中使能:
//使能顶点属性数组,使之有效
GLES20.glEnableVertexAttribArray(vPosition);
//使能之后,为顶点属性赋值,绑定顶点坐标
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
//使能片元属性数组,使之有效
GLES20.glEnableVertexAttribArray(fPosition);
//使能之后,为片元属性赋值,绑定纹理坐标
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, fragmentBuffer);
其中 vPosition、fPosition是从源程序(shader经过加载、编译、链接得到的源程序)中获取的,绑定之后就可以进行相应的绘制了。
1.4 Shader的加载
Shader需要编译成源程序后才可以获取到其中的变量值,然后才能被使能。以下是以代码加注释的方式对Shader的加载进行讲解:
/**
* 获取 着色器 源程序
* @param vertexSource 顶点着色器 内容
* @param fragmentSource 片元着色器 内容
* @return 着色器 源程序
*/
public static int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (vertexShader != 0 && fragmentShader != 0) {
//创建一个渲染程序 program
int program = GLES20.glCreateProgram();
//将顶点着色器程序添加到渲染程序中
GLES20.glAttachShader(program, vertexShader);
//将片元着色器程序也添加到渲染程序中
GLES20.glAttachShader(program, fragmentShader);
//链接源程序
GLES20.glLinkProgram(program);
return program;
}
return 0;
}
1.5 Shader源程序的使用–绘制
//使用着色器源程序
GLES20.glUseProgram(program);
//绘制绿色
GLES20.glUniform4f(fPosition, 1f, 0f, 1f, 1f);
//使能顶点属性数组,使之有效
GLES20.glEnableVertexAttribArray(vPosition);
//使能之后,为顶点属性赋值,绑定顶点坐标
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
//绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);//三角形,三个点
2.OpenGL绘制三角形
package com.york.media.opengl.demo.shape;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import com.york.media.opengl.R;
import com.york.media.opengl.egl.YGLSurfaceView;
import com.york.media.opengl.egl.YShaderUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* author : York
* date : 2020/12/20 15:01
* desc : 绘制三角形的 Render
*/
public class YTriangleRender implements YGLSurfaceView.YGLRender {
private final Context mContext;
private FloatBuffer vertexBuffer;
private int program;
private int vPosition;
private int fPosition;
public YTriangleRender(Context mContext) {
this.mContext = mContext;
//顶点坐标
float[] vertexData = {
0f, 1f,
-1f, -1f,
1f, -1f
};
//读取顶点坐标
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
vertexBuffer.position(0);
}
@Override
public void onSurfaceCreated() {
//加载顶点着色器 shader
String vertexSource = YShaderUtil.getRawResource(mContext, R.raw.screen_vert);
//加载片元着色器 shader
String fragmentSource = YShaderUtil.getRawResource(mContext, R.raw.screen_frag_color);
//获取源程序
program = YShaderUtil.createProgram(vertexSource, fragmentSource);
//从渲染程序中得到着顶点色器中的属性
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//从渲染程序中得到片元着色器中的属性
fPosition = GLES20.glGetUniformLocation(program, "f_Color");
}
@Override
public void onSurfaceChanged(int width, int height) {
//设置窗口大小
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame() {
//清除屏幕,此处用的是红色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(0f, 1f, 0f, 1f);
//使用着色器源程序
GLES20.glUseProgram(program);
//绘制绿色
GLES20.glUniform4f(fPosition, 1f, 0f, 1f, 1f);
//使能顶点属性数组,使之有效
GLES20.glEnableVertexAttribArray(vPosition);
//使能之后,为顶点属性赋值,绑定顶点坐标
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
//绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);//三角形
}
}
3.OpenGL绘制四边形
package com.york.media.opengl.demo.shape;
import android.content.Context;
import android.opengl.GLES20;
import com.york.media.opengl.R;
import com.york.media.opengl.egl.YGLSurfaceView;
import com.york.media.opengl.egl.YShaderUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* author : York
* date : 2020/12/20 15:43
* desc : 绘制四边形的 Render
*/
public class YQuadrilateralRender implements YGLSurfaceView.YGLRender {
private final Context mContext;
private final FloatBuffer vertexBuffer;
private int program;
private int vPosition;
private int fPosition;
public YQuadrilateralRender(Context mContext) {
this.mContext = mContext;
//顶点坐标
float[] vertexData = {
-1f, 0f,
0f, 1f,
0f,-1f,
1f, 0f
};
//读取顶点坐标
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
vertexBuffer.position(0);
}
@Override
public void onSurfaceCreated() {
//加载顶点着色器 shader
String vertexSource = YShaderUtil.getRawResource(mContext, R.raw.screen_vert);
//加载片元着色器 shader
String fragmentSource = YShaderUtil.getRawResource(mContext, R.raw.screen_frag_color);
//获取源程序
program = YShaderUtil.createProgram(vertexSource, fragmentSource);
//从渲染程序中得到着顶点色器中的属性
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//从渲染程序中得到片元着色器中的属性
fPosition = GLES20.glGetUniformLocation(program, "f_Color");
}
@Override
public void onSurfaceChanged(int width, int height) {
//设置窗口大小
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame() {
//清除屏幕,此处用的是红色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(1f,0f, 0f, 1f);
//使用着色器源程序
GLES20.glUseProgram(program);
//绘制绿色
GLES20.glUniform4f(fPosition,0f,1f,1f,1f);
//使能顶点属性数组,使之有效
GLES20.glEnableVertexAttribArray(vPosition);
//使能之后,为顶点属性赋值,绑定顶点坐标
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
//绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);//四边形
}
}
3.OpenGL绘制圆形
package com.york.media.opengl.demo.shape;
import android.content.Context;
import android.opengl.GLES20;
import com.york.media.opengl.R;
import com.york.media.opengl.egl.YGLSurfaceView;
import com.york.media.opengl.egl.YShaderUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* author : York
* date : 2020/12/20 16:33
* desc : 绘制圆形
*/
public class YCircularRender implements YGLSurfaceView.YGLRender {
private final Context mContext;
private final FloatBuffer vertexBuffer;
private int program;
private int vPosition;
private int fPosition;
public YCircularRender(Context mContext) {
this.mContext = mContext;
//顶点坐标
float[] vertexData = new float[720];
for (int i = 0; i < 720; i += 2) {
// x value
vertexData[i] = (float) Math.cos(i);
// y value
vertexData[i + 1] = (float) Math.sin(i);
}
//读取顶点坐标
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
vertexBuffer.position(0);
}
@Override
public void onSurfaceCreated() {
//加载顶点着色器 shader
String vertexSource = YShaderUtil.getRawResource(mContext, R.raw.screen_vert);
//加载片元着色器 shader
String fragmentSource = YShaderUtil.getRawResource(mContext, R.raw.screen_frag_color);
//获取源程序
program = YShaderUtil.createProgram(vertexSource, fragmentSource);
//从渲染程序中得到着顶点色器中的属性
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//从渲染程序中得到片元着色器中的属性
fPosition = GLES20.glGetUniformLocation(program, "f_Color");
}
@Override
public void onSurfaceChanged(int width, int height) {
//设置窗口大小
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame() {
//清除屏幕,此处用的是红色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(0f, 0f, 1f, 1f);
//使用着色器源程序
GLES20.glUseProgram(program);
//绘制绿色
GLES20.glUniform4f(fPosition, .5f, .5f, 1f, 1f);
//使能顶点属性数组,使之有效
GLES20.glEnableVertexAttribArray(vPosition);
//使能之后,为顶点属性赋值,绑定顶点坐标
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
//绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 360);//圆形
}
}
效果是这样的: