用c++实现3d引擎

您所在的位置:网站首页 用3d做一个简易卧室 用c++实现3d引擎

用c++实现3d引擎

2024-07-16 20:04| 来源: 网络整理| 查看: 265

upd:2024/1 重写全部 b l o g blog blog

前言

这篇文章介绍如何用最简单的c++代码(只有100行)实现3d线框渲染 思路主要来自SPixel 3d教程网站(英文) 重要:关于前置数学(如矩阵、线性代数)请参见 这个 \boxed{这个} 这个​ 下一篇:渲染贴图

效果图

实现了第一人称视角移动等等 在这里插入图片描述

C++如何输出图形?

需要ege库,但是easyx也可(graphics.h) 或者不用图形库,输出到svg文件也可,需要自行更改代码

可以下载 小熊猫 c + + ( 直接带有 e a s y x ) \boxed{小熊猫c++(直接带有easyx)} 小熊猫c++(直接带有easyx)​或下载 e a s y x ( V S 或 M i n G W ) \boxed{easyx(VS或MinGW)} easyx(VS或MinGW)​

本文使用了EGE

Easyx/EGE的使用可参见:这个

如何获得看到的图像

假设我们的屏幕是棱锥的底面 在这里插入图片描述 我们的眼睛看到的物体位置可以转化为 连接眼睛与物体的直线与屏幕的交点 在这里插入图片描述

原理

将顶点投影到屏幕上,这个过程我们称之为透视投影。 在这里插入图片描述

如果垂直于x轴看,可以发现 △ A B C \triangle ABC △ABC与 △ A B ′ C ′ \triangle AB'C' △AB′C′相似 在这里插入图片描述

注意我们的屏幕处于 z z z的负半轴,并且假定焦距 A B ′ = 1 AB'=1 AB′=1 于是有 B C A B = B ′ C ′ A B ′ → P ′ . y = P . y − P . z \dfrac{BC}{AB}=\dfrac{B^{\prime}C^{\prime}}{AB^{\prime}}\to P^{\prime}.y=\dfrac{P.y}{-P.z} ABBC​=AB′B′C′​→P′.y=−P.zP.y​ 以上是透视投影的原理,你可以略过下面的内容(下面的内容是具体实现)

现在我们需要注意屏幕的大小(显示屏),应该根据假想屏幕(如下图)变化到真实屏幕里 在这里插入图片描述

现在我们的投影后坐标会在这个假定的屏幕里,即应该满足 x , y ∈ [ − 1 , 1 ] x,y\in[-1,1] x,y∈[−1,1],当然屏幕外的点应该舍去不显示 将 [ − 1 , 1 ] → [ 0 , 长 ] 和 [ 0 , 宽 ] [-1,1]\to[0,长]和[0,宽] [−1,1]→[0,长]和[0,宽], 这里引入NDC空间,即,将点从它们最初所在的范围转换为范围 [ 0 , 1 ] [0,1] [0,1] 在这里插入图片描述

我们可以采用 x ′ = ( 1 + x ) / 2 × 长, y ′ = ( 1 + y ) / 2 × 宽 x'=(1+x)/2\times长,y'=(1+y)/2\times宽 x′=(1+x)/2×长,y′=(1+y)/2×宽

可见性

如何处理立方体哪个面可见,哪个面不可见,就是可见性问题 我们可以按照点的 z z z值从大到小(从后到前)进行计算,以避免此问题

透视投影的具体计算 第一步

将物体从世界坐标系变换到相机坐标系(因为相机会移动和旋转) 在这里插入图片描述 为了计算,我们需要引入矩阵(看不懂就看代码)

现在假设相机坐标系为 M 相机 M_{相机} M相机​,这是一个局部坐标系, 于是我们只要用世界坐标乘以相机坐标系即可得到相机坐标 这一步相当于 P 世界 × M 世界 − 相机 = P 相机 P_{世界}\times M_{世界-相机}=P_{相机} P世界​×M世界−相机​=P相机​ 相反的,我们要求的 P 世界 = P 相机 / M 相机 P_{世界}=P_{相机}/ M_{相机} P世界​=P相机​/M相机​

在这里插入图片描述

所以将物体从世界坐标系变换到相机坐标系的结果 P ′ = P M 相机 P'=\dfrac P{M_{相机}} P′=M相机​P​

第二步

现在,我们可以使用相机空间中的点坐标,通过使用透视投影方程 P ′ . y = P . y − P . z P^{\prime}.y=\dfrac{P.y}{-P.z} P′.y=−P.zP.y​来计算其在屏幕上的坐标。

即:将 P ′ P' P′变换到屏幕坐标系 P ′ ′ P'' P′′ 但是要注意 P ′ ′ P'' P′′的可见性 { 可见 ∣ P ′ . x ∣ ≤ W 2 and ∣ P ′ . y ∣ ≤ H 2 不可见 otherwise \begin{cases}可见&|P'.x|\leq\frac{W}{2}\text{and}|P'.y|\leq\frac{H}{2}\\不可见&\text{otherwise}\end{cases} {可见不可见​∣P′.x∣≤2W​and∣P′.y∣≤2H​otherwise​ 注意: P ′ ′ P'' P′′在以后仍记为 P P P

第三步

注意显示屏为 [ 0 , W ] × [ 0 , H ] [0,W]\times[0,H] [0,W]×[0,H],而现在的 P P P点在 [ − W / 2 , W / 2 ] × [ − H / 2 , H / 2 ] [-W/2,W/2]\times[-H/2,H/2] [−W/2,W/2]×[−H/2,H/2] 所以要将 P P P变换到显示屏坐标系

(1)对 P P P归一化(NDC标准空间),即将 x , y x,y x,y相对于长宽,化为 [ 0 , 1 ] [0,1] [0,1] P 归一化 . x = P . x + W / 2 W P 归一化 . y = P . y + H / 2 H \begin{aligned}P_{归一化}.x&=\frac{P.x+W/2}{W}\\P_{归一化}.y&=\frac{P.y+H/2}{H}\end{aligned} P归一化​.xP归一化​.y​=WP.x+W/2​=HP.y+H/2​​ (2)注意显示屏的 y y y向下,于是有 P 栅格 . x = ⌊ P 归一化 . x ∗ W 像素 ⌋ P 像素 . y = ⌊ ( 1 − P 归一化 . y ) ∗ H 栅格 ⌋ \begin{array}{l}P_{栅格}.x=\lfloor P_{归一化}.x*W_{像素}\rfloor\\P_{像素}.y=\lfloor(1-P_{归一化}.y)*H_{栅格}\rfloor\end{array} P栅格​.x=⌊P归一化​.x∗W像素​⌋P像素​.y=⌊(1−P归一化​.y)∗H栅格​⌋​

具体代码怎么写?

在这里插入图片描述

#include #include float pi = 3.14159265; struct Vec2i{int x, y;}; struct Vec2f{float x, y;}; template class Vec3 { public: Vec3() : x(T(0)), y(T(0)), z(T(0)) {} Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} Vec3 operator + (Vec3 &v) {return Vec3(x + v.x, y + v.y, z + v.z);} Vec3 operator - (Vec3 &v){return Vec3(x - v.x, y - v.y, z - v.z);} Vec3 operator * (T &r){return Vec3(x * r, y * r, z * r);} Vec3 operator * (Vec3 &v){return Vec3(x * v.x, y * v.y, z * v.z);} /*绕x旋转*/void rotate_point3d_x(float R) {T new_y = y * cos(R) - z * sin(R);T new_z = y * sin(R) + z * cos(R);y = new_y;z = new_z;} /*绕y旋转*/void rotate_point3d_y(float R) {T new_x = x * cos(R) + z * sin(R);T new_z = -x * sin(R) + z * cos(R);x = new_x;z = new_z;} /*点乘*/T dotProduct(Vec3 &v){return x * v.x + y * v.y + z * v.z;} Vec3& operator *= (T &r){x *= r, y *= r, z *= r;return *this;} /*叉乘*/Vec3 crossProduct(Vec3 &v){return Vec3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x);} /*模长平方*/T norm(){return x * x + y * y + z * z;} /*模长*/T length(){return sqrt(norm());} /*下标访问*/const T& operator [] (uint8_t i) const{return (&x)[i];} T& operator [] (uint8_t i){return (&x)[i];} /*归一化*/Vec3& normalize(){T n = norm();if (n > 0){T factor = 1 / sqrt(n);x *= factor, y *= factor, z *= factor;}return *this;} friend Vec3 operator * (const T &r, const Vec3 &v){return Vec3(v.x * r, v.y * r, v.z * r);} friend Vec3 operator / (const T &r, const Vec3 &v){return Vec3(r / v.x, r / v.y, r / v.z);} friend std::ostream& operator


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3