【Minecraft原理探索】編程繪制旋轉的方塊線框

本文轉載自CSDN博客
原文地址:https://blog.csdn.net/qq_38069872/article/details/66473338?
B站視頻展示:https://www.bilibili.com/video/av24620355/


我對Minecraft十分熱愛,同時也對用簡單的方塊表現(xiàn)世界的原理十分好奇。各種三維軟件的原理必定特別深奧,而簡單的Minecraft蘊含簡單的原理,讓我有了一探究竟的信心。
百科中有這樣一行文字:
“游戲引擎 The Lightweight Java Game Library(LWJGL),基于OpenGL”
我搜索一番卻沒有什么收獲。
以下是我自己的思考。
假設屏幕后面的世界中有一個方塊,從眼睛(以下稱“視點”)向某一個頂點作一條射線,那么射線與屏幕的交點就是該頂點應該在屏幕上顯示的位置,我們要做的就是由該頂點的世界坐標得到它在屏幕上的xy坐標。一個明顯的事實是,一條射線經(jīng)過的任何頂點在屏幕上對應的位置相同。所以,我們可以由此得到啟發(fā),通過這條射線把頂點坐標轉換成屏幕坐標。
過程如下:
世界坐標(x,y,z)
視點相對坐標(x-x0,y-y0,z-z0)
視點相對球坐標(r,s,d)
視線球坐標(r-r0,s-s0,d)
屏幕坐標(x,y)
為了簡便,在計算時首先以視點為原點建立空間直角坐標系,將頂點坐標轉換為與視點的相對坐標,同樣地,射線的始端就位于原點。射線沒有長度,我們最關心的是它的方向,這需要兩個量來表示。一個簡單的例子是,我們玩3D游戲(這里指Minecraft)時,為了移動視角,可以將鼠標上下左右移動,對應抬頭、低頭、左轉、右轉,同時視線的方向隨之變動,我們就可以看向天球上任意一點。這表明,在空間中兩個量可以確定射線的方向————方位角、仰角。那么我們就從基礎做起吧——一個立方體線框的xuanzhua
還是為了簡便,使用左手直角坐標系,即以右、上、前作為x、y、z軸的正方向——畢竟我們看不到身后的事物。方位角以z軸正半軸為始邊,順時針旋轉為正角,逆時針旋轉為負角。過視射線作一個平面垂直于xz平面,視射線與兩平面交線的夾角就是仰角s,即視射線與它在xz平面上的投影的夾角。這里對球坐標的定義和地球經(jīng)緯度的概念差不多。

如圖,我們以這個方塊右下角的頂點為例。首先以正方形中心為坐標原點建立極坐標系,依照定義,以x軸正半軸為始邊,逆時針旋轉為正角,順時針旋轉為負角,這就是這個方塊的自身坐標系。綠線與水平藍線的夾角即為方位角,利用三角函數(shù)求得xz坐標,然后加上正方形中心的坐標轉換為世界坐標。
以下給出球坐標的計算公式,其中距離d是用來判斷前后遮蓋的,本程序用不到。
tanr=x/z
tans=y/sqrt(x*x+z*z)
d=sqrt(x*x+y*y+z*z)
由于本程序沒有旋轉視角的功能,所以略去轉換玩家相對坐標和視線相對球坐標的過程,即視點位于世界原點,視線與z軸重合。
當最終得到方塊頂點相對于視線的球坐標后,我們就要確定這一點在屏幕上的位置。首先,我們的視線必定垂直穿過屏幕的中心,視點與屏幕四角的連線形成了一個錐體。所有位于這個錐體中的頂點都會映射在屏幕上一個確定的位置,即視線與該頂點的連線交于屏幕上的那一點。
如果屏幕是弧形的,圖形渲染就會變得十分簡單,因為屏幕中圖像隨視角變化的變化是均勻的。但我們的屏幕是平直的,所以我們還需要進一步處理。大家玩Minecraft時應該會注意到,屏幕四角的圖形好像被拉伸了一樣,我們可以畫一個圖來解釋這一現(xiàn)象。首先畫一個正方形和它的內切圓,從圓心向與你相對的那條邊作垂線,然后間隔相同的角度向兩側作射線,你會發(fā)現(xiàn)它們在正方形邊上截得的線段由中間向兩側是逐漸變長的。
公式:x=tanr*x0/2*tan(S/2)
推導過程:設視點到屏幕的距離為d,視角為S,屏幕寬度為x0,視射線與屏幕的交點到屏幕中心的距離為x,視射線與中心視線的夾角為r
tan(S/2)=(1/2*x0)/d=x0/2*d
tanr=x/d
d=x0/2*tan(S/2)=x/tanr
x=tanr*x0/2*tan(S/2)
可見,此公式是假定屏幕中心為坐標原點,因此計算結果需要加上屏幕寬度或高度的一半。但是屏幕坐標系的y軸的正方向是向下的,所以需要把頂點的屏幕y坐標乘以-1,于是我干脆把頂點的世界y坐標就以其相反數(shù)賦值。
坐標轉換的過程到此解釋完畢,下面開始動手實踐。
#include<stdio.h>
#include<math.h>
?
int main(void){
int i,j;
double xa,za,r=0;
?
struct POINT {
????double x,y,z;
int xs,ys;
};
struct POINT table[8];
for(i=0;i<4;i++){
table[i].y=20;
}
for(i=4;i<8;i++){
table[i].y=40;
}
FILE *fp=NULL;
fp=fopen("block.txt","w");
if(fp==NULL){
return -1;
}
for(j=0;j<360;j++){
xa=14.1421356*cos(r);? //? 14.1421356=20/sqrt(2)=10*sqrt(2)
za=14.1421356*sin(r);
table[0].x=xa;
table[0].z=80+za;
table[1].x=-za;
table[1].z=80+xa;
table[2].x=-xa;
table[2].z=80-za;
table[3].x=za;
table[3].z=80-xa;
table[4].x=table[0].x;
table[4].z=table[0].z;
table[5].x=table[1].x;
table[5].z=table[1].z;
table[6].x=table[2].x;
table[6].z=table[2].z;
table[7].x=table[3].x;
table[7].z=table[3].z;
?
for(i=0;i<8;i++){
table[i].xs=(int)(table[i].x*482/table[i].z);
table[i].ys=(int)(table[i].y/sqrt(table[i].x*table[i].x+table[i].z*table[i].z)*340);
fprintf(fp,"%d,%d,",table[i].xs+200,table[i].ys+100);
// fprintf(fp,"xa=%f,za=%f,r=%f,i=%d,xs=%d,ys=%d\n",xa,za,r,i,table[i].xs,table[i].ys);
}
r+=0.0174532;? //? 0.0174532=2pi/360
}
fclose(fp);
fp=NULL;
return 0;
}
由于我現(xiàn)在只會寫字符模式程序,對顯示圖形束手無策,所以我使用haribote操作系統(tǒng)(見于《30天自制操作系統(tǒng)》)的API來完成圖形顯示。以下程序中用block.txt的內容為數(shù)組p賦值。
#include "apilib.h"
?
int p[360*16]={};
?
void HariMain(void){
int win,buf,timer,i=0;
api_initmalloc();
buf = api_malloc(150 * 50);
win = api_openwin(buf, 512, 384, -1, "block");
api_boxfilwin(win+1, 5, 24, 506, 378, 0);
timer = api_alloctimer();
api_inittimer(timer, 128);
for (;;) {
api_boxfilwin(win+1, 5, 24, 506, 378, 0);
api_linewin(win+1,p[i],p[i+1],p[i+2],p[i+3],3);
api_linewin(win+1,p[i+2],p[i+3],p[i+4],p[i+5],4);
api_linewin(win+1,p[i+4],p[i+5],p[i+6],p[i+7],5);
api_linewin(win+1,p[i+6],p[i+7],p[i],p[i+1],6);
api_linewin(win+1,p[i+8],p[i+9],p[i+10],p[i+11],7);
api_linewin(win+1,p[i+10],p[i+11],p[i+12],p[i+13],8);
api_linewin(win+1,p[i+12],p[i+13],p[i+14],p[i+15],9);
api_linewin(win+1,p[i+14],p[i+15],p[i+8],p[i+9],10);
api_linewin(win+1,p[i],p[i+1],p[i+8],p[i+9],11);
api_linewin(win+1,p[i+2],p[i+3],p[i+10],p[i+11],12);
api_linewin(win+1,p[i+4],p[i+5],p[i+12],p[i+13],13);
api_linewin(win+1,p[i+6],p[i+7],p[i+14],p[i+15],14);
api_refreshwin(win, 5, 24, 506, 378);
api_settimer(timer, 1);
i+=16;
if(i>=360*16){
i-=360*16;
}
if (api_getkey(1) != 128) {
api_end();
}
}
}

下一步是改進api_linewin,加入抗鋸齒功能,然后再編寫一個填充不規(guī)則四邊形的API。還需繼續(xù)努力!
---------------------?
作者:Flinx_方凌旭?
來源:CSDN?
原文:https://blog.csdn.net/qq_38069872/article/details/66473338?
版權聲明:本文為博主原創(chuàng)文章,轉載請附上博文鏈接!