OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维
OpenGL之纹理
三角形带
要构建圆,可以使用一个三角形扇
要构建圆柱的侧面,可以使用三角形带(Triangle Strip)。与三角形扇一样,三角形带让我们定义多个三角形而不用重复那些三角形中共有的点,如图:
三角形带的前三个顶点定义了第一个三角形,之后的每个额外的顶点都定义了另外的一个三角形,为了使用三角形带定义这个圆柱体的侧面,只需要把这个带绕成一个管子,并确保最后两个顶点与最前面的两个顶点一致
添加几何图形的类
public class Point {
private float mX;
private float mY;
private float mZ;
public Point(float x, float y, float z) {
this.mX = x;
this.mY = y;
this.mZ = z;
}
/**
* 沿着y轴移动点
* @param y
* @return
*/
public Point translateY(float y) {
return new Point(mX, mY + y, mZ);
}
public float getX() {
return mX;
}
public float getY() {
return mY;
}
public float getZ() {
return mZ;
}
}
public class Circle {
private Point mCenter;
private float mRadius;
/**
* @param center
* @param radius
*/
public Circle(Point center, float radius) {
this.mCenter = center;
this.mRadius = radius;
}
public Point getCenter() {
return mCenter;
}
public float getRadius() {
return mRadius;
}
}
public class Cylinder {
private Circle mCircle;
private float mHeight;
public Cylinder(Circle circle, float height) {
mCircle = circle;
mHeight = height;
}
public Circle getCircle() {
return mCircle;
}
public float getHeight() {
return mHeight;
}
}
添加物体构建器
public class ObjectBuilder {
private static final int FLOAT_PER_VERTEX = 3;
// 用于保存生成的顶点数据和绘制命令
protected GeneratedData mGeneratedData;
// 记录下一个顶点的位置
protected int mOffset;
// 保存顶点的位置
protected float[] mVertexData;
public ObjectBuilder(int vertexSize) {
mVertexData = new float[vertexSize * FLOAT_PER_VERTEX];
mGeneratedData = new GeneratedData(mVertexData);
}
/**
* 创建冰球
* @param circle
* @param cylinder
* @param numPoints
* @return
*/
public static GeneratedData createPuck(Circle circle, Cylinder cylinder, int numPoints) {
int size = sizeOfVertexInCircle(numPoints) + sizeOfVertexInCylinder(numPoints);
ObjectBuilder builder = new ObjectBuilder(size);
builder.addCircle(circle, numPoints);
builder.addCylinder(cylinder, numPoints);
return builder.build();
}
/**
* 创建木槌
* @param topCircle
* @param topCylinder
* @param bottomCircle
* @param bottomCylinder
* @param numPoints
* @return
*/
public static GeneratedData createMallet(Circle topCircle, Cylinder topCylinder,
Circle bottomCircle, Cylinder bottomCylinder, int numPoints) {
int size = (sizeOfVertexInCircle(numPoints) + sizeOfVertexInCylinder(numPoints)) * 2;
ObjectBuilder builder = new ObjectBuilder(size);
builder.addCircle(topCircle, numPoints);
builder.addCylinder(topCylinder, numPoints);
builder.addCircle(bottomCircle, numPoints);
builder.addCylinder(bottomCylinder, numPoints);
return builder.build();
}
/**
* 生成顶点数据和绘画指令
*
* @return
*/
public GeneratedData build() {
return mGeneratedData;
}
/**
* 添加圆柱体侧面
*
* @param size
* @param cylinder
*/
protected void addCylinder(Cylinder cylinder, int size) {
int startDrawCylinderOffset = mOffset / FLOAT_PER_VERTEX;
//添加圆柱体侧面的顶点数据
addCylinderVertex(cylinder, size);
//画圆柱体侧面命令
mGeneratedData.addDrawCommand(new GeneratedData.DrawCommand() {
@Override
public void draw() {
glDrawArrays(GL_TRIANGLE_STRIP, startDrawCylinderOffset, sizeOfVertexInCylinder(size));
}
});
}
/**
* 添加圆柱体顶端的圆
*
* @param size
* @param circle
*/
protected void addCircle(Circle circle, int size) {
int startDrawCircleOffset = mOffset / FLOAT_PER_VERTEX;
//添加圆柱体顶部圆的顶点数据
addCircleVertex(circle, size);
//画圆命令
mGeneratedData.addDrawCommand(new GeneratedData.DrawCommand() {
@Override
public void draw() {
glDrawArrays(GL_TRIANGLE_FAN, startDrawCircleOffset, sizeOfVertexInCircle(size));
}
});
}
/**
* 添加圆柱体顶部圆的顶点数据
*
* @param circle
* @param size
*/
private void addCircleVertex(Circle circle, int size) {
Point center = circle.getCenter();
float radius = circle.getRadius();
//三角形扇的第一个点,中间的点
mVertexData[mOffset++] = center.getX();
mVertexData[mOffset++] = center.getY();
mVertexData[mOffset++] = center.getZ();
double angleInRadian;
for (int i = 0; i <= size; i++) {
angleInRadian = (2 * Math.PI / size) * i;
mVertexData[mOffset++] = (float) (center.getX() + radius * Math.cos(angleInRadian));
mVertexData[mOffset++] = center.getY();
mVertexData[mOffset++] = (float) (center.getZ() + radius * Math.sin(angleInRadian));
}
}
/**
* 添加圆柱体侧面的顶点数据
*
* @param cylinder
* @param size
*/
private void addCylinderVertex(Cylinder cylinder, int size) {
Point center = cylinder.getCircle().getCenter();
float radius = cylinder.getCircle().getRadius();
float height = cylinder.getHeight();
double angleInRadian;
for (int i = 0; i <= size; i++) {
angleInRadian = (2 * Math.PI / size) * i;
//上面的顶点
mVertexData[mOffset++] = (float) (center.getX() + radius * Math.cos(angleInRadian));
mVertexData[mOffset++] = center.getY() + height;
mVertexData[mOffset++] = (float) (center.getZ() + radius * Math.sin(angleInRadian));
//下面的顶点
mVertexData[mOffset++] = (float) (center.getX() + radius * Math.cos(angleInRadian));
mVertexData[mOffset++] = center.getY();
mVertexData[mOffset++] = (float) (center.getZ() + radius * Math.sin(angleInRadian));
}
}
/**
* 圆顶点数据大小
*
* @param size
* @return
*/
protected static int sizeOfVertexInCircle(int size) {
return 1 + size + 1;
}
/**
* 圆柱侧边顶点数据大小
*
* @param size
* @return
*/
protected static int sizeOfVertexInCylinder(int size) {
return (size + 1) * 2;
}
}
public class GeneratedData {
private List<DrawCommand> mDrawCommandList = new ArrayList<>();
private float[] mVertexData;
public GeneratedData(float[] vertexData) {
mVertexData = vertexData;
}
public void addDrawCommand(DrawCommand drawCommand) {
mDrawCommandList.add(drawCommand);
}
public float[] getVertexData() {
return mVertexData;
}
public List<DrawCommand> getDrawCommandList() {
return mDrawCommandList;
}
public interface DrawCommand {
void draw();
}
}
新建冰球类
同样的方式去更新木槌类
public class Puck {
private final GeneratedData mGeneratedData;
private final Point centerPoint = new Point(0f, 0f, 0f);
private VertexArray mVertexArray;
public Puck(float radius, float height, int size) {
// 创建圆柱体顶部圆
Point topCircleCenter = new Point(centerPoint.getX(), centerPoint.getY(), centerPoint.getZ());
Circle circle = new Circle(topCircleCenter, radius);
//创建圆柱体侧面
Circle cylinderCircle = new Circle(centerPoint, radius);
Cylinder cylinder = new Cylinder(cylinderCircle, height);
mGeneratedData = ObjectBuilder.createPuck(circle, cylinder, size);
mVertexArray = new VertexArray(mGeneratedData.getVertexData());
}
/**
* 顶点数据绑定到OpenGL程序
*
* @param colorProgram
*/
public void bindData(ColorProgram colorProgram) {
mVertexArray.setVertexAttribPointer(
0,
colorProgram.getAPosition(),
POSITION_COMPONENT_COUNT,
0
);
}
/**
* 画冰球
*/
public void draw() {
List<GeneratedData.DrawCommand> drawCommandList = mGeneratedData.getDrawCommandList();
for (GeneratedData.DrawCommand drawCommand : drawCommandList) {
drawCommand.draw();
}
}
}
更新着色器
顶点着色器
attribute vec4 a_Position;
uniform mat4 u_Matrix;
void main(){
gl_Position=u_Matrix*a_Position;
gl_PointSize=10.0;
}
片段着色器
precision mediump float;
uniform vec4 u_Color;
void main(){
gl_FragColor=u_Color;
}
更新冰球对应的程序
public class ColorProgram extends BaseProgram {
private final int mAPosition;
private final int mUMatrix;
private final int mUColor;
public ColorProgram(Context context) {
super(context, R.raw.buildobjects6_color_vertex, R.raw.buildobjects6_color_fragment);
mAPosition = glGetAttribLocation(mProgram, A_POSITION);
mUColor = glGetUniformLocation(mProgram, U_COLOR);
mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
}
public void setUniforms(float[] matrix, float r, float g, float b) {
glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
glUniform4f(mUColor, r, g, b, 1f);
}
public int getAPosition() {
return mAPosition;
}
}
矩阵总结
矩阵 | 说明 |
---|---|
模型矩阵 | 移动、旋转、平移单独的某个物体 |
视图矩阵 | 平等地影响场景中的每一·个物体,相当于移动相机 |
投影矩阵 | 帮助创建三维的幻象,通常只有当屏幕变换方位时,它才会变化 |
视图矩阵
使用 Matrix.setLookAtM 方法创建视图矩阵
方法原型
public static void setLookAtM(float[] rm, int rmOffset,
float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ, float upX, float upY,
float upZ) {
参数说明
参数 | 说明 |
---|---|
float[] rm | 最终生成的视图矩阵数组,长度至少容纳16个元素,以便它能存储视图 |
int rmOffset | 从 rmOffset 的这个偏移值开始存进rm |
float eyeX, float eyeY, float eyeZ, | 这是眼睛所在的位置,场景中的所有东西看起来都像是从这个点观察它们一样 |
float centerX, float centerY, float centerZ | 这是眼睛正在看的地方,这个位置出现在整个场景的中心 |
float upX, float upY, float upZ | 你的头指向的地方,upY的值为1意味着你的头笔直指向Y轴上方 |
坐标变换总结
vertex(clip) = ProjectionMatrix * ViewMatrix * ModelMatrix * vertex(model)
修改渲染器
public class MyRenderer implements GLSurfaceView.Renderer {
private Context mContext;
// 保存模型矩阵
private float[] modelMatrix = new float[16];
// 视图矩阵
private float[] viewMatrix = new float[16];
// 保存投影矩阵
private float[] projectionMatrix = new float[16];
// 保存视图矩阵和投影矩阵的乘积
private float[] viewProjectionMatrix = new float[16];
// 保存模型矩阵、视图矩阵和投影矩阵的乘积
private float[] modelViewProjectionMatrix = new float[16];
private TextureProgram mTextureProgram;
private ColorProgram mColorProgram;
private Table mTable;
private Mallet mMallet;
private int mTextureId;
private Puck mPuck;
public MyRenderer(Context context) {
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
glClearColor(0f, 0f, 0f, 0f);
mTextureProgram = new TextureProgram(mContext);
mColorProgram = new ColorProgram(mContext);
// 创建物体
mTable = new Table();
mMallet = new Mallet(0.08f, 0.05f, 0.04f, 0.10f, 64);
mPuck = new Puck(0.06f, 0.05f, 64);
mTextureId = TextureHelper.loadTexture(mContext, R.drawable.air_hockey_surface);
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
glViewport(0, 0, width, height);
MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / height, 1, 10);
// 创建视图矩阵
Matrix.setLookAtM(viewMatrix, 0, 0, 1.2f, 2.2f, 0, 0, 0, 0, 1, 0);
Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}
@Override
public void onDrawFrame(GL10 gl10) {
glClear(GL_COLOR_BUFFER_BIT);
// 绘制桌子
positionTableOnScreen();
mTextureProgram.useProgram();
mTextureProgram.setUniforms(modelViewProjectionMatrix, mTextureId);
mTable.bindData(mTextureProgram);
mTable.draw();
// 绘制木槌
positionObjectOnScreen(0f, 0f, 0.4f);
mColorProgram.useProgram();
mColorProgram.setUniforms(modelViewProjectionMatrix, 0f, 1f, 0f);
mMallet.bindData(mColorProgram);
mMallet.draw();
// 绘制冰球
positionObjectOnScreen(0f, 0f, 0f);
mColorProgram.useProgram();
mColorProgram.setUniforms(modelViewProjectionMatrix, 1f, 0f, 0f);
mPuck.bindData(mColorProgram);
mPuck.draw();
}
/**
* 桌子原来是以x和y坐标定义的,因此要使它平放在地上,我们需要让它绕x轴向后旋转90度
* 不需要把桌子平移一定的距离,因为我们想要桌子在世界坐标里保持在位置(0,0,0)
* 并且视图矩阵已经想办法使桌子对我们可见了
*/
public void positionTableOnScreen() {
Matrix.setIdentityM(modelMatrix, 0);
Matrix.rotateM(modelMatrix, 0, -90, 1, 0, 0);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
}
/**
* 将物体移动到(x,y,z)的位置
*
* @param x
* @param y
* @param z
*/
public void positionObjectOnScreen(float x, float y, float z) {
Matrix.setIdentityM(modelMatrix, 0);
Matrix.translateM(modelMatrix, 0, x, y, z);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
}
}