说明

本次作业主要是实现对一个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. Delphi 实现刘谦春晚魔术

    看了博友的C# 实现刘谦春晚魔术很好,改成了delphi版的. 1 program Project1; 2 3 {$APPTYPE CONSOLE} 4 {$R *.res} 5 6 uses 7 S ...

  2. An Introduction to ANYDATA

    以下内容来自Oracle FAQ writen By Kevin,关于ANYDATA类型在项目中的应用. My newest project needed to create a record kee ...

  3. win32 - PE Executable and section inject

    #include <iostream> #include <Windows.h> #include <ShlObj.h> #include <Shlwapi. ...

  4. 新零售SaaS架构:订单履约系统的应用架构梳理

    订单履约系统的核心能力 通过分析订单履约的全流程和各个业务活动,我们可以梳理出订单履约的核心业务链路,基于业务链路,我们抽象出订单履约系统的三大系统能力,分别为履约服务表达.履约调度.物流配送. 履约 ...

  5. Mac环境下, VMware Fusion Pro下的虚拟机( CentOS 7)的 NAT网络配置

    前提实现说明 1.vm版本VMware Fusion Pro 12.1.0 2.centos版本centos7.6 1.虚拟机能访问外网,虚拟机能访问mac本机: 2.mac本机可以连接虚拟机 操作步 ...

  6. 【LeetCode二叉树#00】二叉树的基础知识

    基础知识 分类 满二叉树 如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树. 完全二叉树 除了底层外,其他部分是满的,且底层从左到右是连续的,称为完全二叉树 满二叉树一定是完全二 ...

  7. Excel Undo-Redo的编程问题

    Excel Undo历史栈对外是不透明的. 代码对Excel表单的编辑操作会清空Excel内部的Undo历史. Application.OnUndo只支持一次撤销,并且不支持ReDo. 使用DDE的方 ...

  8. 【Azure 存储服务】记一次调用Storage Blob API使用 SharedKey Authorization出现的403错误

    问题描述 使用Azure Storag Blob REST API上传文件,用SharedKey作为Authorization出现403错误. 错误消息 b'\xef\xbb\xbf<?xml ...

  9. 【Azure 事件中心】从Azure Event Hub中消费数据,如何查看当前消费客户端消费数据的Offset和SequenceNumber呢(消息偏移量和序列号)?

    问题描述 当通过Azure Event Hub SDK消费Event Hub中的消息时,必须指定一个Storage Account(存储账号)用于保存 Checkpoint (检查点). 比如在C#代 ...

  10. 【Azure Developer】如何通过Azure REST API 获取到虚拟机(VM)所使用的公共IP地址信息

    问题描述 如何通过Azure REST API 获取到虚拟机(VM)所使用的公共IP地址信息 问题解答 由于直接获取到的虚拟机信息(Virtual Machines - Get)中,并不会包含虚拟机的 ...