
自己动手写三维引擎(一)
前言
最近比较忙,所以很久没更新。
之前玩过很多三维沙盒游戏,但是很好奇是如何实现那种三维投射的
于是最近写出来了一个三维引擎
过程
我还是用的qt(自从用qt就发现爱上qt了)去实现打开窗口、画线等功能,但是qt自带的三维支持我不用。
准备工作
导入库、创建qt应用、创建空间对象,都不多说。
1 | from PySide6.QtWidgets import QWidget,QApplication |
点对象和相机对象
组成图形的基本元素是点。为了方便描述三维空间坐标,我创建了一个点对象。
同时,需要一个相机,点会投射到相机上
1 | class Point(): |
1 | class Camera(QWidget): |
投射
这是整个三维投影中最关键的一点。
可见,$\triangle OAE \sim \triangle OFD$, $OA$是相机深度,D是我们要投射的点 ,O是相机的位置,求出BE就可以求出E点的位置,即显示在屏幕上的位置。
从上面看(x坐标)和从右边看(y坐标)这张图都适用。
但是要考虑相机会旋转角度的问题。不妨将相机到投射的点看作一个向量。
假设相机位置为$(a{x},a{y},a{z})$,投射的点的位置为$(c{x},c{y},c{z})$,相机长$w$,宽$h$,深$d$
先计算向量旋转后的新向量:
利用相似三角形对应边成比例,得:
因此我们有了代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# class Point():
def project(self,camera):
cx, cy, cz = camera.posx, camera.posy, camera.posz
thx, thy, thz = -camera.rotatex, camera.rotatey, camera.rotatez
x = self.x - cx
y = self.y - cy
z = self.z - cz
dx = math.cos(thy)*(math.sin(thz)*y+math.cos(thz)*x)-math.sin(thy)*z
dy = math.sin(thx)*(math.cos(thy)*z+math.sin(thy)*(math.sin(thz)*y+math.cos(thz)*x))+\
math.cos(thx)*(math.cos(thz)*y-math.sin(thz)*x)
dz = math.cos(thx)*(math.cos(thy)*z+math.sin(thy)*(math.sin(thz)*y+math.cos(thz)*x))-\
math.sin(thx)*(math.cos(thz)*y-math.sin(thz)*x)
newx= camera.cwidth/2-(camera.cdeepth/dz) * dx
newy= camera.cheight/2-(camera.cdeepth/dz) * dy
if camera.width()/camera.height()>camera.cwidth/camera.cheight:
rectw=camera.height()/camera.cheight*camera.cwidth
resultx=camera.width()/2-rectw/2+newx/camera.cwidth*rectw
resulty=newy/camera.cheight*camera.height()
else:
recth=camera.width()/camera.cwidth*camera.cheight
resultx=newx/camera.cwidth*camera.width()
resulty=camera.height()/2-recth/2+newy/camera.cheight*recth
return resultx,resulty,dz>0#当dz<=0时,即投影的点在相机的后面时,投射的位置不正确,需要特判处理
线段
连接两个点的是线段。将一个线段投影之后线段还是直的,所以只需连接两个投影后的端点即可得到投影后的线段。
1 | class Segment(): |
相机的控制
监听键盘
用WSAD键移动,反引号键(Esc下面那个)呼出呼入鼠标。
1 | # class Camera(QWidget): |
进行移动和旋转操作
刚刚已经改变了self.movement
的值,现在只需要根据值来移动相机,如果鼠标已锁住,同时用鼠标到窗口正中心的偏移来旋转相机,然后把鼠标的位置设置到画面正中心。
当QWidget.update()
被执行时,就会执行paintEvent()
,重写这个函数就可以实现重复执行。
1 | # class Camera(QWidget): |
多面体
将线段相接,得到多面体。
1 | class Body(): |
矩体
矩体是一种特殊的多面体。
1 | class Cubold(Body): |
平面文字
没有什么特别的,但是设置了一个更新时会执行的函数
1 | class Text2D(): |
更新相机画面
把当前相机所在空间中的每一个多面体的每一条棱画出来,同时也也要写文字,执行文字更新是的那个函数
使用QPainter
。
1 | # class Camera(QWidget): |
测试
我把这个文件叫做threedengine.py
。同时建了一个测试文件demo.py
。
测试代码
1 | import threedengine |
效果
到这里这篇文章就结束了,886!