結(jié)合球體和圓環(huán),學(xué)習(xí)復(fù)雜圖形繪制
1. 球體繪制
1.1. 思路
在上一篇筆記中說(shuō)過(guò),OpenGL可以畫點(diǎn)、線和三角形,為了能讓球體有立體顯示的效果我們采用線帶(line_strip)的方式就行繪制.
要想使用OpenGL畫復(fù)雜圖形,最重要的就是分解圖形.
首先看下圖中球體:

分析上面的球體, 我們可以首先對(duì)球體橫向切片,這樣就會(huì)得到若干個(gè)圓臺(tái)(頂部特殊).

拆分圓臺(tái):

當(dāng)我們拆開(kāi)圓臺(tái)之后,在上下圓上打點(diǎn)并連線,形成帶狀:

綜上,畫一個(gè)球,實(shí)際就是把球拆分成一個(gè)個(gè)帶狀拼接起來(lái)形成.
1.2. 求坐標(biāo)
通過(guò)1.1中的球體可以看出,我們無(wú)非就是求得a、b點(diǎn)坐標(biāo),然后讓OpenGL引擎畫出來(lái).
已知半徑R,橫向切片次數(shù)stack,因?yàn)樾枰獧M向切開(kāi)180°,故橫向切角步長(zhǎng)為stackStep = (π/stack).
設(shè)第i次切片,r0為圓臺(tái)的半徑
通過(guò)上述條件求出,圖中 alpha = (-π/2) + (i * stackStep);
所以, y0 = R * sin(alpha); r0 = R*cos(alpha)
接下來(lái)看圓臺(tái)的俯視圖:

設(shè)設(shè)第j次打點(diǎn), 打點(diǎn)次數(shù)為slice,打點(diǎn)范圍為360°,故打點(diǎn)的步長(zhǎng)為sliceStep = (2π/since);
故圖中的beta = j * sliceStep;
可輕松求得: x0和y0的坐標(biāo):
x0 = r0 * cos(beta);
z0 = r0 * sin(beta);
同樣的辦法求得b(x1,y1,z1)的坐標(biāo).
1.3. 核心代碼
通過(guò)上面分析,很容易編寫代碼:
//計(jì)算球體坐標(biāo)
float R = 0.5f;//球的半徑
int stack = 8;//水平層數(shù)
float stackStep = ((float) (Math.PI / stack));//單位角度值 180度
int slice = 12;//豎直
float sliceStep = (float) ((Math.PI*2) / slice);//水平圓遞增角度 360度
//上下兩個(gè)圓的坐標(biāo) 半徑
float r0, r1, x0, x1, y0, y1, z0, z1;
//兩個(gè)切片的夾角
float alpha0 = 0;
float alpha1 = 0;
//切片圓的角度
float beta = 0;
//頂點(diǎn)坐標(biāo)
List<Float> coords = new ArrayList<>();
//外層循環(huán) 水平切片!
for (int i = 0; i <= stack; i++) {
alpha0 = (float) (-Math.PI/2 + (i * stackStep));
alpha1 = (float) (-Math.PI/2 + ((i+1) * stackStep));
y0 = (float) (R * Math.sin(alpha0));
r0 = (float) (R * Math.cos(alpha0));
y1 = (float) (R * Math.sin(alpha1));
r1 = (float) (R * Math.cos(alpha1));
//循環(huán)每一層的圓
for (int j = 0; j <= slice * 2; j++) {
beta = j * sliceStep;
x0 = (float) (r0 * Math.cos(beta));
z0 = -(float) (r0 * Math.sin(beta));
x1 = (float) (r1 * Math.cos(beta));
z1 = -(float) (r1 * Math.sin(beta));
coords.add(x0);
coords.add(y0);
coords.add(z0);
coords.add(x1);
coords.add(y1);
coords.add(z1);
}
}
FloatBuffer fbb = BufferUtil.list2FloatBuffer(coords);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fbb);
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, coords.size()/3);
2. 圓環(huán)繪制
2.1. 思路
如果球體的繪制屢清楚了,圓環(huán)就更不在話下了,同樣還是把圓環(huán)拆分.
如下圖所示, 圓環(huán)就是一個(gè)一個(gè)圓套在一起形成圓環(huán),我們的目標(biāo)就是求得a點(diǎn)坐標(biāo).
為了描述清楚我把圓環(huán)中的圓單獨(dú)也抽在坐標(biāo)系中.


2.2. 求坐標(biāo)
已知條件,內(nèi)環(huán)圓半徑 r, 環(huán)中圓半徑r'.
通過(guò)2.1圖中所示:
x0 = (r + r' + r' * cos(beta)) * cos(alpha);
y0 = (r + r' + r' * cos(beta)) * sin(alpha);
z0 = r' * sin(beta).
同樣的道理再求得下一個(gè)點(diǎn)坐標(biāo).
2.3 核心代碼
//開(kāi)始畫
float Rinner = 0.2f; //內(nèi)環(huán)半徑
float Rring = 0.3f; //環(huán)半徑
int count = 20; //環(huán) 圓與圓之間的循環(huán)次數(shù)
float alphaStep = (float) ((2 * Math.PI) / count);
float alpha;
int countRing = 20;//環(huán)上圓循環(huán)次數(shù)
float betaStep = (float) ((2 * Math.PI) / countRing);
float beta;
float x0, y0, z0, x1, y1, z1;
List<Float> coordsList = new ArrayList<>();
//外層 圓與圓之間
for (int i = 0; i < count; i++) {
alpha = alphaStep * i;
//環(huán)上圓的點(diǎn) (需要閉合)
for (int j = 0; j <= countRing; j++) {
beta = betaStep * j;
x0 = (float) ((Rinner + Rring * (1 + Math.cos(beta))) * Math.cos(alpha));
y0 = (float) ((Rinner + Rring * (1 + Math.cos(beta))) * Math.sin(alpha));
z0 = -(float) (Rring * Math.sin(beta));
x1 = (float) ((Rinner + Rring * (1 + Math.cos(beta))) * Math.cos(alpha + alphaStep));
y1 = (float) ((Rinner + Rring * (1 + Math.cos(beta))) * Math.sin(alpha + alphaStep));
z1 = -(float) (Rring * Math.sin(beta));
coordsList.add(x0);
coordsList.add(y0);
coordsList.add(z0);
coordsList.add(x1);
coordsList.add(y1);
coordsList.add(z1);
}
}
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.list2ByteBuffer(coordsList));
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, coordsList.size()/3);
2.4 預(yù)覽圖
