欢迎光临散文网 会员登陆 & 注册

【Minecraft原理探索】编程绘制旋转的方块线框

2019-07-04 15:12 作者:Flinx_方凌旭  | 我要投稿

本文转载自CSDN博客

原文地址:https://blog.csdn.net/qq_38069872/article/details/66473338 

B站视频展示:https://www.bilibili.com/video/av24620355/


我对Minecraft十分热爱,同时也对用简单的方块表现世界的原理十分好奇。各种三维软件的原理必定特别深奥,而简单的Minecraft蕴含简单的原理,让我有了一探究竟的信心。

百科中有这样一行文字:

“游戏引擎 The Lightweight Java Game Library(LWJGL),基于OpenGL”

我搜索一番却没有什么收获。


以下是我自己的思考。


假设屏幕后面的世界中有一个方块,从眼睛(以下称“视点”)向某一个顶点作一条射线,那么射线与屏幕的交点就是该顶点应该在屏幕上显示的位置,我们要做的就是由该顶点的世界坐标得到它在屏幕上的xy坐标。一个明显的事实是,一条射线经过的任何顶点在屏幕上对应的位置相同。所以,我们可以由此得到启发,通过这条射线把顶点坐标转换成屏幕坐标。


过程如下:

世界坐标(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平面上的投影的夹角。这里对球坐标的定义和地球经纬度的概念差不多。


俯视坐标图


如图,我们以这个方块右下角的顶点为例。首先以正方形中心为坐标原点建立极坐标系,依照定义,以x轴正半轴为始边,逆时针旋转为正角,顺时针旋转为负角,这就是这个方块的自身坐标系。绿线与水平蓝线的夹角即为方位角,利用三角函数求得xz坐标,然后加上正方形中心的坐标转换为世界坐标。


以下给出球坐标的计算公式,其中距离d是用来判断前后遮盖的,本程序用不到。


tanr=x/z

tans=y/sqrt(x*x+z*z)

d=sqrt(x*x+y*y+z*z)


由于本程序没有旋转视角的功能,所以略去转换玩家相对坐标和视线相对球坐标的过程,即视点位于世界原点,视线与z轴重合。


当最终得到方块顶点相对于视线的球坐标后,我们就要确定这一点在屏幕上的位置。首先,我们的视线必定垂直穿过屏幕的中心,视点与屏幕四角的连线形成了一个锥体。所有位于这个锥体中的顶点都会映射在屏幕上一个确定的位置,即视线与该顶点的连线交于屏幕上的那一点。


如果屏幕是弧形的,图形渲染就会变得十分简单,因为屏幕中图像随视角变化的变化是均匀的。但我们的屏幕是平直的,所以我们还需要进一步处理。大家玩Minecraft时应该会注意到,屏幕四角的图形好像被拉伸了一样,我们可以画一个图来解释这一现象。首先画一个正方形和它的内切圆,从圆心向与你相对的那条边作垂线,然后间隔相同的角度向两侧作射线,你会发现它们在正方形边上截得的线段由中间向两侧是逐渐变长的。


公式: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坐标就以其相反数赋值。


坐标转换的过程到此解释完毕,下面开始动手实践。


#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;

}

由于我现在只会写字符模式程序,对显示图形束手无策,所以我使用haribote操作系统(见于《30天自制操作系统》)的API来完成图形显示。以下程序中用block.txt的内容为数组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();

}

}

}

QEMU内截图

下一步是改进api_linewin,加入抗锯齿功能,然后再编写一个填充不规则四边形的API。还需继续努力!

--------------------- 

作者:Flinx_方凌旭 

来源:CSDN 

原文:https://blog.csdn.net/qq_38069872/article/details/66473338 

版权声明:本文为博主原创文章,转载请附上博文链接!

【Minecraft原理探索】编程绘制旋转的方块线框的评论 (共 条)

分享到微博请遵守国家法律