说明

本次作业主要是实现对一个obj文件表示的物体利用贴图进行渲染

rasterizer.cpp框架分析

和作业二类似,只不过颜色不再是固定值,而是通过纹理获得

//draw 函数
// Also pass view space vertice position
rasterize_triangle(newtri, viewspace_pos);

viewspace_pos为视口空间中顶点的位置,传入顶点位置是为了方便后续求解Blinn-Phong光照。

通过注释可以发现在这个函数中主要实现的就是求解一下四个插值

// TODO: Interpolate the attributes:
// auto interpolated_color // 颜色插值
// auto interpolated_normal //法线插值
// auto interpolated_texcoords //纹理贴图插值
// auto interpolated_shadingcoords //位置坐标插值

rasterize_triangle函数上方就是两个重载的插值函数,通过传入重心坐标,以及对应的坐标即可求得该pixel的四个插值

最后一部分说明了该框架是如何进行着色的,通过构建一个payload 然后调用fragment_shader()函数进行对像素的着色

fragment_shader是一个是std::function<>可以通过参数来控制调用的shader函数,这就是在命令行中输入的第3个参数决定的

payload的定义是在Shader.hpp文件中,本质是一个结构体,通过构造函数来初始化payload中的color,normal,tex_coods(纹理坐标),texture(使用的纹理贴图)



所以最终代码

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4();
//b-box 包围盒
int minx = std::min(v[0].x(),std::min(v[1].x(),v[2].x()));
int miny = std::min(v[0].y(),std::min(v[1].y(),v[2].y()));
int maxx = std::max(v[0].x(),std::max(v[1].x(),v[2].x()));
int maxy = std::max(v[0].y(),std::max(v[1].y(),v[2].y()));
Eigen::Vector3i P;
for(P.x() = minx;P.x()<=maxx;P.x()++)
{
for(P.y() = miny;P.y()<=maxy;P.y()++)
{
//barycentric 判断是否在三角形内
if(!insideTriangle(P.x(), P.y(), t.v)) continue;
int cur_index = get_index(P.x(),P.y());
//重心坐标
auto [alpha, beta, gamma] = computeBarycentric2D(P.x(), P.y(), 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[cur_index])
{
depth_buf[cur_index] = zp;
// auto interpolated_color
auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0],t.color[1],t.color[2],1);
// auto interpolated_normal
auto interpolated_normal = interpolate(alpha,beta,gamma,t.normal[0],t.normal[1],t.normal[2],1);
// auto interpolated_texcoords u,v
auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
// auto interpolated_shadingcoords camer location -- r and l
auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1); fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
//pixel的点坐标 用于blinn-phong
payload.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(payload);
Vector2i p2;
p2 << P.x(),P.y();
set_pixel(p2,pixel_color);
}
}
}

各个Shader

phone shading

可以看到在环境中存在两个光照,我们只需要在循环中计算Blinn-Phong的三个值相加即可

入射向量,观测向量和半程向量

Eigen::Vector3f l = (light.position-point).normalized();
Eigen::Vector3f v = (eye_pos-point).normalized();
Eigen::Vector3f h = (l+v).normalized();

光源到照射点的距离

float r =  std::sqrt((light.position[0]-point[0])*(light.position[0]-point[0])+(light.position[1]-point[1])*(light.position[1]-point[1])+(light.position[2]-point[2])*(light.position[2]-point[2]));

求解漫反射,环境光照和高光

//ambient
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
//diffuse
Eigen::Vector3f Ld = kd.cwiseProduct((light.intensity/(r*r)) * std::max(0.0f,normal.dot(l)));
//specular
Eigen::Vector3f Ls = ks.cwiseProduct((light.intensity/(r*r)) * std::pow(std::max(0.0f,normal.dot(h)),p));

使用纹理贴图

纹理贴图的主要作用就是Blinn-Phong模型中的kd值,在对应的Shader函数中,可以看到相应的注释,我们需要做的就是利用payload获取当前pixel对应的kd值

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());
}

凹凸贴图

凹凸贴图存储的是当前点的高度值,通过课程的学习,可以通过高度求解切线,然后利用切线求解法线。通过注释也可以理解这样一个流程

//cur pixel normal 当前pixel的法线
Eigen::Vector3f n = normal;
float x = normal.x();
float y = normal.y();
float z = normal.z();
//计算矩阵
//由于结算法线是由(0,0,1)改变的,所以需要在计算之后进行变换
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 = Eigen::Vector3f(n.y()*t.z()-n.z()*t.y(),-1.0*(n.x()*t.z()-n.z()*t.x()),n.x()*t.y()-n.y()*t.x());
Eigen::Matrix3f TBN;
TBN << t.x(),b.x(),n.x(),
t.y(),b.y(),n.y(),
t.z(),b.z(),n.z();
//get u,v获取当前点的u,v值即高度变换值
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
//get width and height 获取整个纹理贴图的宽和高
float w =payload.texture->width;
float h = payload.texture->height;
//calculate the changed normal
//计算当前点的变化量
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(-1.0*dU,-1.0*dV,1.0);
//求解实际的法线向量
n = TBN*ln;
normal = n.normalized();

注意:为什么在求解dU和dV时,我们计算相邻点的高度值是+1/w或者+1/h。因为通过观察getColor函数可以发现最后实际计算颜色的位置是\(u*width和(1-v)*height\),我们带入就可以发现最终相邻点就是\(u*width+1\)

计算dU 和 dV为什么采用norm



可以查看FAQ中的解释

displacement 纹理

课上讲过,这种纹理会实际改变点的位置,而注释中也给出点的计算方法

Position p = p + kn * n * h(u,v)

所以我们只需要在bumpshader的基础上,在求得改变后的normal之后更改点的位置,然后再利用Blinn-Phone模型求解着色情况。

Games101 -- 作业3的更多相关文章

  1. GAMES101作业2

    作业任务: 填写并调用函数 rasterize_triangle(const Triangle& t). 即实现光栅化 该函数的内部工作流程如下: 创建三角形的 2 维 bounding bo ...

  2. 【UE4】GAMES101 图形学作业2:光栅化和深度缓存

    总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...

  3. GAMES101课程 作业6 源代码概览

    GAMES101课程 作业6 源代码概览 Written by PiscesAlpaca(双鱼座羊驼) 一.概述 本篇将从main函数为出发点,按照各cpp文件中函数的调用顺序和层级嵌套关系,简单分析 ...

  4. 【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)

    总览 在这部分的课程中,我们将专注于使用光线追踪来渲染图像.在光线追踪中最重要的操作之一就是找到光线与物体的交点.一旦找到光线与物体的交点,就可以执行着色并返回像素颜色. 在这次作业中,我们要实现两个 ...

  5. 【UE4】GAMES101 图形学作业4:贝塞尔曲线

    总览 Bézier 曲线是一种用于计算机图形学的参数曲线. 在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更 ...

  6. 【UE4】GAMES101 图形学作业3:Blinn-Phong 模型与着色

    总览 在这次编程任务中,我们会进一步模拟现代图形技术.我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与Fragment Shader,并且支持了纹理映 ...

  7. 【UE4】GAMES101 图形学作业1:mvp 模型、视图、投影变换

    总览 到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象.所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了.在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光 ...

  8. 【UE4】GAMES101 图形学作业0:矩阵初识

    作业描述 给定一个点P=(2,1), 将该点绕原点先逆时针旋转45◦,再平移(1,2), 计算出变换后点的坐标(要求用齐次坐标进行计算). UE4 知识点 主要矩阵 FMatrix FBasisVec ...

  9. python10作业思路及源码:类Fabric主机管理程序开发(仅供参考)

    类Fabric主机管理程序开发 一,作业要求 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线 ...

  10. SQLServer2005创建定时作业任务

    SQLServer定时作业任务:即数据库自动按照定时执行的作业任务,具有周期性不需要人工干预的特点 创建步骤:(使用最高权限的账户登录--sa) 一.启动SQL Server代理(SQL Server ...

随机推荐

  1. Anaconda下载安装步骤

    下载地址 下载比较慢的,用迅雷下,点击复制地址,然后在迅雷里面直接创建连接 Anaconda基于python3.8 Anaconda基于python3.6的 安装步骤 没啥说的,一路安装,中间有个勾选 ...

  2. 用BootstrapBlazor组件制作新增Customer Order的页面

    1.在Shared目录下新建OrderCreateView.razor文件: 2.在OrderCreateView.razor里用最简单的表格准备好布局 3.准备好BootstrapBlazor的组件 ...

  3. 第140篇:微信小程序的登录流程

    好家伙,补补补   顶不住了,跑不掉了,这部分的知识还是要补上   来看看微信小程序登录的完整流程   最左边的一列就是前端负责的部分了 几个关键的参数: code:一个用户登录凭证,就是一个临时的t ...

  4. 第135篇:Three.js基础入门

    好家伙,这东西太帅了,我要学会   先放张帅图(都是用three.js做出来的,这我学习动力直接拉满)    还有另外一个 Junni is... 帧数太高,录不了   开始学习 官方文档 1.Thr ...

  5. Redis动态配制,限内存,免重启

    p.p1 { margin: 0; font: 14px Menlo; color: rgba(0, 255, 255, 1); background-color: rgba(0, 0, 0, 0.8 ...

  6. Geospatial Data 在 Nebula Graph 中的实践

    本文首发于 Nebula Graph Community 公众号 本文主要介绍了地理空间数据(Geospatial Data)以及它在 Nebula Graph 中的具体实践. Geospatial ...

  7. 7、zookeeper应用场景-分布式锁

    分布式锁 实现原理:有序节点+watch监听机制实现 分布式锁有多种实现方式,比如通过数据库.redis都可实现.作为分布式协同工具Zookeeper,当然也有着标准的实现方式.下面介绍在zookee ...

  8. 一文搞懂Vue的MVVM模式与双向绑定

    v-model 是 Vue.js 框架中用于实现双向数据绑定的指令.它充分体现了 MVVM(Model-View-ViewModel)模式中的双向数据绑定特性.下面我们将详细解释 v-model 如何 ...

  9. Mysql数据库未添加索引引发的生产事故

    最近开发的新功能主要是首页的红点提示功能,某个用户登录系统app,然后进入某一个功能模块, 在该功能下面有很多地方可以操作,新功能就是根据用户信息查询当月是否存在新的数据.总共有四五 个地方如果出现增 ...

  10. 分支合并规则 dev master v1.0.XX 三条分支的合并原则

    分支合并规则 dev master v1.0.XX 三条分支的合并原则