OpenGL 相机 LookAt 矩阵计算

原理: 相机的运动实际上是对世界坐标系中的物体进行反向运动变换 相机的运动矩阵与其他物体的变换矩阵类似 先缩放变换,再进行旋转,最后进行平移 所以 glm::lookAt 函数返回的是相机变换矩阵的逆矩阵 先对相机的平移矩阵求逆,然后乘以旋转矩阵的逆矩阵(参考计算矩阵乘积的逆矩阵公式) 注意事项: 相机矩阵的 Z轴 与相机的照射方向相反 glm 中的矩阵是列向量优先 正交矩阵的逆矩阵等于它的转置 刚体运动中变换矩阵的逆矩阵求解: 刚体运动变换矩阵: $$\begin{equation} T = \begin{bmatrix*}[c] R & t \newline 0^T & 1 \end{bmatrix*} \end{equation}$$ 分块矩阵求逆公式 $$\begin{equation} M = \begin{bmatrix*}[c] A & B \newline 0 & D \end{bmatrix*} \space\space\space\space M^{-1} = \begin{bmatrix*}[c] A^{-1} & -A^{-1}BD^{-1} \newline 0 & D^{-1} \end{bmatrix*} \end{equation}$$ 刚体运动变换矩阵的逆矩阵: $$\begin{equation} T^{-1} = \begin{bmatrix*}[c] R^{-1} & -R^{-1}t \newline 0^T & 1 \end{bmatrix*} = \begin{bmatrix*}[c] R^T & -R^Tt \newline 0^T & 1 \end{bmatrix*} \end{equation}$$ 方法一: glm::mat4 Camera::calculateLookAtMatrix() { // 1....

June 28, 2022 · 2 min · Rick Cui

OpenGL 渲染管线

红宝书第九版,OpenGL Version 4.5 Games101 闫令琪 顶点数据->顶点着色器(MVP 矩阵变换,NDC 标准化设备坐标)->细分控制着色器->细分计算着色器->几何着色器->图元装配->裁剪和剔除->光栅化->片元着色器 片元着色器后,可能还有混色、测试等流程,最后将渲染结果输入到 Frame Buffer 中 顶点着色器: 将传入的顶点坐标进行 MVP 矩阵变换,经过顶点着色器,它们就该是标准化设备坐标。 细分着色: 细分着色包括细分控制着色器和细分计算着色器,细分控制着色器和细分计算着色器是相互依存的,要么都不要,要么都要。细分着色的作用就是通过面片(patch)来描述一个物体的形状。顶点着色器只能处理每个顶点关联的数据,而细分着色能通过面片的形式分割更多的数据点(比如曲面细分) 细分控制着色器:细分控制着色器的一个常见应用就是将输入面片顶点(控制点)传递给细分计算着色器和通过设置细分层次因素,告诉 OpenGL 怎么生成顶点,每个顶点的标注化二维坐标(细分坐标)以二维向量(也就是说只有其 x 和 y 分量是有效的)的方式保存在变量 gl_TessCorrd 内,该变量会被传递到细分计算着色器中(注:gl_TessCorrd 保存的是一个线段的因子,如一个线段的 0.5 横坐标处,那么 gl_TessCorrd.x=0.5); 细分计算着色器:细分控制着色器完成后,细分计算着色器就通过控制点和细分坐标生成一系列顶点坐标,输出跟顶点着色器是一样的。 更多细节:可以看文章OpenGL 图元处理 几何着色器: 几何着色器提供了一种更加灵活的图元生成方法,它能够将(这一组)顶点变换为完全不同的图元,并且还能生成比原来更多的顶点。原理上来说,几何着色器通过一些手段也可以完成细分着色器的任务,但是为什么需要细分着色器呢?这是因为在某些图元生成上,细分着色器可以更加精确或者方便,比如生成三角形网格顶点,更多介绍参见几何着色器。可以看到,顶点着色器,细分着色器和几何着色器本质上都是对顶点进行处理。 图元装配: 前面的着色阶段都对顶点进行操作,关于这些顶点如何被组织成几何图元的信息并传到到 OpenGL 的下个阶段。图元组装阶段将顶点组织成它们相关的几何图形,为裁剪和光栅化做准备。 裁剪和剔除: 偶尔,顶点会在视口之外(窗口的区域),并对与顶点相关的图元进行修改,使其像素都不在视口之外。这个操作被称为裁剪,并由 OpenGL 自动处理。 光栅化: 经过以上步骤,基本上一个图形就形成了,但是这个图形的坐标还是在自己的坐标系中,光栅化就是将图形的坐标转化为屏幕像素坐标,最终将图元的数学描述转化为用于显示在屏幕上的片段,然后通过帧缓存就可以在电脑上看到一个个美丽的图形。 片元着色器: 片元着色器是opengl渲染的最后一个流程,它的主要作用就是赋予我们图形最终的颜色,纹理渲染也在这个阶段 参考: 顶点着色器到片元着色器的过程,varying变量 opengl基本流程 OpenGL学习脚印: 投影矩阵和视口变换矩阵(math-projection and viewport matrix) OpenGL入门(五)– OpenGL渲染流程图解析 Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码) GPU是如何工作的?Shader图形编程入门 GAMES101-现代计算机图形学入门-闫令琪 20分钟让你了解OpenGL——OpenGL全流程详细解读 OpenGL学习:Per-fragment operation(1)-模板测试(stencil test)

June 17, 2022 · 1 min · Rick Cui

Qt 信号和槽

信号函数没有返回值,且只是一个函数声明,必须有 signals: 声明 槽函数的函数标签必须和信号函数相同(相同的返回值类型和形参列表) Qt5 后成员函数也可作为 slots,可以不用 slots: 声明 slots: 声明前要添加访问权限修饰符 信号函数和槽函数其实都可以被重载 // signal 和 slots 都可以重载 // Qt5的方式 void(subWindow::*pFun1)() = &subWindow::showMainWindow; void(MainWindow::*pSlot1)() = &MainWindow::onShowMainWindow; connect(&sw, pFun1, this, pSlot1); void(subWindow::*pFun2)(int,QString) = &subWindow::showMainWindow; void(MainWindow::*pSlot2)(int,QString) = &MainWindow::onShowMainWindow; connect(&sw, pFun2, this, pSlot2); // Qt4的方式 // 1. SIGNAL、SLOT宏会把信号和槽转成字符串,所以就不能进行编译期错误检查 // 2. 槽函数前面必须有 slots 标签,否则无法识别为槽函数 connect(&sw, SIGNAL(showMainWindow()), this, SLOT(onShowMainWindow())); connect(&sw, SIGNAL(showMainWindow(int,QString)) , this, SLOT(onShowMainWindow(int,QString))); // Lambda 表达式 void(subWindow::*pFun1)() = &subWindow::showMainWindow; connect(&sw, pFun1, [](){ qDebug()<<"没有参数的信号"; } ); void(subWindow::*pFun2)(int,QString) = &subWindow::showMainWindow; connect(&sw, pFun2, [](int a, QString msg){ qDebug()<<a<<msg; } ); // Lambda 表达式使用注意事项 // 如果b3是局部变量或者成员变量,lambda不要使用引用传值 QPushButton *b3 = new QPushButton(this); b3->setText("Lambda"); int a = 10, b = 100; connect(&b2/*b3*/, &QPushButton::clicked, [=](){ // 此处使用 & 传值会有问题 qDebug()<<a<<b; } );

June 14, 2022 · 1 min · Rick Cui

C++11 关于右值引用、左值引用和通用引用的思考

一、通用引用 类型声明形式为 type&& 其中 type 类型是要进行推导的,如果类型推导没有发生,那么 type&& 代表一个右值引用 如果一个对象被声明为 auto&&,这个形参或者对象就是一个通用引用 通用引用,如果它被右值初始化,就会对应地成为右值引用;如果它被左值初始化,就会成为左值引用 如果在一个类模板里面看见了一个函数形参类型为 T&&,也不一定就是通用引用,可能并没有发生类型推导 void f(Widget&& param); //右值引用 Widget&& var1 = Widget(); //右值引用 auto&& var2 = var1; //通用引用(不是右值引用) template<typename T> void f(std::vector<T>&& param); //右值引用 template<typename T> void f(T&& param); //通用引用(不是右值引用) template <typename T> void f(const T&& param); //param是一个右值引用,因为添加了 const 限定符 // 函数模板 一般可能是通用引用 template <typename T> void print_reference_type(T &&i) { // T&& 或是 auto&& if (std::is_lvalue_reference<decltype(i)>::value) { std::cout << "lvalue: " << i << std::endl; } else if (std::is_rvalue_reference<decltype(i)>::value) { std::cout << "rvalue: " << i << std::endl; } else { std::cout << "unknown value: " << i << std::endl; } } // 因为 push_back 在有一个特定的 vector 实例之前不可能存在, // 而实例化 vector 时的类型已经决定了 push_back 的声明 // 所以在这里并没有发生类型推导 template<class T, class Allocator = allocator<T>> //来自C++标准 class vector { public: void push_back(T&& x); … } 一个记录任意函数调用的时间开销的函数模板...

June 11, 2022 · 2 min · Rick Cui

C 实现继承和多态

C中的继承和多态

May 24, 2022 · 1 min · Rick Cui