games101 作业3分析 详解bump mapping

代码分析

整体代码结构 其实变化还是不大 主要是引入了vertexshader(什么都没做) 与 fragmentshader(使用了不同的着色方法 直接用法线作为rgb 使用blingphong光照模型 纹理贴图 bumpmapping displacementmapping)

主要变化在光栅化部分 着色计算使用特定的fragmentshader

这里我之前写的代码有问题 boundingbox没有覆盖边界 导致第一次出来的牛牛不同的三角形之间有缝隙hhh

注意这里使用的w值不再是之前错误的1 而是真实深度 但是我感觉这里插值使用的公式还是没有矫正过的 不过整体影响不大

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
//这里不要使用toVector4 会导致深度值直接赋值为1
auto v = t.v;
int XMin = std::min(std::min(v[0].x(), v[1].x()), v[2].x());
int XMax = std::max(std::max(v[0].x(), v[1].x()), v[2].x());
int YMin = std::min(std::min(v[0].y(), v[1].y()), v[2].y());
int YMax = std::max(std::max(v[0].y(), v[1].y()), v[2].y());
for (int x = XMin; x <= XMax; x++) {
for (int y = YMin; y <= YMax; y++) {
int index = get_index(x, y);
if (insideTriangle(x + 0.5, y + 0.5, t.v)) {
auto [alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);
float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
zp *= Z; if (zp < depth_buf[index]) {
depth_buf[index] = zp;
auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1.0f);
auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1.0f);
auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1.0f);
auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1.0f);
//frame_buf[index] = t.getColor();
fragment_shader_payload FragShader(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
FragShader.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(FragShader);
Vector2i point;
point << x, y;
set_pixel(point, pixel_color);
}
} }
}

下面draw函数中还是有一些细节

比如透视除法 w分量保留了z值

用view_Pos来存储真实的世界坐标 因为光照计算必须是在三维空间中计算的 所以这里要存储

法线矫正 防止model矩阵中物体进行了非等比缩放

void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {

    float f1 = (50 - 0.1) / 2.0;
float f2 = (50 + 0.1) / 2.0; Eigen::Matrix4f mvp = projection * view * model;
for (const auto& t:TriangleList)
{
Triangle newtri = *t;
//使用三维空间中的坐标获取着色点坐标
std::array<Eigen::Vector4f, 3> mm {
(view * model * t->v[0]),
(view * model * t->v[1]),
(view * model * t->v[2])
}; std::array<Eigen::Vector3f, 3> viewspace_pos; std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
return v.template head<3>();
}); Eigen::Vector4f v[] = {
mvp * t->v[0],
mvp * t->v[1],
mvp * t->v[2]
};
//Homogeneous division
for (auto& vec : v) {
vec.x()/=vec.w();
vec.y()/=vec.w();
vec.z()/=vec.w();
}
//法线校正
Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();
Eigen::Vector4f n[] = {
inv_trans * to_vec4(t->normal[0], 0.0f),
inv_trans * to_vec4(t->normal[1], 0.0f),
inv_trans * to_vec4(t->normal[2], 0.0f)
}; //Viewport transformation
for (auto & vert : v)
{
vert.x() = 0.5*width*(vert.x()+1.0);
vert.y() = 0.5*height*(vert.y()+1.0);
vert.z() = vert.z() * f1 + f2;
} for (int i = 0; i < 3; ++i)
{
//screen space coordinates
newtri.setVertex(i, v[i]);
} for (int i = 0; i < 3; ++i)
{
//view space normal
newtri.setNormal(i, n[i].head<3>());
} newtri.setColor(0, 148,121.0,92.0);
newtri.setColor(1, 148,121.0,92.0);
newtri.setColor(2, 148,121.0,92.0); // Also pass view space vertice position
rasterize_triangle(newtri, viewspace_pos);
}
}

理论分析

bling-phong模型 与 纹理映射的理论都比较简单 这里不再赘述

有关纹理寻址 与 纹理过大/过小的问题 大概会再写一篇 参考一下别的资料

重点分析一下 bumpmapping的理论

其实normal mapping 和bumpmapping 很像 Bump Mapping 使用高度图来扰动法线 Normal Mapping 使用法线贴图来直接定义表面法线 理论上normal mapping 能够实现更好的细节 这两者都是为了使用更少的三角形 来实现更多的细节:

不使用bump mapping



使用bump mapping

首先 可以参考一下learnopengl的资料:https://learnopengl-cn.github.io/05 Advanced Lighting/04 Normal Mapping/

简单总结一下: 为什么我们要使用切线空间来存储法线?

如果直接存储物体的法线信息 那么物体只要发生变化 我们的法线贴图就没办法使用了 但是在切线空间下存储 我们只要每次求出一个TBN矩阵 然后进行变换就可以将切线空间的法线转换到世界空间 然后计算光照了

关于TBN矩阵就是对应着切线空间的三个基向量 在世界空间中的坐标 这样进行空间转换

推导方面 需要求出切线 以及副切线 可以看看learnopengl中的推导 本次作业中切线的求解我没看太懂 感觉是做了个近似?

得到了TBN矩阵 如何使用:

我们直接使用TBN矩阵,这个矩阵可以把切线坐标空间的向量转换到世界坐标空间。因此我们把它传给片段着色器中,把通过采样得到的法线坐标左乘上TBN矩阵,转换到世界坐标空间中,这样所有法线和其他光照变量就在同一个坐标系中了。

这也是本次课程中的做法

现在唯一的问题就是我们如何求出这个法线坐标

而games101课程中介绍的就是我们如何求经过bump mapping得到的法线坐标

即经过高度图来扰动点的位置之后 利用偏导数来近似切线 进一步得到法线:



但其实这样也是一个近似的结果

pbr中的推导是这样的:



上述是点的位置发生了变化 d(u,v)代表着高度变化 n(u,v)是表面法线 本来应该是个标量 但是作业中用的是rgb形式的 所以要求它的范数

我们对上述求偏导实际的近似结果应该是:

作业中相当只使用了uv坐标的变化来近似 也是因为我们信息有限嘛

详细分析见:https://www.pbr-book.org/4ed/Textures_and_Materials/Material_Interface_and_Implementations#NormalMapping

这里我们求得的其实是shading normal 而我们之前用的tbn中的n是surface normal 着色是计算我们要用高度扰动之后的位置以及法线(shading normal) 而高度扰动之后的位置计算 我们要使用surface normal来计算 这点很重要 我看到有些网上的答案计算是错误的

实际解决

注意所有的方向向量 都要归一化!

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
// TODO: Get the texture value at the texture coordinates of the current fragment
return_color = payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y());
}
Eigen::Vector3f texture_color;
texture_color << return_color.x(), return_color.y(), return_color.z(); Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = texture_color / 255.f;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937); auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}}; std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10}; float p = 150; Eigen::Vector3f color = texture_color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal; Eigen::Vector3f result_color = {0, 0, 0}; for (auto& light : lights)
{ Eigen::Vector3f ambient_color = ka.cwiseProduct(amb_light_intensity);
double distance = (light.position - point).norm();
Eigen::Vector3f light_dir = (light.position - point).normalized();
Eigen::Vector3f diffuse_color = kd.cwiseProduct(light.intensity) / (distance * distance) * std::max(normal.normalized().dot(light_dir), 0.0f);
Eigen::Vector3f half_vector = (light_dir + (eye_pos - point).normalized()).normalized();
Eigen::Vector3f specular_color = ks.cwiseProduct(light.intensity) / (distance * distance) * std::pow(std::max(normal.normalized().dot(half_vector), 0.0f), p);
result_color += (ambient_color + diffuse_color + specular_color);
} return result_color * 255.f;
} Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = payload.color;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937); auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}}; std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10}; float p = 150; Eigen::Vector3f color = payload.color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal; Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{ Eigen::Vector3f ambient_color = ka.cwiseProduct(amb_light_intensity);
double distance = (light.position - point).norm();
Eigen::Vector3f light_dir = (light.position - point).normalized();
Eigen::Vector3f diffuse_color = kd.cwiseProduct(light.intensity) / (distance * distance) * std::max(normal.normalized().dot(light_dir),0.0f);
Eigen::Vector3f half_vector = (light_dir + (eye_pos - point).normalized()).normalized();
Eigen::Vector3f specular_color = ks.cwiseProduct(light.intensity) / (distance * distance) * std::pow(std::max(normal.normalized().dot(half_vector),0.0f), p);
result_color += (ambient_color + diffuse_color + specular_color);
} return (result_color) * 255.f;
} //
Eigen::Vector3f displacement_fragment_shader(const fragment_shader_payload& payload)
{ Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = payload.color;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937); auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}}; std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10}; float p = 150; Eigen::Vector3f color = payload.color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal; float kh = 0.2, kn = 0.1; Eigen::Vector3f tagent;
tagent << normal.x() * normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
normal.z()* normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2));
Eigen::Vector3f bitangent = normal.cross(tagent);
Eigen::Matrix3f TBN;
TBN.col(0) = tagent.normalized();
TBN.col(1) = bitangent.normalized();
TBN.col(2) = normal.normalized(); float width = 1.0f / payload.texture->width;
float height = 1.0f / payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y()).norm() -
payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());
float dV = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y() + height).norm() -
payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());
//计算扰动之后的位置
point += kn * normal * payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm();
Eigen::Vector3f ln;
ln << -dU, -dV, 1;
Eigen::Vector3f shading_normal = (TBN * ln).normalized(); Eigen::Vector3f result_color = {0, 0, 0}; for (auto& light : lights)
{ Eigen::Vector3f ambient_color = ka.cwiseProduct(amb_light_intensity);
double distance = (light.position - point).norm();
Eigen::Vector3f light_dir = (light.position - point).normalized();
Eigen::Vector3f diffuse_color = kd.cwiseProduct(light.intensity) / (distance * distance) * std::max(shading_normal.normalized().dot(light_dir), 0.0f);
Eigen::Vector3f half_vector = (light_dir + (eye_pos - point).normalized()).normalized();
Eigen::Vector3f specular_color = ks.cwiseProduct(light.intensity) / (distance * distance) * std::pow(std::max(shading_normal.normalized().dot(half_vector), 0.0f), p);
result_color += (ambient_color + diffuse_color + specular_color); } return result_color * 255.f;
} Eigen::Vector3f bump_fragment_shader(const fragment_shader_payload& payload)
{ Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
Eigen::Vector3f kd = payload.color;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937); auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}}; std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10}; float p = 150; Eigen::Vector3f color = payload.color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal; float kh = 0.2, kn = 0.1; Eigen::Vector3f tagent;
tagent << normal.x() * normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
normal.z()* normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2));
Eigen::Vector3f bitangent = normal.cross(tagent);
Eigen::Matrix3f TBN;
TBN.col(0) = tagent.normalized();
TBN.col(1) = bitangent.normalized();
TBN.col(2) = normal.normalized(); float width = 1.0f / payload.texture->width;
float height = 1.0f / payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y()).norm() -
payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());
float dV = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y() + height).norm() -
payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm()); Eigen::Vector3f ln;
ln << -dU, -dV, 1;
normal = (TBN * ln).normalized();
Eigen::Vector3f result_color = {0, 0, 0};
result_color = normal; return result_color * 255.f;
}

# games101 作业3分析 详解bump mapping的更多相关文章

  1. Memcache的使用和协议分析详解

    Memcache的使用和协议分析详解 作者:heiyeluren博客:http://blog.csdn.NET/heiyeshuwu时间:2006-11-12关键字:PHP Memcache Linu ...

  2. wav文件格式分析详解

    wav文件格式分析详解 文章转载自:http://blog.csdn.net/BlueSoal/article/details/932395 一.综述    WAVE文件作为多媒体中使用的声波文件格式 ...

  3. 线程组ThreadGroup分析详解 多线程中篇(三)

    线程组,顾名思义,就是线程的组,逻辑类似项目组,用于管理项目成员,线程组就是用来管理线程. 每个线程都会有一个线程组,如果没有设置将会有些默认的初始化设置 而在java中线程组则是使用类ThreadG ...

  4. HanLP中人名识别分析详解

    HanLP中人名识别分析详解 在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: l ·名字识别的问题 #387 l ·机 ...

  5. GC日志分析详解

    点击返回上层目录 原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 GC日志分析详解 以ParallelGC为例,YoungGC日志解释如下 ...

  6. HashMap实现原理分析(详解)

    1. HashMap的数据结构 http://blog.csdn.net/gaopu12345/article/details/50831631   ??看一下 数据结构中有数组和链表来实现对数据的存 ...

  7. MongoDB执行计划分析详解

    要保证数据库处于高效.稳定的状态,除了良好的硬件基础.高效高可用的数据库架构.贴合业务的数据模型之外,高效的查询语句也是不可少的.那么,如何查看并判断我们的执行计划呢?我们今天就来谈论下MongoDB ...

  8. 15.linux-LCD层次分析(详解)

    如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口. 什么是frambuffer设备? frambu ...

  9. HanLP中的人名识别分析详解

    在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: u u名字识别的问题 #387 u u机构名识别错误 u u关于层叠H ...

  10. (转)__attribute__之section 分析详解

    原文地址:__attribute__之section详解 前言 第一次接触 "section" 是在公司的一个STM32的项目代码中,前工程师将所有的初始化函数都使用的" ...

随机推荐

  1. C# .net core中如何将多张png图片合并成一个gif

    背景 我们有很多这样的序列帧: 我这边要把这些序列帧裁切最后合并成gif,以下是我裁切后的png文件: 我一开始选用的是 SixLabors.ImageSharp 这是裁切代码: using var ...

  2. 解决Vue中使用history路由模式出现404的问题

    背景 vue中默认的路由模式是hash,会出现烦人的符号#,如http://127.0.0.1/#/. 改为history模式可以解决这个问题,但是有一个坑是:强刷新.回退等操作会出现404. Vue ...

  3. 如何在Spring Boot框架下实现高效的Excel服务端导入导出?

    前言 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置 ...

  4. VSCode 中 Markdown Preview Enhanced 插件利用 Chrome (Puppeteer) 导出 PDF 文件使用说明与问题解决

    准备 预先安装好 Chrome 浏览器. 使用方法 右键选择 Chrome (Puppeteer). 设置 Puppeteer 通过 front-matter 即在 markdown 文档开头加上 y ...

  5. NXP i.MX 8M Plus工业核心板硬件说明书( 四核ARM Cortex-A53 + 单核ARM Cortex-M7,主频1.6GHz)

    1          硬件资源 创龙科技SOM-TLIMX8MP是一款基于NXP i.MX 8M Plus的四核ARM Cortex-A53 + 单核ARM Cortex-M7异构多核处理器设计的高端 ...

  6. logo描边

  7. map端join和reduce端join的区别

    MapReduce Join MapJoin和ReduceJoin区别及优化 maptask处理后写到本地,如果再到reduce,又涉及到网络的拷贝. map端join最大优势,可以提前过滤不需要的数 ...

  8. 【冷启动#2】实用的springboot tutorial入门demo

    跟着官方文档熟悉一遍创建spring工程的步骤 https://spring.io/guides/gs/spring-boot https://juejin.cn/post/7077958723829 ...

  9. 量子算法抛转(以及Oracle函数初步)

    接下来要接触量子算法了,我们会看到怎么利用量子并行机制和干涉原理.干涉在算法对结果进行测量求值时举足轻重. Deutsch-Jozsa 算法 DJ算法是量子算法的入门算法,就像编程界的"He ...

  10. [无线隔离]同一WIFI下两主机无法互联

    问题描述 在公司WIFI下想进行两台主机之间的数据传输,却发现虽在同一网段且防火墙关闭也无法ping通. 在一台主机下查看ARP表,发现没有对方的IP与MAC记录. 使用Wireshark抓包,发现虽 ...