概述
从体绘制绕路过来的,三维重建,网格处理,鼠标交互,开始找不到路,边走边问,最后到了光栅渲染器,应该是这里吧?这个坑挖好久了,试试能填多少。这里没有公式和算法,网上和参考文献中都有详细的解释,不再重复内容。 --2020年4月9日
其实有点累了,有点想放弃,再坚持一下。 --2020年4月23日
这坑有点大,适当收敛一下。 --2020年4月24日
基本有个交代了,暂时就到这里。–2020年4月28日
第一步 将韦大的代码移植到gcc下,IDE用QtCreater。–2020年4月8日
#ifdef _MSC_VER
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#endif
//改成了
LIBS += libgdi32
libuser32
既然windows下的Qt也是win32扩展,那么win32那一套在Qt下也是可用的,so,win32 api 和 Qt api 混用也可以。目前将msc平台下的代码移植到gcc下运行没问题,可移植的最大原因是代码本来就跨平台。
第二步 代码抽离封装,并增加一些功能。–2020年4月9日
- 添加了圆柱
- 添加了读取点集后线
- 添加了读取中心线后管状物
第三步 代码封装管线机制,并添加鼠标交互 --2020年4月11日
封装完成后扩展性和易读性好了很多。
只是将原来的键盘交互改成了鼠标响应,并没有实质性的变动,离真正的轨迹球模式还有一段路要走。
鼠标响应借用了Qt的mouse event。
第四步 添加了轨迹球,光照,相机 --2020年4月21日
重写了相机。
将原来的2个方向扩展N个方向的轨迹球。
加入了一个实现不太好的光照。
第五步 3D线框渲染流水结构梳理
回到最初的代码,去掉封装,看3D线框变化原理
1、摄像机是一个4X4的矩阵
// 设置摄像机:创建左手坐标系的观察矩阵
void struct_demo::matrix_set_lookat(matrix_t *m, //相机坐标
const vector_t *eye, //eye:相机所在的位置,
const vector_t *at, //at:相机到目标的向量,默认{ 0, 0, 0, 1 }
const vector_t *up) //up:向上的方向向量,这里用 { 0, 0, 1, 1 }
{
vector_t xaxis, yaxis, zaxis;
vector_sub(&zaxis, at, eye);//zaxis = at - eye
vector_normalize(&xaxis);//zaxis归一化
vector_crossproduct(&xaxis, up, &zaxis);// xaxis = up x zaxis
vector_normalize(&xaxis);//xaxis归一化
vector_crossproduct(&yaxis, &zaxis, &xaxis);//yaxis = zaxis x xaxis
m->m[0][0] = xaxis.x;
m->m[1][0] = xaxis.y;
m->m[2][0] = xaxis.z;
m->m[3][0] = -vector_dotproduct(&xaxis, eye);//xaxis * eye
m->m[0][1] = yaxis.x;
m->m[1][1] = yaxis.y;
m->m[2][1] = yaxis.z;
m->m[3][1] = -vector_dotproduct(&yaxis, eye);//yaxis * eye
m->m[0][2] = zaxis.x;
m->m[1][2] = zaxis.y;
m->m[2][2] = zaxis.z;
m->m[3][2] = -vector_dotproduct(&zaxis, eye);//zaxis * eye
m->m[0][3] = 0.0f;
m->m[1][3] = 0.0f;
m->m[2][3] = 0.0f;
m->m[3][3] = 1.0f;
}
左手坐标系的观察矩阵:
eye:相机所在的位置
at:相机到目标的向量
up:向上的方向向量,书中介绍时为[0,1,0],而很多地方用[0,-1,0]
观察坐标系的z轴为:zaxis = normal(at - eye)
观察坐标系的x轴为:xaxis = normal(cross(up,zaxis))
观察坐标系的z轴为:yaxis = cross(zaxis,xaxis)
其中:normal为使单位向量化,cross为求两向量的法向量(单位向量)
dot为:轴 * eye.x + 轴 * eye.y + 轴 * eye.z
创建的矩阵为:
观察矩阵 | |||
---|---|---|---|
xaxis.x | yaxis.x | zaxis.x | 0 |
xaxis.y | yaxis.y | zaxis.y | 0 |
xaxis.z | yaxis.z | zaxis.z | 0 |
-dot(xaxis,eye) | -dot(yaxis,eye) | -dot(zaxis,eye) | 1 |
在物体视角变换的过程中,全程只用了一个camera_at_zero函数,而这个函数也只调整了一个参数eye,即相机所在的位置:
void struct_demo::camera_at_zero(device_t *device, float x, float y, float z)
{
point_t eye = { x, y, z, 1 }, at = { 0, 0, 0, 1 }, up = { 0, 0, 1, 1 };
matrix_set_lookat(&device->transform.view, &eye, &at, &up);
//一旦调整相机位置,马上刷新观察矩阵。
transform_update(&device->transform);
}
这里定义了坐标变换,最重要的是 transform = world * view * projection:
typedef struct {
matrix_t world; // 世界坐标变换
matrix_t view; // 摄影机坐标变换
matrix_t projection; // 投影变换
matrix_t transform; // transform = world * view * projection
float w, h; // 屏幕大小
} transform_t;
旋转是物体自传(如果是相机转呢?物体静止不动,细细想来,真是情况中两种情况都是存在的,应用场景不同),缩放是相机近大远小:
int main(int argc, char *argv[])
{
……
device_t device;
int indicator = 0;
int kbhit = 0;
float alpha = 1;
float pos = 3.5;
……
if (screen_init(800, 600, title))
return -1;
struct_demo *demo = new struct_demo;
demo->device_init(&device, 800, 600, screen_fb);//给出一个初始化800*600大小的画布
demo->camera_at_zero(&device, 3, 0, 0);//给出一个初始化的相机,不然看不到。其实就是虚拟与真实世界的抽象和封装。
//demo->init_texture(&device);//给出一个初始化的纹理,如果不用纹理,可以注释
device.render_state = RENDER_STATE_WIREFRAME;
while (screen_exit == 0 && screen_keys[VK_ESCAPE] == 0) {
screen_dispatch();
demo->device_clear(&device, 1);//清空整个窗口画布
demo->camera_at_zero(&device, pos, 0, 0);//调整相机位置
if (screen_keys[VK_UP]) pos -= 0.01f;//这部分全是键盘输入参数
if (screen_keys[VK_DOWN]) pos += 0.01f;
if (screen_keys[VK_LEFT]) alpha += 0.01f;
if (screen_keys[VK_RIGHT]) alpha -= 0.01f;
if (screen_keys[VK_SPACE]) {
if (kbhit == 0) {
kbhit = 1;
if (++indicator >= 3) indicator = 0;
device.render_state = RENDER_STATE_WIREFRAME;
}
} else {
kbhit = 0;
}
demo->draw_box(&device, alpha);//刷新并绘制立方体
screen_update();
Sleep(1);
}
delete demo;
return a.exec();
}
光栅渲染器关键词:
- 读取解析数据;
- 多边形网格:点->线->三角形->面。两点构成一线,三条线构成一个三角形,若干个三角形连接成三角带(面)。
- 纹理坐标,颜色
- Camera :平移,旋转,缩放:平移和自转是网格的矩阵变换,缩放是摄像机的矩阵变换
- KeyEvent,MouseEvent
- World Transform,View Transform ,Projection Transform
- 矢量运算,矩阵变换,顶点运算
- 齐次坐标,cvv,归一化,初始化
- 光栅化:光栅化是将几何数据经过一系列变换后最终转换为像素,从而呈现在显示设备上的过程。光栅化的本质是坐标变换、几何离散化。
- 剪裁,透视除法,背面剔除,视口转换,扫描转换
- 光照,阴影
- 左手坐标系:Z轴指向屏幕里
第六步 理论问题若干
基本结构
- 先定义一个4属性的向量vector,并组织向量的数学运算:叉乘,点乘,减法,归一化。
- 由向量派生空间点point。
- 由空间点构建顶点vertex。
- 将数据点集按照vertex的格式组织起来,构成点,线,三角形,面,体。
- 再定义一个4X4的矩阵matrix,组织该矩阵的数学运算:
- 由该矩阵产生3个对象:世界坐标变换world,摄影机坐标变换view,投影变换projection
- 由这3个对象产生一个变换矩阵:transform = world * view * projection
- 构建场景renderer,接管了绘制画布的内存,事实上所有的绘制都在这部分,其他的都是抽象和封装。
- 构建相机camera,相机的变化就是修改摄影机坐标变换view,然后刷新变换矩阵transform = world * view * projection
- 物体的旋转变化就是修改世界坐标变换world,
几个问题
- 如何把物体放置在视图的正中央
- 在视野中,哪些点可见,哪些点不可见
// 检查齐次坐标同 cvv 的边界用于视锥裁剪
int fishTransform::transform_check_cvv(const fishVector *v)
{
float w = v->w;
int check = 0;
if (v->z < 0.0f) check |= 1;
if (v->z > w) check |= 2;
if (v->x < -w) check |= 4;
if (v->x > w) check |= 8;
if (v->y < -w) check |= 16;
if (v->y > w) check |= 32;
return check;
}
- 物体旋转矩阵
// 旋转矩阵
void fishMatrix::matrixSetRotate(matrix_t *m, float x, float y, float z, float theta)
{
float qsin = (float)sin(theta * 0.5f);
float qcos = (float)cos(theta * 0.5f);
fishVector vec;
vec.x = x;
vec.y = y;
vec.z = z;
vec.w = 1.0f;
float w = qcos;
// m_vector->vectorNormalize(&vec);
vec.vectorNormalize();
x = vec.x * qsin;
y = vec.y * qsin;
z = vec.z * qsin;
m->m[0][0] = 1 - 2 * y * y - 2 * z * z;
m->m[1][0] = 2 * x * y - 2 * w * z;
m->m[2][0] = 2 * x * z + 2 * w * y;
m->m[0][1] = 2 * x * y + 2 * w * z;
m->m[1][1] = 1 - 2 * x * x - 2 * z * z;
m->m[2][1] = 2 * y * z - 2 * w * x;
m->m[0][2] = 2 * x * z - 2 * w * y;
m->m[1][2] = 2 * y * z + 2 * w * x;
m->m[2][2] = 1 - 2 * x * x - 2 * y * y;
m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
m->m[3][3] = 1.0f;
}
//cube
void fishBlocks::drawCube(float theta)
{
matrix_t m;
//计算旋转矩阵
m_renderer->GetfishMatrix()->matrixSetRotate(&m, 1, 0, 0, theta);//Z
//m_renderer->GetfishMatrix()->matrixSetRotate(&m, 0, 1, 0, theta);//X
//m_renderer->GetfishMatrix()->matrixSetRotate(&m, 0, 0, 1, theta);//Y
//m_renderer->GetfishMatrix()->matrixSetRotate(&m, -1, -0.5, 1, theta);
//世界坐标变换
m_renderer->GetfishDevice()->GetFishTransform()->world = m;
m_renderer->GetfishTransform()->transform_update();
drawPlane(0, 1, 2, 3);
drawPlane(4, 5, 6, 7);
drawPlane(0, 4, 5, 1);
drawPlane(1, 5, 6, 2);
drawPlane(2, 6, 7, 3);
drawPlane(3, 7, 4, 0);
// qDebug()<<"fishRenderer::draw_cube";
// std::cout<<"fishRenderer::draw_cube done"<<std::endl;
}
- 相机旋转矩阵
// 设置摄像机
void fishCamera::matrixSetLookat(matrix_t *m, const fishVector *eye, const fishVector *at, const fishVector *up)
{
fishVector xaxis, yaxis, zaxis;
zaxis.vectorSub(at,eye);
zaxis.vectorNormalize();
xaxis.vectorCrossproduct(up,&zaxis);
xaxis.vectorNormalize();
yaxis.vectorCrossproduct(&zaxis,&xaxis);
m->m[0][0] = xaxis.x;
m->m[1][0] = xaxis.y;
m->m[2][0] = xaxis.z;
m->m[3][0] = -m_renderer->GetfishVector()->vectorDotproduct(&xaxis, eye);
m->m[0][1] = yaxis.x;
m->m[1][1] = yaxis.y;
m->m[2][1] = yaxis.z;
m->m[3][1] = -m_renderer->GetfishVector()->vectorDotproduct(&yaxis, eye);
m->m[0][2] = zaxis.x;
m->m[1][2] = zaxis.y;
m->m[2][2] = zaxis.z;
m->m[3][2] = -m_renderer->GetfishVector()->vectorDotproduct(&zaxis, eye);
m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
m->m[3][3] = 1.0f;
}
参考文献:
- 想用C++实现一个软件渲染器,类似DX和OpenGL,除了《3D游戏编程大师技巧》,或者什么网站推荐? - 知乎
https://www.zhihu.com/question/33712299/answer/58495947 - 渲染器 1 —— 基本绘图 - 知乎
https://zhuanlan.zhihu.com/p/20140034 - 《3D数学基础:图形与游戏开发》
- 《3D游戏编程大师技巧》
- 《计算机图形学与几何造型导论》
最后
以上就是顺利水池为你收集整理的光栅渲染器基础知识的全部内容,希望文章能够帮你解决光栅渲染器基础知识所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复