OpenGL ES之十一——绘制3D图形

2023-11-07

概述

这是一个系列的Android平台下OpenGl ES介绍,从最基本的使用最终到VR图的展示的实现,属于基础篇。(后面针对VR视频会再有几篇文章,属于进阶篇)

OpenGL ES之一——概念扫盲
OpenGL ES之二——Android中的OpenGL ES概述
OpenGL ES之三——绘制纯色背景
OpenGL ES之四——绘制点,线,三角形
OpenGL ES之五——相机和投影,绘制等腰三角形
OpenGL ES之六——绘制矩形和圆形
OpenGL ES之七——着色器语言GLSL
OpenGL ES之八——GLES20类和Matrix类
OpenGL ES之九——相机和投影
OpenGL ES之十——纹理贴图(展示一张图片)
OpenGL ES之十一——绘制3D图形
OpenGL ES之十二——地球仪和VR图

本篇概述

经过前面的铺垫,我们对OpenGLES了解了不少了,是时候绘制立体图像了。

一 圆锥

拆分原理:

在之前的文章中我们绘制过圆形,圆锥可以看成是圆心顶点坐标z不为0的圆形,绘制的方法和绘制一个圆是一样的,将圆锥的侧面切分为一个个三角形。如下:

着色器文件

顶点着色器

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
uniform mat4 u_Matrix;
out vec4 vColor;
void main() {
    gl_Position  = u_Matrix*vPosition;
    gl_PointSize = 10.0;
    vColor = aColor;
}

片段着色器

#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
    fragColor = vColor;
}

渲染器
public class ConeRenderer implements GLSurfaceView.Renderer {
    private static final int BYTES_PER_FLOAT = 4;
    //顶点位置缓存
    private final FloatBuffer vertexBuffer;
    //顶点颜色缓存
    private final FloatBuffer colorBuffer;
    //渲染程序
    private int mProgram;

    //相机矩阵
    private final float[] mViewMatrix = new float[16];
    //投影矩阵
    private final float[] mProjectMatrix = new float[16];
    //最终变换矩阵
    private final float[] mMVPMatrix = new float[16];

    //返回属性变量的位置
    //变换矩阵
    private int uMatrixLocation;
    //位置
    private int aPositionLocation;
    //颜色
    private int aColorLocation;

    //圆形顶点位置
    private float circularCoords[];
    //顶点的颜色
    private float color[];


    public ConeRenderer() {
        createPositions(0.5f,60);

        //顶点位置相关
        //分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
        vertexBuffer = ByteBuffer.allocateDirect(circularCoords.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(circularCoords);
        vertexBuffer.position(0);

        //顶点颜色相关
        colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        colorBuffer.put(color);
        colorBuffer.position(0);
    }

    private void createPositions(float radius, int n){
        ArrayList<Float> data=new ArrayList<>();
        data.add(0.0f);             //设置圆锥顶点坐标
        data.add(0.0f);
        data.add(-0.5f);
        float angDegSpan=360f/n;
        for(float i=0;i<360+angDegSpan;i+=angDegSpan){
            data.add((float) (radius*Math.sin(i*Math.PI/180f)));
            data.add((float)(radius*Math.cos(i*Math.PI/180f)));
            data.add(0.0f);
        }
        float[] f=new float[data.size()];
        for (int i=0;i<f.length;i++){
            f[i]=data.get(i);
        }

        circularCoords = f;

        //处理各个顶点的颜色
        color = new float[f.length*4/3];
        ArrayList<Float> tempC = new ArrayList<>();
        ArrayList<Float> totalC = new ArrayList<>();
        ArrayList<Float> total0 = new ArrayList<>();
        total0.add(0.5f);
        total0.add(0.0f);
        total0.add(0.0f);
        total0.add(1.0f);
        tempC.add(1.0f);
        tempC.add(1.0f);
        tempC.add(1.0f);
        tempC.add(1.0f);
        for (int i=0;i<f.length/3;i++){
            if (i==0){
                totalC.addAll(total0);
            }else {
                totalC.addAll(tempC);
            }

        }

        for (int i=0; i<totalC.size();i++){
            color[i]=totalC.get(i);
        }
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //将背景设置为白色
        GLES30.glClearColor(0.5f,0.5f,0.5f,1.0f);

        //编译顶点着色程序
        String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);


        uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
        aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition");
        aColorLocation = GLES30.glGetAttribLocation(mProgram, "aColor");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置绘制窗口
        GLES30.glViewport(0, 0, width, height);


        //相机和透视投影方式
        //计算宽高比
        float ratio=(float)width/height;
        //设置透视投影
        Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
        //设置相机位置
        Matrix.setLookAtM(mViewMatrix, 0, 6, 0, -1f, 0f, 0f, 0f, 0f, 0.0f, 1.0f);
        //计算变换矩阵
        Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把颜色缓冲区设置为我们预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //将变换矩阵传入顶点渲染器
        GLES30.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);
        //准备坐标数据
        GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(aPositionLocation);

        //准备颜色数据
        GLES30.glVertexAttribPointer(aColorLocation, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
        //启用顶点颜色句柄
        GLES30.glEnableVertexAttribArray(aColorLocation);

        //绘制圆锥侧面
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, circularCoords.length/3);

        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(aPositionLocation);
        GLES30.glDisableVertexAttribArray(aColorLocation);
    }
}

上面唯一不同的就是相机的参数变了如下,如果认真看了之前关于相机和投影的知识这里很好理解。

		//设置相机位置
        Matrix.setLookAtM(mViewMatrix, 0, 6, 0, -1f, 0f, 0f, 0f, 0f, 0.0f, 1.0f);

效果图

在这里插入图片描述
我们发现它不是个实体圆锥而是一个侧面。那么为了得到一个有底的圆锥,我们就再绘制一个圆就是了。

完整的渲染器
public class ConeRenderer implements GLSurfaceView.Renderer{
        //一个Float占用4Byte
        private static final int BYTES_PER_FLOAT = 4;
        //顶点位置缓存(圆锥侧面)
        private final FloatBuffer vertexBuffer;
        //顶点位置缓存(圆锥底面)
        private FloatBuffer vertexBuffer1;
        //顶点颜色缓存
        private final FloatBuffer colorBuffer;
        //渲染程序
        private int mProgram;

        //相机矩阵
        private final float[] mViewMatrix = new float[16];
        //投影矩阵
        private final float[] mProjectMatrix = new float[16];
        //最终变换矩阵
        private final float[] mMVPMatrix = new float[16];

        //返回属性变量的位置
        //变换矩阵
        private int uMatrixLocation;
        //位置
        private int aPositionLocation;
        //颜色
        private int aColorLocation;

        //圆锥顶点位置(侧面)
        private float coneCoords[];

        //圆锥顶点位置(圆底面)
        private float coneCoords1[];

        //顶点的颜色
        private float color[];

        public ConeRenderer() {
            //圆锥的侧面数据
            createPositions(0.5f,60);

            //圆锥的圆形底面数据
            createCircularPositions();

            //顶点位置相关(圆锥侧面)
            //分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
            vertexBuffer = ByteBuffer.allocateDirect(coneCoords.length * BYTES_PER_FLOAT)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            vertexBuffer.put(coneCoords);
            vertexBuffer.position(0);

            //顶点颜色相关
            colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            colorBuffer.put(color);
            colorBuffer.position(0);

            //顶点位置相关(圆锥底面)
            vertexBuffer1 = ByteBuffer.allocateDirect(coneCoords1.length * BYTES_PER_FLOAT)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            vertexBuffer1.put(coneCoords1);
            vertexBuffer1.position(0);
        }

    private void createCircularPositions() {
        coneCoords1 = new float[coneCoords.length];

        for (int i=0;i<coneCoords.length;i++){
            if (i==2){
                coneCoords1[i]=0.0f;
            }else {
                coneCoords1[i]=coneCoords[i];
            }
        }
    }

    private void createPositions(float radius, int n){
            ArrayList<Float> data=new ArrayList<>();
            data.add(0.0f);//设置锥心坐标
            data.add(0.0f);
            data.add(-0.5f);

            float angDegSpan=360f/n;
            for(float i=0;i<360+angDegSpan;i+=angDegSpan){
                data.add((float) (radius*Math.sin(i*Math.PI/180f)));
                data.add((float)(radius*Math.cos(i*Math.PI/180f)));
                data.add(0.0f);
            }
            float[] f=new float[data.size()];

            for (int i=0;i<f.length;i++){
                f[i]=data.get(i);
            }

            coneCoords = f;

            //处理各个顶点的颜色
            color = new float[f.length*4/3];
            ArrayList<Float> tempC = new ArrayList<>();
            ArrayList<Float> totalC = new ArrayList<>();
            ArrayList<Float> totalCForColor = new ArrayList<>();

            tempC.add(0.8f);
            tempC.add(0.8f);
            tempC.add(0.8f);
            tempC.add(1.0f);

            ArrayList<Float> zeroIndexC = new ArrayList<>();
            zeroIndexC.add(1.0f);
            zeroIndexC.add(0.0f);
            zeroIndexC.add(0.0f);
            zeroIndexC.add(1.0f);
            for (int i=0;i<f.length/3;i++){
                if (i==0){
                    totalC.addAll(zeroIndexC);
                }else {
                    totalC.addAll(tempC);
                }

                totalCForColor.addAll(tempC);
            }

            for (int i=0; i<totalC.size();i++){
                color[i]=totalC.get(i);
            }
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            //将背景设置为白色
            GLES30.glClearColor(0.3f,0.3f,0.3f,1.0f);

            //编译顶点着色程序
            String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_cone_renderer);
            int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
            //编译片段着色程序
            String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_cone_renderer);
            int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
            //连接程序
            mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
            //在OpenGLES环境中使用程序
            GLES30.glUseProgram(mProgram);

            uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
            aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition");
            aColorLocation = GLES30.glGetAttribLocation(mProgram, "aColor");
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            //设置绘制窗口
            GLES30.glViewport(0, 0, width, height);

            //相机和透视投影方式
            //计算宽高比
            float ratio=(float)width/height;
            //设置透视投影
            Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
            //设置相机位置
            Matrix.setLookAtM(mViewMatrix, 0, 6, 0, -1f, 0f, 0f, 0f, 0f, 0f, 1f);

            //计算变换矩阵
            Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            //把颜色缓冲区设置为我们预设的颜色
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

            //将变换矩阵传入顶点渲染器
            GLES30.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);

            //准备坐标数据
            GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
            //启用顶点位置句柄
            GLES30.glEnableVertexAttribArray(aPositionLocation);
            //准备颜色数据
            GLES30.glVertexAttribPointer(aColorLocation, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
            //启用顶点颜色句柄
            GLES30.glEnableVertexAttribArray(aColorLocation);
            //绘制圆锥侧面
            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, coneCoords.length/3);

            //准备坐标数据
            GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer1);
            //启用顶点位置句柄
            GLES30.glEnableVertexAttribArray(aPositionLocation);

            //绘制圆锥圆形底面
            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, coneCoords1.length/3);

            //禁止顶点数组的句柄
            GLES30.glDisableVertexAttribArray(aPositionLocation);
            GLES30.glDisableVertexAttribArray(aColorLocation);
        }
}

下面是效果图
在这里插入图片描述

二 圆柱

2.1 拆分原理

圆柱的与圆锥类似,我们可以把圆柱拆解成上下两个圆面,加上一个圆筒。圆筒我们之前也没画过,它怎么拆解成三角形呢?我们可以如同拆圆的思路来理解圆柱,想想正三菱柱、正八菱柱、正一百菱柱……菱越多,就越圆滑与圆柱越接近了,然后再把每个菱面(矩形)拆解成两个三角形就OK了。

2.2 完整的渲染器

public class CylinderRenderer implements GLSurfaceView.Renderer{
    private static final int SEPARATE_COUNT = 120;
    private static final float RADIUS = 0.5f;
    private static final float HEIGHT = 1.0f;

    //一个Float占用4Byte
    private static final int BYTES_PER_FLOAT = 4;
    //顶点位置缓存(圆柱侧面)
    private final FloatBuffer vertexBuffer;
    //顶点位置缓存(圆柱底面)
    private FloatBuffer vertexBuffer1;
    //顶点位置缓存(圆柱底面)
    private FloatBuffer vertexBuffer2;
    //渲染程序
    private int mProgram;

    //相机矩阵
    private final float[] mViewMatrix = new float[16];
    //投影矩阵
    private final float[] mProjectMatrix = new float[16];
    //最终变换矩阵
    private final float[] mMVPMatrix = new float[16];

    //返回属性变量的位置
    //变换矩阵
    private int uMatrixLocation;
    //位置
    private int aPositionLocation;

    //圆柱顶点位置(侧面)
    private float cylinderCoords[];

    //圆柱顶点位置(圆底面)
    private float cylinderCoords1[];

    //圆柱顶点位置(圆底面)
    private float cylinderCoords2[];

    public CylinderRenderer() {
        //侧面数据
        createCylinderPositions();

        //底面数据
        createCircularPositions();

        //顶点位置相关(圆柱侧面)
        //分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
        vertexBuffer = ByteBuffer.allocateDirect(cylinderCoords.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(cylinderCoords);
        vertexBuffer.position(0);

        //顶点位置相关(圆柱底面)
        vertexBuffer1 = ByteBuffer.allocateDirect(cylinderCoords1.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer1.put(cylinderCoords1);
        vertexBuffer1.position(0);

        //顶点位置相关(圆柱底面)
        vertexBuffer2 = ByteBuffer.allocateDirect(cylinderCoords2.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer2.put(cylinderCoords2);
        vertexBuffer2.position(0);
    }

    private void createCircularPositions() {
        ArrayList<Float> data=new ArrayList<>();
        data.add(0.0f);
        data.add(0.0f);
        data.add(HEIGHT);

        ArrayList<Float> data1=new ArrayList<>();
        data1.add(0.0f);
        data1.add(0.0f);
        data1.add(0.0f);

        float angDegSpan=360f/SEPARATE_COUNT;
        for(float i=0;i<360+angDegSpan;i+=angDegSpan){
            data.add((float) (RADIUS*Math.sin(i*Math.PI/180f)));
            data.add((float)(RADIUS*Math.cos(i*Math.PI/180f)));
            data.add(HEIGHT);

            data1.add((float) (RADIUS*Math.sin(i*Math.PI/180f)));
            data1.add((float)(RADIUS*Math.cos(i*Math.PI/180f)));
            data1.add(0.0f);
        }
        float[] f=new float[data.size()];
        float[] f1=new float[data.size()];

        for (int i=0;i<f.length;i++){
            f[i]=data.get(i);
            f1[i]=data1.get(i);
        }

        cylinderCoords1 = f;
        cylinderCoords2 = f1;
    }

    private void createCylinderPositions(){
        ArrayList<Float> pos=new ArrayList<>();
        float angDegSpan=360f/SEPARATE_COUNT;
        for(float i=0;i<360+angDegSpan;i+=angDegSpan){
            pos.add((float) (RADIUS*Math.sin(i*Math.PI/180f)));
            pos.add((float)(RADIUS*Math.cos(i*Math.PI/180f)));
            pos.add(HEIGHT);
            pos.add((float) (RADIUS*Math.sin(i*Math.PI/180f)));
            pos.add((float)(RADIUS*Math.cos(i*Math.PI/180f)));
            pos.add(0.0f);
        }
        float[] d=new float[pos.size()];
        for (int i=0;i<d.length;i++){
            d[i]=pos.get(i);
        }

        cylinderCoords = d;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //将背景设置为白色
        GLES30.glClearColor(0.3f,0.3f,0.3f,1.0f);

        //编译顶点着色程序
        String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_cone_renderer);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_cone_renderer);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);

        uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
        aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置绘制窗口
        GLES30.glViewport(0, 0, width, height);

        //相机和透视投影方式
        //计算宽高比
        float ratio=(float)width/height;
        //设置透视投影
        Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
        //设置相机位置
        Matrix.setLookAtM(mViewMatrix, 0, 6, 0, -1f, 0f, 0f, 0f, 0f, 0f, 1f);

        //计算变换矩阵
        Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把颜色缓冲区设置为我们预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //将变换矩阵传入顶点渲染器
        GLES30.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);

        //准备坐标数据
        GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(aPositionLocation);
        //绘制圆柱侧面
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, cylinderCoords.length/3);

        //准备坐标数据
        GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer1);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(aPositionLocation);
        //绘制圆柱侧面
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, cylinderCoords1.length/3);

        //准备坐标数据
        GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer2);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(aPositionLocation);
        //绘制圆柱侧面
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, cylinderCoords2.length/3);

        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(aPositionLocation);
    }
}

2.3 顶点着色器

稍微有变化,这里不去特意处理颜色。

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
uniform mat4 u_Matrix;
out vec4 vColor;
void main() {
    gl_Position=u_Matrix*vPosition;
    if(vPosition.z!=0.0){
        vColor=vec4(0.0,0.0,0.0,1.0);
    }else{
        vColor=vec4(0.8,0.8,0.8,1.0);
    }
}

2.4 效果图

在这里插入图片描述

三 球

球的绘制这里只是一个铺垫为后面的绘制“地球仪”和后面的VR图做准备

3.1 原理

相对于圆锥圆柱来说,球体的拆解就复杂了许多,比较常见的拆解方法是将按照经纬度拆解和按照正多面体拆解,下图分别为正多面体示意和经纬度拆解示意:

正多面体的方法拆解:

在这里插入图片描述

经纬度的方法拆解(每一个小块看做一个四边形形,再拆成三角形。):

在这里插入图片描述

球面上点的坐标

无论是按照经纬度拆还是按照多面体拆,都需要知道球上面点的坐标,这算是基本的几何知识了。以球的中心为坐标中心,球的半径为R的话,那么球上点的坐标则为:
(R∗cos(ψ)∗sin(λ),R∗sin(ψ),R∗cos(ψ)∗cos(λ))
其中,ψ为圆心到点的线段与xz平面的夹角,λ为圆心到点的线段在xz平面的投影与z轴的夹角。用图形表示如下:
在这里插入图片描述

按经纬度切分如下

先将球体以经度为准分为若干份,再在相应经度的一圈纬度上进行切分如下图(注意看下面获取顶点坐标时候的操作)
在这里插入图片描述

3.2 绘制

顶点着色器
#version 300 es
in vec4 vPosition;
uniform mat4 u_Matrix;
out vec4 vColor;
void main(){
    gl_Position=u_Matrix*vPosition;
    float color;
    if(vPosition.z>0.0){
        color=vPosition.z;
    }else{
        color=-vPosition.z;
    }
    vColor=vec4(color,color,color,1.0);
}
片段着色器
#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
    fragColor = vColor;
}

渲染器
public class BallRenderer implements GLSurfaceView.Renderer {
    private static final int BYTES_PER_FLOAT = 4;
    //顶点位置缓存
    private final FloatBuffer vertexBuffer;
    //渲染程序
    private int mProgram;

    //相机矩阵
    private final float[] mViewMatrix = new float[16];
    //投影矩阵
    private final float[] mProjectMatrix = new float[16];
    //最终变换矩阵
    private final float[] mMVPMatrix = new float[16];

    //返回属性变量的位置
    //变换矩阵
    private int uMatrixLocation;
    //位置
    private int aPositionLocation;

    private float[] ballCoords;

    public BallRenderer() {
        createVertexPos();

        //顶点位置相关
        //分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
        vertexBuffer = ByteBuffer.allocateDirect(ballCoords.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(ballCoords);
        vertexBuffer.position(0);
    }

    private void createVertexPos() {
        float radius = 1.0f; // 球的半径
        double angleSpan = Math.PI / 90f; // 将球进行单位切分的角度
        ArrayList<Float> alVertix = new ArrayList<>();
        for (double vAngle = 0; vAngle < Math.PI; vAngle = vAngle + angleSpan){

            for (double hAngle = 0; hAngle < 2*Math.PI; hAngle = hAngle + angleSpan){
            	//获取一个四边形的四个顶点
                float x0 = (float) (radius* Math.sin(vAngle) * Math.cos(hAngle));
                float y0 = (float) (radius* Math.sin(vAngle) * Math.sin(hAngle));
                float z0 = (float) (radius * Math.cos((vAngle)));

                float x1 = (float) (radius* Math.sin(vAngle) * Math.cos(hAngle + angleSpan));
                float y1 = (float) (radius* Math.sin(vAngle) * Math.sin(hAngle + angleSpan));
                float z1 = (float) (radius * Math.cos(vAngle));

                float x2 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.cos(hAngle + angleSpan));
                float y2 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.sin(hAngle + angleSpan));
                float z2 = (float) (radius * Math.cos(vAngle + angleSpan));

                float x3 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.cos(hAngle));
                float y3 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.sin(hAngle));
                float z3 = (float) (radius * Math.cos(vAngle + angleSpan));

				//将四个点拆分为两个三角形
                alVertix.add(x1);
                alVertix.add(y1);
                alVertix.add(z1);

                alVertix.add(x0);
                alVertix.add(y0);
                alVertix.add(z0);

                alVertix.add(x3);
                alVertix.add(y3);
                alVertix.add(z3);

                alVertix.add(x1);
                alVertix.add(y1);
                alVertix.add(z1);

                alVertix.add(x3);
                alVertix.add(y3);
                alVertix.add(z3);

                alVertix.add(x2);
                alVertix.add(y2);
                alVertix.add(z2);
            }
        }

        int size = alVertix.size();
        ballCoords = new float[size];
        for (int i=0;i<size;i++){
            ballCoords[i] = alVertix.get(i);
        }
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //将背景设置为灰色
        GLES30.glClearColor(0.5f,0.5f,0.5f,1.0f);

        //编译顶点着色程序
        String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);


        uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
        aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置绘制窗口
        GLES30.glViewport(0, 0, width, height);
        
        //相机和透视投影方式
        //计算宽高比
        float ratio=(float)width/height;
        //设置透视投影
        Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
        //设置相机位置
        Matrix.setLookAtM(mViewMatrix, 0, 6, 0, -1f, 0f, 0f, 0f, 0f, 0.0f, 1.0f);
        //计算变换矩阵
        Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //把颜色缓冲区设置为我们预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //将变换矩阵传入顶点渲染器
        GLES30.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);
        //准备坐标数据
        GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //启用顶点位置句柄
        GLES30.glEnableVertexAttribArray(aPositionLocation);

        //绘制球
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, ballCoords.length/3);

        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(aPositionLocation);
    }
}

里面最重要的是获取球面上顶点位置也就是方法createVertexPos()

绘制结果

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenGL ES之十一——绘制3D图形 的相关文章

  • 找不到不使用 GL11Ext 进行绘图的 android 2d opengl sprite 类的合适示例

    正如 SpriteMethodTest 所说 绘制精灵的方法有很多种 首先 我尝试了canvas 并遇到了一些性能问题 接下来 我决定学习opengl 我使用 GL11Ext 扩展取得了第一个成就 但是 默认情况下您知道 当您绘制纹理时 它
  • 在 OpenGL ES 中绘制立方体需要多少个顶点?

    我在不同的在线站点中看到不同数量的顶点来表示 OpenGL ES 中的同一个立方体 例如 这是一个 float vertices width height depth 0 width height depth 1 width height
  • 使用未声明的标识符“gl_InstanceID”

    大家好 我一直在IOS平台上尝试在OpenGLES2 0中进行实例化绘制 我的渲染代码 glEnableVertexAttribArray glVertexAttribPointer glDrawElementsInstancedEXT G
  • 帮助理解 gluLookAt()

    想象一下你站在地上 抬头看着天空中的一个立方体 当你倾斜头部时 立方体就会移动 我试图在 iPhone 上使用 OpenGL ES 来复制这一点 方法是操纵相机的倾斜 同时查看围绕原点绘制的简单 3D 立方体 我正在使用gluLookAt
  • 具有漫反射和法线贴图纹理的 3D 模型

    我想使用 libgdx 的资源加载器加载具有漫反射纹理和法线贴图的 3D 模型 据我所知 fbx 以及转换后的 g3dj g3db 格式可以包含漫反射纹理 正如我在 fbx conv 示例中看到的那样 骑士 g3db 如何为其添加法线贴图纹
  • libgdx SpriteBatch 渲染到纹理

    是否可以使用 libGdx 适用于 Android 桌面的 Java 引擎 中的 SpriteBatch 渲染到纹理 如果是这样 怎么办 基本上我想将所有内容渲染到 512 x 256 纹理的 320 x 240 区域 然后缩放区域以适合屏
  • iPhone OpenGL ES 单视图还是多视图?

    我很困惑为 iPhone 编写游戏时最好的方法是什么 游戏将使用 OpenGL 渲染 但我很好奇创建开始屏幕 菜单 高分页面等 您是否使用 OpenGL 完成所有这些操作 或者创建额外的 UIView 并使用 UIKit 我认为没有最好的方
  • Android 纹理仅显示纯色

    我正在尝试在四边形上显示单个纹理 我有一个可用的 VertexObject 它可以很好地绘制一个正方形 或任何几何对象 现在我尝试扩展它来处理纹理 但纹理不起作用 我只看到一种纯色的四边形 坐标数据位于 arrayList 中 the ve
  • Cocos2d - 将 GLImageProcessing 效果应用于 CCSprite

    苹果的oplenglGL图像处理 http developer apple com library ios samplecode GLImageProcessing Introduction Intro html加载图像并应用图像调整 亮度
  • 对于某些纹理尺寸,glFramebufferTexture2D 在 iPhone 上失败

    当我尝试将纹理附加到帧缓冲区时 glCheckFramebufferStatus 报告某些纹理大小的 GL FRAMEBUFFER UNSUPPORTED 我已经在第二代和第四代 iPod Touch 上进行了测试 两个模型之间失败的纹理尺
  • OpenGL - 我应该存储属性/统一位置吗?

    Are glGetUniformLocation and glGetAttribLocation耗时 哪种方式更好 Call glGetAttribLocation or glGetUniformLocation每次我需要它 将位置存储在变
  • 从哪里开始使用适用于 Retina 显示屏的 OpenGL 绘制程序

    我知道由于这里提到的错误 我无法将 GLPainter 示例从苹果适应到视网膜 在 Retina iPad 上显示全屏 CAEAGLLayer 时出现问题 https stackoverflow com questions 9757052
  • Android 中的 OpenGL 缩小

    我正在使用 3D 对象并渲染它并通过扩展 GLSurfaceView 实现渲染器来显示它 问题是如何通过捏合和捏合进行缩小 下面是我的班级 package com example objLoader import java nio Byte
  • ios 8 opengl es 1.1 已停产?

    我们即将在 iOS 应用商店上推出一款游戏 最近我们发现它无法在 iOS 8 上运行 游戏加载到黑屏 但其他一切似乎都可以运行 可以听到音乐 对触摸屏有反应 但显示屏上没有任何反应 我们的引擎相当旧并且使用 OpenGL ES 1 1 我现
  • 如何在 Android 上启用 OpenGL 扩展 GL_EXT_shader_framebuffer_fetch?

    我正在使用 OpenGL ES 2 0 开发一个 Android 应用程序 我想使用GL EXT shader framebuffer fetch https www khronos org registry gles extensions
  • 为什么 GDB 调试器不断冻结 Xcode 4?

    这真是一个奇怪的错误 我正在开发一个使用相机源的 iPhone 项目 并通过 OpenGL 着色器运行它以对其应用效果 然而 每次我在代码中简单地创建一个新的 GLfloat 时 调试器都会在启动时冻结 我无法复制和粘贴文本 因为整个 XC
  • iPhone:OpenGL ES:检测您是否点击了屏幕上的对象(立方体)

    我已经问了一个类似的问题 这让我达到了现在的水平 但我真的需要一些帮助 这是我完成一些很酷的事情的最后一件事 在我看来哈哈 我有一个 3D 世界 我可以在其中移动 这个世界里有简单的立方体 使用函数 CGPoint getScreenCoo
  • 允许使用 SurfaceTexture 在 GLSurfaceView 渲染器中进行多通道渲染

    我正在显示视频GLSurfaceView使用需要连续应用多个着色器的自定义渲染器 目前 它可以成功地使用一个着色器 但我不确定如何扩展渲染管道以连续应用多个着色器 我知道有一些关于应用多个着色器的示例 使用FrameBuffers and
  • GLConsumer 已附加到新 SurfaceTexture 的上下文

    Is SurfaceTexture附于GLContext手动创建时默认吗 如果是这样 怎么办 这是一个例子 我正在尝试创建自己的SurfaceTexture并将其设置为TextureView public class MainActivit
  • 如何在 Moderngl EGL 后端添加深度缓冲区?

    此代码渲染一个带有抗锯齿功能的彩色三角形 samples 8 当深度缓冲线depth attachment ctx depth texture 512 512 samples 8 被评论 但是当我添加深度缓冲区时 它会在绑定处返回 GL 错

随机推荐

  • Ubuntu软件包升级失败的终极修复方法

    升级失败 apt upgrade y 尝试修复 apt autoremove Reading package lists Done Building dependency tree Reading state information Don
  • Centos7.6重置root密码

    启动Centos 7 虚拟机 三秒之内在这个系统boot引导界面迅速按e键进入boot编辑模式 如果没有在3秒内按写e 系统正常启动就不会进入到boot编辑模式了 找到以 linux16 开头的行 将从ro开始 ro不要删 往后到下一行前内
  • synchronized原理之前置知识

    一 Monitor概述 一 Java 对象头以 32 位虚拟机为例 一 普通对象 Object Header 64 bits Mark Word 32 bits Klass Word 32 bits 这个可以找到对象 二 数组对象
  • 构造一个简单的操作系统内核,详解进程切换细节

    1 基本功能介绍 如题 本文将介绍如何构造一个简单的操作系统内核 基于内核版本3 9 4 它有以下功能 1 进程的管理 2 进程的初始化 3 进程基于时间片的调度 2 实操步骤 1 安装qemu 以ubuntu为例 sudo apt get
  • jsp记住密码--Cookie

    jsp记住账号密码 本文介绍使用Cookie来实现记住账号密码操作 什么是Cookie Cookie是客户端访问服务器时 服务器在客户端硬盘上存放的信息 Cookie是服务器通知客户端保存键值对的一种技术 Cookie的用途 Cookie可
  • 百度智能云度能推出全新碳盘查服务,助力企业和园区摸清家底实现精细化管理

    今年1月 国务院发布 十四五 节能减排综合工作方案的通知 方案提出到2025年 全国单位国内生产总值能源消耗比2020年下降13 5 能源消费总量得到合理控制 百度也积极履行科技企业减碳责任 于2021年正式公布到2030年实现集团运营层面
  • 测开上手codewhisperer初体验

    AWS新出了一个插件 codewhisperer 这个名字一听还挺有意思 wispiser意为在耳边轻声细语的人 官方解释是一个强大的机器学习AI代码生成器 可以给你一些代码的建议 Amazon CodeWhisperer is a gen
  • 【CV大模型SAM(Segment-Anything)】如何一键分割图片中所有对象?并对不同分割对象进行保存?

    之前的文章 CV大模型SAM Segment Anything 真是太强大了 分割一切的SAM大模型使用方法 可通过不同的提示得到想要的分割目标 中详细介绍了大模型SAM Segment Anything 根据不同的提示方式得到不同的目标分
  • [转]QNX_IDE使用cin输入变量不能编译通过的解决方法

    如果你认为本系列文章对你有所帮助 请大家有钱的捧个钱场 点击此处赞助 赞助额0 1元起步 多少随意 声明 本文只用于个人学习交流 若不慎造成侵权 请及时联系我 立即予以改正 锋影 email 174176320 qq com 在使用QNX
  • 去掉有定位的left值

    left initial 一开始就是初始 默认值 的意思 就可以解决定位的left啦 转载于 https www cnblogs com renxiao1218 p 11611101 html
  • Flink运行时之批处理程序生成计划

    批处理程序生成计划 DataSet API所编写的批处理程序跟DataStream API所编写的流处理程序在生成作业图 JobGraph 之前的实现差别很大 流处理程序是生成流图 StreamGraph 而批处理程序是生成计划 Plan
  • Red Hat Linux 命令Crontab的使用方法

    Red Hat Linux 命令Crontab的使用方法1 cron是一个linux下的定时执行工具 可以在无需人工干预的情况下运行作业 由于Cron 是Linux的内置服务 但它不自动起来 可以用以下的方法启动 关闭这个服务 sbin s
  • Pytorch推出fx,量化起飞

    本文首发于公众号 没事来逛逛 Pytorch1 8 发布后 官方推出一个 torch fx 的工具包 可以动态地对 forward 流程进行跟踪 并构建出模型的图结构 这个新特性能带来什么功能呢 别的不说 就模型量化这一块 炼丹师们有福了
  • Linux下怎么让挂起的(suspend or stopped)进程恢复执行(resume)

    今天在linux安装app的时候 安装的进度长时间停止不前 于是我使用Ctrl Z 打断了安装 然后又运行了一遍安装的命令 这个时候 提示了警告 说这个安装已经安排了但是现在的状态是suspend的 一时间 我想要通过PID把这个进程差掉
  • Windows下 的MySQL安装、配置以及中文字符集编码设置

    一 MySQL安装 第一步 双击程序包 会弹出如下图 第二步 点击Next之后 出现如下图 第三步 选完自己安装的版本 点击Next 第四步 点击Next之后 出现如下图 第五步 点击两次Next之后 显示如下图 根据自己需求改动 一般情况
  • 软件设计师笔记公告(备考攻略)

    软件设计师笔记公告 上午题 下午题 先上成绩 今天软考成绩出来了 很高兴我一次拿下 非常感谢b站up主zst 2001 zst 2001主页链接 我一次性通过很大程度上是因为zst 2001的帮助 于此同时 你们能看到我这个笔记也要感谢up
  • Python 标准库之 xml.etree.ElementTree 简介

    文章来源 https www cnblogs com ifantastic archive 2013 04 12 3017110 html 简介 Element类型是一种灵活的容器对象 用于在内存中存储结构化数据 注意 xml etree
  • pico park无法连接至远程服务器,pico park怎么联机玩?pico park怎么邀请朋友一起玩?[多图]...

    pico park是最近这几天比较火爆的一款游戏了 想必与很多的玩家们 都想与自己的好友一起联机作战 但是又不知道该怎么邀请朋友一起玩 各位新手玩家们 不用太着急 小编这就为大家带来了一套与朋友一起联机玩的教程 只要轻松两步 便可以解决大家
  • C# 命令行参数分割

    CommandLineToArgvW 函数 DllImport shell32 dll SetLastError true private static extern IntPtr CommandLineToArgvW MarshalAs
  • OpenGL ES之十一——绘制3D图形

    概述 这是一个系列的Android平台下OpenGl ES介绍 从最基本的使用最终到VR图的展示的实现 属于基础篇 后面针对VR视频会再有几篇文章 属于进阶篇 OpenGL ES之一 概念扫盲 OpenGL ES之二 Android中的Op