GAMES101

您所在的位置:网站首页 微星gtx1070怎么样 GAMES101

GAMES101

#GAMES101| 来源: 网络整理| 查看: 265

前三次作业使用了差不多的一个框架,所以我就把它们整合到一起做,同时分析一下这个框架是如何用CPU模拟GPU的。

其他内容可见:

环境配置

我用的vs 2019,而不是官方建议的虚拟机,因为vs 更方便。配置也很简单:实际上这个系列框架只用了Eigen和OpenCV两个包(这个框架使用CPU模拟GPU的过程,所以帧率会有点低),所以可以直接在VS 2019中做,于是我看到可以这样实现:

首先安装vcpkg然后指定把开源的两个库编译成64位win架构下(管理员模式运行,开上全局梯子)

.\vcpkg.exe install eigen3:x64-windows .\vcpkg.exe install opencv:x64-windows .\vcpkg.exe integrate install // 最后一句的意思是全局应用 Applied user-wide integration for this vcpkg root. All MSBuild C++ projects can now #include any installed libraries. Linking will be handled automatically. Installing new libraries will make them instantly available. CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=C:/sooooft/vcpkg/scripts/buildsystems/vcpkg.cmake"

参考文章:https://blog.csdn.net/weixin_39548859/article/details/107170535很不巧,就在我install命令发出去的两三个小时前,这个repository坏了,空了。所以,,,只能等它好。

包下载好了之后,配置vs2019,其实没什么好配置的,建立一个空项目,直接就默认找到了vcpkg,可以include了(因为我们用vs自动的编译器,所以不用cmake了,配置cmake也很简单,直接设置就好,这里我就不做)

成功另外记得在解决方案设置中的启动选项改为“当前选定内容”

这样就可以方便的选择不同项目运行代码了。要加入作业文件到项目中,只需要把作业文件复制到项目文件夹下,然后在vs中点击右上角按钮显示所有文件

然后左图中选中这些文件右键选择 包括到项目中 , 结果如右图所示另外注意把C++语言标准设置到C++17

这样基本就配置好环境了。

接下来我们首先用作业一来分析一下这个框架

作业1

作业要求:普通已有给定点 v0 (2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2 (−2.0, 0.0, −2.0)围成的一个三角形。现需要

构建模型变换矩阵(主要是进行旋转变换) get_model_matrix(float rotation_angle) 构建投影矩阵 get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)

提高在 main.cpp 中构造一个函数,该函数的作用是得到绕任意过原点的轴的旋转变换矩阵。Eigen::Matrix4f get_rotation(Vector3f axis, float angle)

分析

框架已经完成了其他任务,我们需要的就是构建模型变换矩阵和投影矩阵,对应上图中 modeling transformation 和 Projection transformation 两个步骤。

框架分析:这里详细来看看,这套框架是怎么运行的。再来回答作业1的问题首先这里有5个文件,(我们用vs自动构建编译过程所以不需要cmake)

main.cpp 入口Triangle.cpp 和 hpp 一个三角形类rasterizer.cpp 和 hpp 光栅化工具类

具体的函数和数据这里就不展开说明,看一眼代码就知道,主要从main函数来分析一下程序运行的流程

首先接收处理保存来自命令行模式下的参数(这里我们不用命令行实例化光栅器r,设置分辨率为700x700这一步会创建一个700*700大小的std::vector frame_buf;std::vector depth_buf;设置相机位置eye-pos在(0,0,5)位置给定三个点pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}}; index是ind{{0, 1, 2}};然后把pos 和ind 传给光栅器,得到它们的ID。这里做的就相当于

然后开始分别处理命令行和键盘输入的逻辑,这里我们不看命令行至此,我们大概可以知道是一个什么结果了,在Blender里面画出来就是:

(黄框是摄像机视图,相机位置在(0,0,5),向上方向为y,看向-z,分辨率为700*700)

(场景应该是这样子)

接下来是核心逻辑:键盘输入控制除非按下esc结束程序,否则一直循环执行:

while (key != 27) { r.clear(rst::Buffers::Color | rst::Buffers::Depth); //r.set_model(get_model_matrix(angle)); r.set_model(get_rotation(Eigen::Vector3f(0, 0, 1), angle)); r.set_view(get_view_matrix(eye_pos)); r.set_projection(get_projection_matrix(45.0f, 1.0f, 0.1f, 50.0f)); r.draw(pos_id, ind_id, rst::Primitive::Triangle); cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data()); image.convertTo(image, CV_8UC3, 1.0f); cv::imshow("image", image); key = cv::waitKey(10); std::cout

rasterize_wireframe 调用draw_line函数进行绘制每一根线这里用一个set_pixel函数,把每个顶点的x,y 输入,转换为其在frame_buf中的下标位置也就是图像中的像素点位置,并且涂上色。

第二点,OpenCV如何把一个点向量转化为一张图片的。首先用Mat构造一个image,Mat(nrows,ncols,type,fillValue)r.frame_buffer().data() 返回指针,指向vector的第一个元素的地址 C++11然后转换一下类型,从32位3通道Float类型转换为8位3通道unsigned类型然后显示出来,每隔10ms刷新。

cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data()); image.convertTo(image, CV_8UC3, 1.0f); cv::imshow("image", image); key = cv::waitKey(10);

这样,这个框架就解释完了,我们看看作业内容代码:普通

结果基本上跟Blender看到的基本一致

旋转矩阵就是很简单,构建:

Eigen::Matrix4f get_model_matrix(float rotation_angle) { rotation_angle = rotation_angle / 180 * MY_PI; Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); Eigen::Matrix4f rotation; rotation Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) { eye_fov = eye_fov / 180 * MY_PI; Eigen::Matrix4f projection = Eigen::Matrix4f::Identity(); Eigen::Matrix4f aspect_fovY; float ty = -1.0f / tan(eye_fov / 2.0f); aspect_fovY

当我改变输入轴,希望让三角形绕45度斜着旋转的时候,r.set_model(get_rotation(Eigen::Vector3f(-1, 1, 0), angle));

确实得到了效果

作业2

作业要求:普通画实心三角形首先把第一个作业里面写的投影矩阵复制过来,然后修改两个函数• rasterize_triangle(): 执行三角形栅格化算法• static bool insideTriangle(): 测试点是否在三角形内。你可以修改此函数的定义,这意味着,你可以按照自己的方式更新返回类型或函数参数。提高 用 super-sampling 处理 Anti-aliasing : 你可能会注意到,当我们放大图像时,图像边缘会有锯齿感。我们可以用 super-sampling来解决这个问题,即对每个像素进行 2 * 2 采样,并比较前后的结果 (这里并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个 samplelist。最后,如果你实现正确的话,你得到的三角形不应该有不正常的黑边。框架分析:这个框架任然延续了上一个框架,稍微有所不同的是,上一个框架是画一个线框三角形, 这个框架是画两个实心三角形,我们来看看具体代码

Main函数没有什么区别,就是硬编码了两个三角形

std::vector pos { {2, 0, -2}, {0, 2, -2}, {-2, 0, -2}, {3.5, -1, -5}, {2.5, 1.5, -5}, {-1, 0.5, -5} }; std::vector ind { {0, 1, 2}, {3, 4, 5} }; std::vector cols { {217.0, 238.0, 185.0}, {217.0, 238.0, 185.0}, {217.0, 238.0, 185.0}, {185.0, 217.0, 238.0}, {185.0, 217.0, 238.0}, {185.0, 217.0, 238.0} };

然后就是要自己实现的 rasterize_triangle(): 执行三角形栅格化算法 函数代码: 普通 rasterize_triangle() 的步骤

找到triangel的Bounding Box判断BoundingBox里面的像素中心坐标是否在三角形内在的话,用重心坐标对其z进行插值,判断当前点的z是否小于depth buffer里面对应的值小于的话更新depth buffer值,把当前像素颜色设置为三角形的颜色。

//Screen space rasterization void rst::rasterizer::rasterize_triangle(const Triangle& t) { auto v = t.toVector4(); std::vector x_arry{ v[0].x(), v[1].x(), v[2].x() }; std::vector y_arry{ v[0].y(), v[1].y(), v[2].y() }; std::sort(x_arry.begin(), x_arry.end()); std::sort(y_arry.begin(), y_arry.end()); int x_min = floor(x_arry[0]), x_max =ceil( x_arry[2]), y_min=floor(y_arry[0]), y_max = ceil(y_arry[2]); for (int x = x_min; x 0.f; }else { return p1p2.cross(p1p).z()

最后的效果看起来达到要求(注意前后遮挡顺序),而且确实有明显的锯齿

提高这里的反采样要求用SSAA,课程里面介绍的是MSAA这里先区分一下两者,然后再看怎么操作。都是以4x为例

4xSSAA,超级取样抗锯齿每个像素维护一个4x子像素的颜色和深度列表在子像素上进行所有的计算最后在显示阶段,平均一下子像素,输出给显示器4xMSAA,多重取样抗锯齿每个像素维护一个4x子像素的颜色和深度列表通过子像素的在三角形内的覆盖率对父像素颜色进行平均最后在显示阶段,直接输出给显示器

原先我是用像素中心点输入insideTriangle函数判断像素是否在三角形内,现在我需要让每个像素维护一个2 * 2 的子像素list,把子像素输入insideTriangle内,并计算更新其color(计算color其实就是ps阶段)和depth。4次计算后,把子像素数组的color和depth进行平均就是当前像素的值。

由于这里的color是固定的,因此我们可以只维护一个depth,这样只有在三角形内部的子像素才有有意义的depth值。我们通过这个可以知道有多少个子像素在三角形内,然后就可以平均其父像素颜色。

经过一番修改后,得到抗锯齿的,三角形

发现,这个是由黑边的 ,而题目明确说了正确结果是不会有黑边,分析发现我把逻辑写错了,少了一步down sampling,没有混合两个三角形。导致黑边的出现,黑边其实不是黑色,而是颜色值被除了4的绿色。这篇文章很详细说明了这个问题:https://blog.csdn.net/weixin_51928794/article/details/117256226

稍加修改逻辑之后,成功得到结果。

#define SSAA true void rst::rasterizer::draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type) { /*...*/ if (SSAA) { for (int x = 0; x

最后了解了一下mat格式https://blog.csdn.net/qq_29540745/article/details/52517269重新修改了uv读取函数,

/*.. 限定范围.*/ Texture::Eigen::Vector3f getColor(float u, float v) { int u_img = static_cast(u * width); int v_img = static_cast((1 - v) * height); if (u_img = width) u_img = width-1; if (v_img = height) v_img = height-1; cv::Vec3b color = image_data.at(v_img, u_img); return Eigen::Vector3f(color[0], color[1], color[2]); }

然后发现得到的颜色偏亮,

发现是我对计算结果做了归一化,然后再*255导致的,去除这一条就可以获得正确结果这里是我理解错了,我本想是不让结果超过1,但是归一化是把三个通道之和不超过1,所以相当于对结果做了除法,导致颜色饱和度不对。如果想实现我的想法这里应该对每个通道进行钳制,但其实没必要这么做。

// result_color.normalize();

完成BumpShader没怎么看懂这个直接抄了个代码

// Let n = normal = (x, y, z) // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z)) // Vector b = n cross product t // Matrix TBN = [t b n] // dU = kh * kn * (h(u+1/w,v)-h(u,v)) // dV = kh * kn * (h(u,v+1/h)-h(u,v)) // Vector ln = (-dU, -dV, 1) // Normal n = normalize(TBN * ln) float kh = 0.2, kn = 0.1; float x = normal.x(); float y = normal.y(); float z = normal.z(); Eigen::Vector3f t = Eigen::Vector3f(x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z * y / std::sqrt(x * x + z * z)); Eigen::Vector3f b = normal.cross(t); Eigen::Matrix3f TBN; TBN height; float dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm()); float dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm()); Eigen::Vector3f ln = Eigen::Vector3f(-dU, -dV, 1.0f); normal = TBN * ln;

这个其实就是把法线贴图的切线空间转回来到视口空间,但是网上关于TBN矩阵和切线空间变换写的很乱,我改天专门研究一下,这里就先不细究了。

可以看到法线信息完全被贴图扰乱了,这就是凹凸贴图在做的事情

如果把扰乱的法线用在Texture上面会如何,

bump强度太大导致出现很强烈的错误

DisplacementShader

变化不大, 就是把phong模型光照加上,然后把贴图信息不但影响法线还影响点的位置

float kh = 0.2, kn = 0.1; // TODO: Implement displacement mapping here // Let n = normal = (x, y, z) // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z)) // Vector b = n cross product t // Matrix TBN = [t b n] // dU = kh * kn * (h(u+1/w,v)-h(u,v)) // dV = kh * kn * (h(u,v+1/h)-h(u,v)) // Vector ln = (-dU, -dV, 1) // Position p = p + kn * n * h(u,v) // Normal n = normalize(TBN * ln) float x = normal.x(); float y = normal.y(); float z = normal.z(); Eigen::Vector3f t = Eigen::Vector3f(x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z * y / std::sqrt(x * x + z * z)); Eigen::Vector3f b = normal.cross(t); Eigen::Matrix3f TBN; TBN height; float dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm()); float dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm()); Eigen::Vector3f ln = Eigen::Vector3f(-dU, -dV, 1.0f); point += (kn * normal * payload.texture->getColor(u, v).norm()); normal = (TBN * ln).normalized();

提高用bunny试一下

模型很小,渲染也很快。

双线性纹理插值

原理:非常简单

,但是在做cv的边界判断的时候老是出错,写了一个边界函数

int rangeSafe(int x, bool isU) { if (x = width) { return width - 1; } else if(!isU && x >= height) { return height - 1; } return x; }

然后核心代码

Eigen::Vector3f getColorBilinear(float u, float v) { float u_img = u * width; float v_img = (1 - v) * height; int u_min = rangeSafe(floor(u_img),true); int u_max = rangeSafe(ceil(u_img), true); int v_min = rangeSafe(floor(v_img), false); int v_max = rangeSafe(ceil(v_img), false); //std::cout


【本文地址】


今日新闻


推荐新闻


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