说明

本次作业主要是实现对一个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. Java中各种比较对象方式对比

    1.介绍 比较对象是面向对象编程语言的一个基本特征.在本教程中,我们将介绍Java语言的一些特性,这些特性允许我们比较对象.此外,我们还将研究外部库中的这些特性. 2.==和!=操作符 让我们从==和 ...

  2. java: -source 1.5 中不支持 diamond 运算符

    1.问题说明 平常在用idea编译spring boot多模块项目时,老是无端提示: Error:(107, 55) java: -source 1.5 中不支持 diamond 运算符 (请使用 - ...

  3. Oracle高级队列介绍

    原始链接:http://www.oracle-developer.net/display.php?id=411 oracle高级队列介绍 高级队列Advanced Queuing(AQ)在oracle ...

  4. SDL开发笔记(三):使用SDL渲染窗口颜色和图片

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  5. 【LeetCode剑指offer 02】矩阵中的路径(老鼠走迷宫plus,应用深度优先搜索与回溯机制)

    矩阵中的路径 https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/ 给定一个 m x n 二维字符网格 board 和一个字符串单词 ...

  6. CXP2.0的相机是否可以使用CXP1.1的Grabber

    可以 答案是肯定的. 目前CXP共有2个发布版本: 2011年发布CXP1.1 2021年发布CXP2.1,向后兼容,新标准增加了同步功能.数据率放大了一倍. 只要是符合CXP标准.接插件匹配,那么C ...

  7. PHP项目&TP框架&SQL&XSS&架构&路由&调试&写法

    开发基础-TP框架-入口&调试&路由&写法等 参考手册-TP5开发手册-为了掌握了解框架 首页文件看APP_PATH定义-为了后期分析核心代码 全局搜索:THINK_VERSI ...

  8. Glide源码解析四(解码和转码)

    本文基于Glide 4.11.0 Glide加载过程有一个解码过程,比如将url加载为inputStream后,要将inputStream解码为Bitmap. 从Glide源码解析一我们大致知道了Gl ...

  9. clickhouse 安装启动报<Error> Application: DB::Exception: There is no profile 'default' in configuration file. 以及常见的错误的总结

    1.启动时报错<Error> Application: DB::Exception: There is no profile 'default' in configuration file ...

  10. Kconnect使用sftp windows自定义协议

    终于有时间写点东西了,上次写东西已经是三个月之前了.自从出现了觉得一个月写一篇文章也没关系的想法之后就已经完全忘记有这回事儿了.一直觉得没有足够的时间,但是又想写出质量比较好的文章,所以就一直没有动笔 ...