OpenGL之构建简单物体

OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维
OpenGL之纹理

三角形带

要构建圆,可以使用一个三角形扇

要构建圆柱的侧面,可以使用三角形带(Triangle Strip)。与三角形扇一样,三角形带让我们定义多个三角形而不用重复那些三角形中共有的点,如图:


image-20211221211226581.png

三角形带的前三个顶点定义了第一个三角形,之后的每个额外的顶点都定义了另外的一个三角形,为了使用三角形带定义这个圆柱体的侧面,只需要把这个带绕成一个管子,并确保最后两个顶点与最前面的两个顶点一致

添加几何图形的类

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);
    }
}

效果

image-20211221222748898.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容