【我的安卓進階之旅】Opengl Es(5)三維圖形繪製圓錐、圓柱和球體(附Github地址)

之前的博客中,我們繪製了三角形、正方形、圓形、立方體,今天我們將繪製圓錐、圓柱和球體。能夠繪製這些基本的常規幾何形體後,其他的常見幾何形體的繪製對於我們來說就基本沒問題了。

繪製圓錐

由之前的博客,我們大家也應該都知道了,OpenGL ES2.0中物體的繪製重點就是在於把這個物體表面分解成三角形,分解成功後,繪製自然就不成問題了。圓錐我們很容易就能想到把它拆解成一個圓形和一個錐面,錐面的頂點與圓形的頂點,除了錐面的中心點的座標有了“高度”,其他的完全相同。圓形在【我的安卓進階之旅】Opengl Es繪製多邊形及圓形(附Github地址)中我們已經繪製過,那麼錐面其實對於我們來說也是小case了:

ArrayList<Float> pos=new ArrayList<>();
pos.add(0.0f);
pos.add(0.0f);
pos.add(height);        //給圓心相對圓邊增加高度,使之形成錐面
float angDegSpan=360f/n;
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(0.0f);
}
float[] d=new float[pos.size()];    //所有的頂點
for (int i=0;i<d.length;i++){
    d[i]=pos.get(i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我們按照繪製圓形的方式,繪製出錐面,然後再在這個錐面的底部繪製一個圓形,這樣我們就得到了一個圓錐了:

主要的代碼變化
    @Override
    public void onDrawFrame(GL10 gl) {
        //GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
        //告知OpenGL所要使用的Program
        GLES20.glUseProgram(mProgramId);
        //啓用指向三角形頂點數據的句柄
        GLES20.glEnableVertexAttribArray(mPositionId);
        drawStep1();
        drawStep2();
        //禁用指向三角形的頂點數據
        GLES20.glDisableVertexAttribArray(mPositionId);
    }
    //繪製錐面
     public void  drawStep1(){
        //綁定三角形的座標數據
        GLES20.glVertexAttribPointer(mPositionId, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                VERTEX_STRID, vertexBuffer);

        //綁定顏色數據
        GLES20.glUniform4fv(mColorId, 1,color , 0);

        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);

        //索引法繪製正方形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, shapePos.length/3);

    }

	//繪製底面
    public void  drawStep2(){
        //綁定三角形的座標數據
        GLES20.glVertexAttribPointer(mPositionId, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                VERTEX_STRID, vertexBuffer2);

        //綁定顏色數據
        GLES20.glUniform4fv(mColorId, 1,color , 0);

        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);

        //索引法繪製正方形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, circlePos.length/3);
    }

運行程序

圓錐

從圖中我們可以看到,我們繪製的是同樣的顏色,很難看出圓錐的立體效果。要達到立體效果,我們來修改它的着色器:

    private final String VERTEX_SHADER =
            "attribute vec4 vPosition;" +
                    "uniform mat4 vMatrix;"+
                    "varying vec4 vColor;"+
                    "void main() {" +
                    "  gl_Position = vMatrix*vPosition;" +
                    "   if(vPosition.z!=0.0){\n" +
                    "        vColor=vec4(1,1,1,1.0);\n" +
                    "    }else{\n" +
                    "        vColor=vec4(1,1,1,1.0);\n" +
                    "    }"+
                    "}";

在頂點着色器中,並沒有傳入顏色,而是在程序中直接判斷進行賦值的,當然也有可以頂點顏色和定邊顏色由外面傳入。在着色器中,我們不再是簡單的賦值,而是加入了流程控制。之後,我們在學習下GLSL語言。 我們看下修改後的效果

圓錐

怎麼樣,看起來是不是更真實了?

繪製圓柱

圓柱的與圓錐類似,我們可以把圓柱拆解成上下兩個圓面,加上一個圓筒。圓筒我們之前也沒畫過,它怎麼拆解成三角形呢?我們可以如同拆圓的思路來理解圓柱,想想正三菱柱、正八菱柱、正一百菱柱……菱越多,就越圓滑與圓柱越接近了,然後再把每個菱面(矩形)拆解成兩個三角形就OK了,拆解的頂點爲:

ArrayList<Float> pos=new ArrayList<>();
float angDegSpan=360f/n;
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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這樣我們就可以繪製出一個圓筒了,只需要在頂部繪製一個圓,底部繪製一個圓,就得到了一個圓柱了:
圓柱

繪製球體

相對於圓錐圓柱來說,球體的拆解就複雜了許多,比較常見的拆解方法是將按照經緯度拆解和按照正多面體拆解,下圖分別爲正多面體示意和經緯度拆解示意:

  • 正多面體的方法拆解:
    多面體
  • 經緯度的方法拆解(每一個小塊看做一個矩形,再拆成三角形。PS:人懶,不想做圖。):
    這裏寫圖片描述

由圖我們也能看出來,多面體雖然看起來好看點,但是還是按照經緯度的方式來拆解計算容易點,畢竟規律那麼明顯。

球上點的座標

無論是按照經緯度拆還是按照多面體拆,都需要知道球上面點的座標,這算是基本的幾何知識了。以球的中心爲座標中心,球的半徑爲R的話,那麼球上點的座標則爲:

(Rcos(ψ)sin(λ),Rsin(ψ),Rcos(ψ)cos(λ))(R∗cos(ψ)∗sin(λ),R∗sin(ψ),R∗cos(ψ)∗cos(λ))

其中,ψ爲圓心到點的線段與xz平面的夾角,λ爲圓心到點的線段在xz平面的投影與z軸的夾角。用圖形表示如下:
這裏寫圖片描述

拆解頂點

按照經緯度方式拆解球體,得到球體的頂點數組:

ArrayList<Float> data=new ArrayList<>();
float r1,r2;
float h1,h2;
float sin,cos;
for(float i=-90;i<90+step;i+=step){
    r1 = (float)Math.cos(i * Math.PI / 180.0);
    r2 = (float)Math.cos((i + step) * Math.PI / 180.0);
    h1 = (float)Math.sin(i * Math.PI / 180.0);
    h2 = (float)Math.sin((i + step) * Math.PI / 180.0);
    // 固定緯度, 360 度旋轉遍歷一條緯線
    float step2=step*2;
    for (float j = 0.0f; j <360.0f+step; j +=step2 ) {
        cos = (float) Math.cos(j * Math.PI / 180.0);
        sin = -(float) Math.sin(j * Math.PI / 180.0);

        data.add(r2 * cos);
        data.add(h2);
        data.add(r2 * sin);
        data.add(r1 * cos);
        data.add(h1);
        data.add(r1 * sin);
    }
}
float[] f=new float[data.size()];
for(int i=0;i<f.length;i++){
    f[i]=data.get(i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

得到頂點後,剩下的工作就和之前繪製其他圖形一樣了。

修改着色器

如果繼續使用圓錐的着色器,我們會得到這樣一個球:

黑球

看起來都不太像個球了,要不是有條白線,這是不是個球就不好說了。我們需要修改下頂點着色器,讓它有立體感。把頂點着色器修改爲:

uniform mat4 vMatrix;
varying vec4 vColor;
attribute vec4 vPosition;

void main(){
    gl_Position=vMatrix*vPosition;
    float color;
    if(vPosition.z>0.0){
        color=vPosition.z;
    }else{
        color=-vPosition.z;
    }
    vColor=vec4(color,color,color,1.0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

運行一下,我們得到的運行結果如下,這樣纔好意思說是個球嘛。
這裏寫圖片描述

源碼

OK,繪製各種簡單的幾何物體到這裏就結束了,現在應該各種常規的幾何形體都攔不到我們了。後面開始講解其他內容了。

源碼

附上github下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章