大家好,接下来将为大家介绍OpenGL ES 3. 绘制球体。
OpenGL ES 中任何形状的 3D 物体都是用三角形而组成的, 因此,构建曲面物体最重要的就是找到将曲面恰当划分成三角形的策略。最基本的策略是首先按照一定的规则将物体按行和列两个方向进行划分,这时就可以得到很多的小四边形。然后再将每个小四边形划分成两个三角形即可。
球面首先被按照纬度 (行)和经度(列)的方向划分成了很多的小四边形,每个小四边形又被划分成两个小三角形。这种划分方式下,三角形中每个顶点的坐标都可以用几何的公式方便地计算出来,具体情况如下。
x = R * cos(a) * cos(b) ;y = R * cos(a) * sin(b) ;z = R * sin(a) 。
上述给出的是当球的半径为 R,在经度为a,纬度为b处球面上顶点坐标的计算公式。
1、按照切分规则生成球面上顶点的坐标,并渲染球体的 Ball 类。
initVertexData方法用于初始化球体的顶点数据,initShader方法用于创建着色器对象:
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.opengl.GLES30;
import android.os.Build;
public class Ball {
int mProgram;// 自定义渲染管线着色器程序id
int muMVPMatrixHandle;//总变换矩阵引用
int maPositionHandle; //顶点位置属性引用
int muRHandle;//球的半径属性引用
String mVertexShader;//顶点着色器代码脚本
String mFragmentShader;//片元着色器代码脚本
FloatBuffer mVertexBuffer;// 顶点坐标数据缓冲
int vCount = 0;
float yAngle = 0;// 绕y轴旋转的角度
float xAngle = 0;// 绕x轴旋转的角度
float zAngle = 0;// 绕z轴旋转的角度
float r = 0.8f;
public Ball(MySurfaceView mv) {
// 初始化顶点数据的方法
initVertexData();
// 初始化着色器的方法
initShader(mv);
}
// 初始化顶点数据的方法
public void initVertexData() {
// 顶点坐标数据的初始化================begin============================
ArrayList<Float> alVertix = new ArrayList<Float>();// 存放顶点坐标的ArrayList
final int angleSpan = 10;// 将球进行单位切分的角度
for (int vAngle = -90; vAngle < 90; vAngle = vAngle + angleSpan)// 垂直方向angleSpan度一份
{
for (int hAngle = 0; hAngle <= 360; hAngle = hAngle + angleSpan)// 水平方向angleSpan度一份
{// 纵向横向各到一个角度后计算对应的此点在球面上的坐标
float x0 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle)) * Math.cos(Math
.toRadians(hAngle)));
float y0 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle)) * Math.sin(Math
.toRadians(hAngle)));
float z0 = (float) (r * UNIT_SIZE * Math.sin(Math
.toRadians(vAngle)));
float x1 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle)) * Math.cos(Math
.toRadians(hAngle + angleSpan)));
float y1 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle)) * Math.sin(Math
.toRadians(hAngle + angleSpan)));
float z1 = (float) (r * UNIT_SIZE * Math.sin(Math
.toRadians(vAngle)));
float x2 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle + angleSpan)) * Math
.cos(Math.toRadians(hAngle + angleSpan)));
float y2 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle + angleSpan)) * Math
.sin(Math.toRadians(hAngle + angleSpan)));
float z2 = (float) (r * UNIT_SIZE * Math.sin(Math
.toRadians(vAngle + angleSpan)));
float x3 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle + angleSpan)) * Math
.cos(Math.toRadians(hAngle)));
float y3 = (float) (r * UNIT_SIZE
* Math.cos(Math.toRadians(vAngle + angleSpan)) * Math
.sin(Math.toRadians(hAngle)));
float z3 = (float) (r * UNIT_SIZE * Math.sin(Math
.toRadians(vAngle + angleSpan)));
// 将计算出来的XYZ坐标加入存放顶点坐标的ArrayList
alVertix.add(x1);
alVertix.add(y1);
alVertix.add(z1);
alVertix.add(x3);
alVertix.add(y3);
alVertix.add(z3);
alVertix.add(x0);
alVertix.add(y0);
alVertix.add(z0);
alVertix.add(x1);
alVertix.add(y1);
alVertix.add(z1);
alVertix.add(x2);
alVertix.add(y2);
alVertix.add(z2);
alVertix.add(x3);
alVertix.add(y3);
alVertix.add(z3);
}
}
vCount = alVertix.size() / 3;// 顶点的数量为坐标值数量的1/3,因为一个顶点有3个坐标
// 将alVertix中的坐标值转存到一个float数组中
float vertices[] = new float[vCount * 3];
for (int i = 0; i < alVertix.size(); i++) {
vertices[i] = alVertix.get(i);
}
// 创建顶点坐标数据缓冲
// vertices.length*4是因为一个整数四个字节
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());// 设置字节顺序
mVertexBuffer = vbb.asFloatBuffer();// 转换为float型缓冲
mVertexBuffer.put(vertices);// 向缓冲区中放入顶点坐标数据
mVertexBuffer.position(0);// 设置缓冲区起始位置
// 特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
// 转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题
}
// 初始化着色器
public void initShader(MySurfaceView mv) {
// 加载顶点着色器的脚本内容
mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh",mv.getResources());
// 加载片元着色器的脚本内容
mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh",mv.getResources());
// 基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
// 获取程序中顶点位置属性引用
maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
// 获取程序中总变换矩阵引用
muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
// 获取程序中球半径引用
muRHandle = GLES30.glGetUniformLocation(mProgram, "uR");
}
public void drawSelf()
{
MatrixState.rotate(xAngle, 1, 0, 0);//绕X轴转动
MatrixState.rotate(yAngle, 0, 1, 0);//绕Y轴转动
MatrixState.rotate(zAngle, 0, 0, 1);//绕Z轴转动
// 指定使用某套shader程序
GLES30.glUseProgram(mProgram);
// 将最终变换矩阵传入渲染管线
GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false,
MatrixState.getFinalMatrix(), 0);
// 将半径尺寸传入渲染管线
GLES30.glUniform1f(muRHandle, r * UNIT_SIZE);
//将顶点位置数据送入渲染管线
GLES30.glVertexAttribPointer(maPositionHandle, 3, GLES30.GL_FLOAT,
false, 3 * 4, mVertexBuffer);
//启用顶点位置数据数组
GLES30.glEnableVertexAttribArray(maPositionHandle);
//绘制球
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount);
}
}
2、自定义的MySurfaceView,创建自定义渲染器SceneRenderer,同时设置渲染模式为主动渲染:RENDERMODE_CONTINUOUSLY。
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.annotation.SuppressLint;
import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
@SuppressLint("ClickableViewAccessibility")
class MySurfaceView extends GLSurfaceView
{
private final float TOUCH_SCALE_FACTOR = 180.0f/320;//角度缩放比例
private SceneRenderer mRenderer;//场景渲染器
Ball ball;//球
private float mPreviousY;//上次的触控位置Y坐标
private float mPreviousX;//上次的触控位置X坐标
public MySurfaceView(Context context) {
super(context);
this.setEGLContextClientVersion(3); //设置使用OPENGL ES3.0
mRenderer = new SceneRenderer(); //创建场景渲染器
setRenderer(mRenderer); //设置渲染器
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染
}
private class SceneRenderer implements GLSurfaceView.Renderer
{
public void onDrawFrame(GL10 gl)
{
//清除深度缓冲与颜色缓冲
GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
//保护现场
MatrixState.pushMatrix();
//绘制球
MatrixState.pushMatrix();
ball.drawSelf();
MatrixState.popMatrix();
//恢复现场
MatrixState.popMatrix();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置视窗大小及位置
GLES30.glViewport(0, 0, width, height);
//计算GLSurfaceView的宽高比
Constant.ratio = (float) width / height;
// 调用此方法计算产生透视投影矩阵
MatrixState.setProjectFrustum(-Constant.ratio, Constant.ratio, -1, 1, 20, 100);
// 调用此方法产生摄像机9参数位置矩阵
MatrixState.setCamera(0, 0, 30, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//初始化变换矩阵
MatrixState.setInitStack();
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置屏幕背景色RGBA
GLES30.glClearColor(0f,0f,0f, 1.0f);
//创建球对象
ball=new Ball(MySurfaceView.this);
//打开深度检测
GLES30.glEnable(GLES30.GL_DEPTH_TEST);
//打开背面剪裁
GLES30.glEnable(GLES30.GL_CULL_FACE);
}
}
}
3、主Activity,实例化自定义的MyGLSurfaceView,并添加到setContentView。
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class Sample6_1_Activity extends Activity {
private MySurfaceView mGLSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置为全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 初始化GLSurfaceView
mGLSurfaceView = new MySurfaceView(this);
// 切换到主界面
setContentView(mGLSurfaceView);
}
@Override
protected void onResume() {
super.onResume();
mGLSurfaceView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mGLSurfaceView.onPause();
}
}
4、shader操作工具类:加载顶点Shader与片元Shader的工具类ShaderUtil,loadShader加载制定shader的方法,createProgram创建shader程序的方法,checkGlError检查每一步操作是否有错误的方法,loadFromAssetsFile从sh脚本中加载shader内容的方法。
import android.annotation.SuppressLint;
import android.content.res.Resources;
import android.opengl.GLES30;
import android.util.Log;
//加载顶点Shader与片元Shader的工具类
@SuppressLint("NewApi")
public class ShaderUtil
{
//加载制定shader的方法
public static int loadShader
(
int shaderType, //shader的类型 GLES30.GL_VERTEX_SHADER GLES30.GL_FRAGMENT_SHADER
String source //shader的脚本字符串
)
{
//创建一个新shader
int shader = GLES30.glCreateShader(shaderType);
//若创建成功则加载shader
if (shader != 0)
{
//加载shader的源代码
GLES30.glShaderSource(shader, source);
//编译shader
GLES30.glCompileShader(shader);
//存放编译成功shader数量的数组
int[] compiled = new int[1];
//获取Shader的编译情况
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0)
{//若编译失败则显示错误日志并删除此shader
Log.e("ES30_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES30_ERROR", GLES30.glGetShaderInfoLog(shader));
GLES30.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
//创建shader程序的方法
public static int createProgram(String vertexSource, String fragmentSource)
{
//加载顶点着色器
int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0)
{
return 0;
}
//加载片元着色器
int pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0)
{
return 0;
}
//创建程序
int program = GLES30.glCreateProgram();
//若程序创建成功则向程序中加入顶点着色器与片元着色器
if (program != 0)
{
//向程序中加入顶点着色器
GLES30.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
//向程序中加入片元着色器
GLES30.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
//链接程序
GLES30.glLinkProgram(program);
//存放链接成功program数量的数组
int[] linkStatus = new int[1];
//获取program的链接情况
GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0);
//若链接失败则报错并删除程序
if (linkStatus[0] != GLES30.GL_TRUE)
{
Log.e("ES30_ERROR", "Could not link program: ");
Log.e("ES30_ERROR", GLES30.glGetProgramInfoLog(program));
GLES30.glDeleteProgram(program);
program = 0;
}
}
return program;
}
//检查每一步操作是否有错误的方法
public static void checkGlError(String op)
{
int error;
while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR)
{
Log.e("ES30_ERROR", op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
//从sh脚本中加载shader内容的方法
public static String loadFromAssetsFile(String fname,Resources r)
{
String result=null;
try
{
InputStream in=r.getAssets().open(fname);
int ch=0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while((ch=in.read())!=-1)
{
baos.write(ch);
}
byte[] buff=baos.toByteArray();
baos.close();
in.close();
result=new String(buff,"UTF-8");
result=result.replaceAll("\\r\\n","\n");
}
catch(Exception e)
{
e.printStackTrace();
}
return result;
}
}
5、shader着色器程序
#version 300 es
uniform mat4 uMVPMatrix;
in vec3 aPosition;
out vec3 vPosition;
void main()
{
gl_Position = uMVPMatrix * vec4(aPosition,1);
vPosition = aPosition;
}
#version 300 es
precision mediump float;
uniform float uR;
in vec2 mcLongLat;
in vec3 vPosition;
out vec4 fragColor;
void main()
{
vec3 color;
float n = 8.0;
float span = 2.0*uR/n;
int i = int((vPosition.x + uR)/span);
int j = int((vPosition.y + uR)/span);
int k = int((vPosition.z + uR)/span);
int whichColor = int(mod(float(i+j+k),2.0));
if(whichColor == 1) {
color = vec3(0.678,0.231,0.129);
}
else {
color = vec3(1.0,1.0,1.0);
}
fragColor=vec4(color,0);
}
6、渲染效果示例:
最后,欢迎大家一起交流学习:微信:liaosy666 ; QQ:2209115372 。