封面图
目录
0.0前言
终于决定要正经写点博客了。水了六篇,我自己都看不下去了
这次我可是认真的!
今天这篇博客希望能对大家有所帮助
我的博客主要面对新人,尤其是学生党
所以如果觉得优秀的话请三连,三连,三连支持一下博主哦!
1.0介绍与环境配置
首先,你需要确保你的电脑上下载好了EGE库并成功配置了C++编译器,如果还没有,可以直接去EGE官网下载。教程网上都有,比较简单,这里我就不多做赘述了。
这里,作者用的是Dev-C++,用小熊猫或VSC++理论上也可以(我没试过)。其实编译器无所谓,重要的是学到了知识
一个好的游戏引擎将会包含很多内容,而我做的这一个并不会像unity3D等等一样,但是自己做一个小3D游戏玩一玩还是绰绰有余的,我做的工作就是让新人和一些没有多少时间从头做的人也可以轻松搭建,同时也教给大家一些相关的基础知识。
*unity3D:
(*图片来自于网络,侵权立删)
1.1点的投影
1.10位置
点的投影是一个3D引擎最基础的部分了,我们知道,当玩家“看”向一个点时,这个点是根据近大远小近疏远密的规律投影到屏幕上的。但问题是,具体投影在那里呢?
直接上图:
如图,假设玩家正对着一面墙,我们可以看到,因为横向视野是120度,所以玩家能看到的距离刚好是与这堵墙距离的四倍(30度角所对的直角边是斜边的一半)
那么这堵墙上的点的X坐标投影在屏幕上的坐标刚好(由相似三角形)就是:
现在的x坐标=原来的x坐标*屏幕宽度/(与“墙”的距离*4)
与“墙”的距离就是点的z坐标
然后再转换为屏幕坐标系即可(即加上屏幕宽度的一半)
伪代码如下:
int x轴投影(坐标){
double 原来的x坐标*=屏幕宽度/(z坐标*4);
int 现在的x坐标=原来的x坐标;
double 不重要的变量= 原来的x坐标-现在的x坐标;
if(不重要的变量>=0.5){//四舍五入
现在的x坐标++;
}
现在的x坐标+= 屏幕宽度/2;
return 现在的x坐标;
}
纵向视角同理,因为是90度的视野,所以玩家能看到的距离就是与z坐标的两倍(等腰直角三角形的定义)
因此:
现在的y坐标=原来的y坐标*屏幕长度/(z坐标*2)
然后再转换为屏幕坐标系……
慢着!
在这里,有一个灰常需要注意的点:
屏幕坐标系
刚刚没有详细地讲,现在我们把它展开来讲。
我们在数学的学习中,平面直角坐标系的坐标轴方向是:
水平向右为x轴正方向,垂直向上为y轴正方向
如图:
(*图片来自于网络,侵权立删)
可是,屏幕坐标系却和平面直角坐标系不一样:
(*图片来自于网络,侵权立删)
水平向右为x轴正方向,垂直向下为y轴正方向
这个顺序和我们阅读时从左到右,从上到下的顺序是一致的
所以,我们需要将它归位
然后来过转倒
(*图片来自于网络,侵权立删)
伪代码如下:
int y轴投影(坐标){
double 原来的y坐标*=屏幕高度/(z坐标*4);
int 现在的y坐标=原来的y坐标;
double 不重要的变量= 原来的y坐标-现在的y坐标;
if(不重要的变量>=0.5){
现在的y坐标++;
}
现在的y坐标+=屏幕高度/2;//归位
现在的y坐标=屏幕高度-现在的y坐标;//倒转
return 现在的y坐标;
}
1.11旋转
玩家视角的旋转可以说是3D游戏的精髓,如果没有就太没意思了
不过作者用过一款3D打印建模的软件,还真就没有视角旋转,也是特殊情况特殊讨论吧
(不过用起来是真的难受)
如图,玩家向右旋转后就相当于O向左旋转得到O'
可以看到,点O的x坐标和z坐标都发生了改变
但是点O与玩家的距离dis并没有改变
于是我们就可以先把点O与玩家的距离dis求出来(后面要用)
代码如下:
double dis(double x1,double x2,double y1,double y2)//两个坐标之间的距离
{
double a,b,c,d;
b=x1-x2;
if(b<0)b=-b;//求绝对值
c=y1-y2;
if(c<0)c=-c;//求绝对值
a=sqrt(b*b+c*c);//勾股定理
return a;
}
然后呢?
然后,就不在光锥之内了……【doge】
三角函数
直接上图:
图上讲得非常清楚,我们要用数学的方法解决
Tip:三角函数
三角函数是数学中的一种特殊函数,它们与三角形的角度有关,常用的三角函数有正弦(sin)、余弦(cos)、正切(tan)、余切(cot)、正割(sec)和余割(csc)等。其中,正弦和余弦是最为常用的两种三角函数。
正弦(sin)是一个周期性函数,其定义域为实数集合,值域为闭区间[-1,1]。在直角三角形中,正弦值表示对角上的斜边与直角边的比值。即,如果一个直角三角形中,一个锐角的度数为x,则该角的正弦值就等于对边长度与斜边长度的比值。例如,在一个角为30度的直角三角形中,该角的正弦值为0.5,即对边长度等于斜边长度的一半。
余弦(cos)也是一个周期性函数,其定义域为实数集合,值域为闭区间[-1,1]。在直角三角形中,余弦值表示该角的邻边与斜边的比值。即,如果一个锐角的度数为x,则该角的余弦值就等于邻边长度与斜边长度的比值。例如,在一个角为30度的直角三角形中,该角的余弦值为√3/2,即邻边长度等于斜边长度的√3/2倍。
正弦和余弦函数是数学中的重要概念,在各种领域都有着广泛的应用。熟练掌握正弦和余弦函数的定义和性质,能够帮助我们更好地理解和应用数学知识。
那么,我们就把这部分写成代码吧
double dis(double x1,double x2,double y1,double y2)//两个坐标之间的距离
{
double a,b,c,d;
b=x1-x2;
if(b<0)b=-b;//求绝对值
c=y1-y2;
if(c<0)c=-c;//求绝对值
a=sqrt(b*b+c*c);//勾股定理
return a;
}
//三维点坐标投影到二维屏幕上的x坐标:玩家坐标:x,y;物体(点)坐标:x,y;玩家横向视角旋转角度
int screen_x(double px,double py,double ox,double oy,int ax){
double rex,rez;
int retx;
rex=ox-px;//求相对位置
rez=oy-py;
rex+=dis(px,ox,py,oy)*sin(ax);//用三角函数计算
rez-=dis(px,ox,py,oy);
rez+=dis(px,ox,py,oy)*cos(ax);
//...
return retx;
}
//三维点坐标投影到二维屏幕上的y坐标:玩家坐标:y,z;物体(点)坐标:y,z;玩家纵向视角旋转角度
int screen_y(double py,double pz,double oy,double oz,int ay){
double rey,rez;
int rety;
rey=oz-pz;//求相对位置
rez=oy-py;
rey+=dis(pz,oz,py,oy)*sin(ay);//用三角函数计算
rez-=dis(pz,oz,py,oy);
rez+=dis(pz,oz,py,oy)*cos(ay);
//...
return rety;
}
1.2渲染单个像素
在完成了点的投影后,我们就来尝试单个像素的渲染
这时候肯定有人说了:渴望渴望,单个像素不就是一个点吗?有什么好渲染的?
这话说的有一定道理,但是并不尽然
我说的像素,并不是指屏幕上的像素,而是指你要3D渲染的目标(如物体,贴图)上最小的单位
这个单位可以很小,那么渲染出来的效果就会很细腻
(*图片来自于网络,侵权立删)
如果配置较低(比如作者)或者想做一个马赛克游戏,也可以很大
就像Minecraft(我的世界)一样,也很不戳
Minecraft(我的世界):
(*图片来自于网络,侵权立删)
那么就让我们开始吧~
1.20正常渲染
没错,正常渲染就是你们想象的正常渲染,就是正儿八经的正常的正常渲染(废话文学)
那具体是什么样子呢?
请看VCR:
不就封面图吗这?
对~(傻笑)
在这里——
注意看!这个男人叫小帅
不是,咳咳(尴尬)
注意看右边的这个小正方体的正面,没戳就是哪里
跟我来——它是个四边形对不对?EGE填充多边形函数知道不?用它!(等下我查个资料)
咳咳——高级绘图不就是这样吗?
void ege_fillpoly(int n, const int *x, const int *y);
其中,n表示多边形的顶点数,x和y分别是长度为n的数组,表示多边形各个顶点的x坐标和y坐标
直接把刚刚的函数套进去不就可以了吗?
至于为什么用填充多边形而不是直接用四边形,这是因为会有特殊情况
1.21特殊情况
那么就遇到特殊情况了
在屏幕外面的像素怎么办呢?
有人说,那好办,直接用一个语句判断简单粗暴的把它们剔除不渲染就行了
然而真的这么简单吗?
废话不说了,毕竟听君一席话,如听一席话
请看VCR:
这不又封面图吗这?
对~(傻笑)
注意看右边的这个小正方体的上面和侧面,没戳就是哪里
是不是……有一部分在屏幕外面?
如果这时候还像上文说的那样,简单粗暴地把它们剔除不渲染
像素小的话还可以将就着看看,像素大的话——
可能导致穿模,透视等3D游戏里经常出现的BUG!
(*图片来自于网络,侵权立删)
敌来将挡,水来土掩,有BUG就别管[手动狗头]
那我们就要计算像素与屏幕的交点了
一次函数
没错,又双叒要用到数学了,希望你还没有像屏幕前的我一样累到趴下
这里我就简单讲讲了,因为很简单,我也是困到不行(Tip:熬夜伤身哦,请勿模仿)
先判断是不是这个像素里一共八个数据全都超出了屏幕,如果是,那么根本就不用考虑
那要是出现了封面图左边的情况怎么办?四个角都超出了屏幕,但实际上还是渲染的
正常情况下是不会出现这种情况的,但也不能不考虑
但是这样,你可以发现,最左边的点的Y坐标在屏幕以内,最下边的点的X坐标在屏幕以内,等等,这都还是可以计算到的
回归正题,我们要用屏幕内的点的坐标和屏幕外的点的坐标求出一个一次函数的解析式,再计算它与坐标轴的截距
(*图片来自于网络,侵权立删)
从而计算出屏幕上渲染的部分
这就是用填充多边形而不是直接用四边形的原因了
1.3写入头文件
理论存在,实践开始~
除了刚刚介绍的函数以外,还要一些像初始化之类的函数
我编的时候用了#error关键字,用来检查一个宏常量是否被定义了
#if !defined stick_pixel//如果 stick_pixel未定义就报错
#error 你忘记定义贴图像素stick_pixel啦!仔细检查一下
#endif
然后就是和之前差不多的操作,详见我的专栏
BUG
没错,我又双叒遇到BUG了
我记得上次遇到BUG还是在上次
这次的BUG是:
error:id returned 1 exit
出现在我用了一个test.cpp测试头文件时(毕竟头文件不能直接测试)
我看到它的第一反应就是,上次运行的程序没关!
但好像不是,于是就跑到编译日志里看,这才知道,原来我用了一个easyx的函数,这玩意显示未定义
void solidpolygon(
const POINT *points,
int num
);
希望大家不要进和我一样的坑
尾声
结尾了,有些读者要问了:
效果呢?完整代码呢?
别急,一是因为代码还没完成,效果出来肯定不会好
二是因为我家这台Win7老笔记本电脑(和我一样大,大家可以掂量掂量到底有多老)没有录屏功能,光看截屏有什么意思?以后有机会再录吧~
这篇文章是我写的时间跨度最长的文章之一,用了足足一个多月的时间(从十月写到十二月)
也是呕心沥血写出来的一篇(哈哈)
几乎都是手码出来的字,没有什么复制的内容
所以……都看到文章结尾了,能支持一下博主吗?
你的支持是我最大的动力!
(未完待续哦~)
2023年12月3日