(注:此文以个人知识框架为基础拓开,为方便后期回顾,在保留原课程内容框架的基础上,个别概念的介绍可能过于简单,感兴趣读者可到 GAMES 平台深入学习)
目录
Blinn-Phong Reflectance Model(Blinn-Phong 反射模型)
方案:Bilinear Interpolation(双线性插值)
方案二:Anisotropic Filtering(各向异性过滤:矩形范围查询)
方案三:EWA filtering(Elliptically Weighted Average:椭圆加权平均)
概要
定义:The process of applying a material to an object(对物体应用材质的过程)
Illumination & Shading
Blinn-Phong Reflectance Model(Blinn-Phong 反射模型)
Diffuse Reflection(漫反射项)
- 漫反射和观察方向 v 无关。
- 关于公式的解释:
kd:漫反射系数,不同材质kd不同,也可理解为颜色;
(I/r^2):I(Intensity)光照强度;r:光线传播球面半径,即是光源到观测点的距离,此项表示I与r^2(球面半径)成反比;
max(0,n.dot(l)):光线在物体表面法线方向上的分量(点乘算余弦),不能为负方向(光线从物体下表面打到观测点无意义);
Specular term(高光项)
1.各项参数参考 diffuse,区别:犹豫半程向量 h 的引入,高光效果和观察方向 v ,光线方向 I有关系。
2.指数p:控制高光范围大小(p越大高光越小)。
Ambient Term(环境光项)
Shading Frequencies(着色频率):
Flat shading:逐三角形(逐平面)着色
Gouraud shading:逐顶点着色
Phong shading:逐像素着色
Graphics Pipeline
Texture Mapping(纹理映射)
定义:在一个二维平面上定义三维物体表面每一个点的基本属性。
手段:定义每一个小三角形每一个顶点对应纹理上坐标(UV)上的位置。
Barycentric coordinates(重心坐标)
定义:
1.三角形平面内的任意点,可通过不同系数的三顶点和的方式表达,系数均非负,表示点在三角形内。
2.各系数的值为:该点与三角形任意两点的面积 与 原三角形面积的比值。公共边对应的原三角形顶点即为该系数对应的顶点。(如下图:系数 beta 对应面积 AB 对应边 AC 对应顶点 B)
3.通用公式:
目的:在三角形内部进行线性插值,给定三顶点颜色、法线向量、纹理坐标的情况下,三角形内部点平滑过渡效果
Texture queries(纹理查值)
纹理精度太小(多个像素对应一个纹理值)
场景:纹理精度不够的情况
方案:Bilinear Interpolation(双线性插值)
取像素点对应纹理空间周围的四个点(u00,u01,u10,u11),按照水平和垂直方向上的比重计算平滑的过渡值。
效果对比:从左到右,直接取纹理;双线性插值;(不知如何翻译:取纹理空间周围16个点,原理同双线性插值);
纹理精度太大(多个纹理值对应一个像素)
(注:此小节涉及的所有所有形状(mipMap:适合方形;Anisotropic:适合矩形;EWA:对角线)指的是屏幕4个像素点映射到纹理空间后形成的大致形状,以mipmap 为例:下图4个点映射到纹理空间可近似成一个正方形)。
场景:透视投影远景的纹理值查询,直接查纹理会产生摩尔纹(远)和锯齿(近)。
方案一:MipMap(方形范围查询)
特性:对正方形(不是矩形)纹理做不准确的快速范围查询。
定义:存储多层次纹理副本,根据实际精度直接查询不同层次副本
图像金字塔,(存储开销增加1/3:1+1/4 + 1/16 + 1/64+...)
怎么查?计算屏幕两个像素点(左)对应纹理空间(右)之间的的距离L,层次D = log2(L),也就是在纹理空间第几层边长为L的(近似看成)正方形 会变成1个像素
不同层次之间的过渡问题:1.8层怎么查?
Trilinear Interpolation(三线性插值):对第1和第2层分别做双线性插值,将两个结果再做一次线性插值;
problem:对(坐标轴对齐)矩形和对角线(也可看成是坐标轴不对齐的矩形)不友好
方案二:Anisotropic Filtering(各向异性过滤:矩形范围查询)
现代GPU在硬件层次支持各向异性过滤,即在原 texture(下图左上第一个) 的基础上进行横向和纵向(各项异指的就是横纵向不同)不同缩放比例的采样。最大支持 x16 级别各向异性,即横纵各16个样本(每个样本横 \ 纵向变为前一个1/2)。因此不会产生更多的内存开销。(关于开销,课程讲的是无限逼近原3倍,这是在不支持 GPU 采样的基础上)。
solve:坐标轴对齐的矩形查询;
problem:对角线查询;
other:课程中对此方案只做了简单介绍。关于如何查询等细节,有兴趣读者可参考知乎这篇文章。
方案三:EWA filtering(Elliptically Weighted Average:椭圆加权平均)
此方案课程只简单提了下,说说我个人的理解:
- 为什么是椭圆?椭圆是 bounding-box,我们知道如果是规则多边形,用圆做 bouding-box 可以实现最大覆盖率,EWA 方案主要为了处理不规则多边形问题,所以是椭圆。
- 椭圆中心的 uv 坐标表示屏幕空间变化一个像素,纹理空间 uv 的变化(当前像素的纹理坐标对于屏幕空间坐标的微分),椭圆的长轴和短轴的比例由实际的微分数值大小确定,这样可以精确反应出(屏幕空间)映射到纹理空间后 uv 的变化(可理解为基于椭圆的各向异性)。
- 加权平均?对映射到纹理空间后的在椭圆包围盒内的点做加权平均。不同范围的结果值存储在不同的椭圆环中,方便做加速。
- 查询?由椭圆中心由内而外多次查询,直到椭圆包围了纹理空间的所有采样点。
- other: 关于此方案,网络上的中文说明莫衷一是,有兴趣的且有耐心的同学可以参考这篇论文
Applications of textures
定义纹理
In modern GPUs texture = memory + range query (filtering)(现代 GPU 中,支持范围查询的内存数据)。
Environmental Lighting(环境光)
Bump/Normal Mapping(凹凸/法线贴图)
- 定义:定义某个点的相对高度/法线。模拟凹凸不平的效果(不改变几何信息)
- (2D)切线向量 (1,dp), 根据旋转公式:法线向量(-dp, 1),归一化:n(p) = (-dp, 1).normalized();
- (3D) n = (-dp/du, -dp/dv, 1).normalized();(注:n(p) = (0,0,1)为局部坐标系,计算完法向量后需要变回世界坐标系)
Displacement mapping(位移贴图)
- 相比 bump map,对顶点高度做了实际移动(这意味着三角形要足够小)。
- other:directX 提供了一种动态细分(三角形)的技术:开始不需要一个足够细的模型,根据需要判断是否需要做三角形的细分。
作业三
题目简单介绍
在作业2深度插值的基础上,实现法向量、颜色、纹理颜色的插值;加上投影变换矩阵,直接运行得到法向量实现结果;依次实现 Blinn-Phong 模型计算Fragment Color;Texture Shading Fragment Shader;Bump mapping.;displacement mapping;
核心代码
1.法向量、颜色、纹理颜色的插值
/// <summary>
/// Screen space rasterization
/// </summary>
/// <param name="t">triangle</param>
/// <param name="view_pos">view position</param>
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4();
// compute the bounding box
int max_x = ceil(MAX(v[0].x(), MAX(v[1].x(), v[2].x())));
int max_y = ceil(MAX(v[0].y(), MAX(v[1].y(), v[2].y())));
int min_x = floor(MIN(v[0].x(), MIN(v[1].x(), v[2].x())));
int min_y = floor(MIN(v[0].y(), MIN(v[1].y(), v[2].y())));
for (int x1 = min_x; x1 <= max_x; x1++) {
for (int y1 = min_y; y1 <= max_y; y1++) {
if (insideTriangle((float)x1 + 0.5, (float)y1 + 0.5, t.v)) {
//计算三角形重心坐标
auto [alpha, beta, gamma] = computeBarycentric2D(x1, y1, t.v);
//透视校正插值
float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal;
if (depth_buf[get_index(x1, y1)] > z_interpolated) {
auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0], t.color[1], t.color[2],1);
auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2],1);
auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2],1);
//view_pos[]是三角形顶点在view space中的坐标,插值是为了还原在camera space中的坐标
//详见http://games-cn.org/forums/topic/zuoye3-interpolated_shadingcoords/
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);
payload.view_pos = interpolated_shadingcoords;
//Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
auto pixel_color = fragment_shader(payload);
// 更新深度
depth_buf[get_index(x1, y1)] = z_interpolated;
// 更新color
set_pixel(Eigen::Vector2i(x1,y1), pixel_color);
}
}
}
}
}
2.blinn-phong 模型实现
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)
{
// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular*
// components are. Then, accumulate that result on the *result_color* object.
// 除了光照 light.intensity 所有向量均按单位向量计算。
Eigen::Vector3f light_direc = (light.position - point).normalized();
Eigen::Vector3f vision_direc = (eye_pos - point).normalized();
float r_pow2 = (light.position - point).dot(light.position - point);
// diffuse
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_pow2) * MAX(0.0f, normal.normalized().dot(light_direc));
// specular
Eigen::Vector3f h = (vision_direc + light_direc).normalized();//半程向量
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_pow2) * pow(MAX(0.0f, normal.normalized().dot(h)), p);
// ambient
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += (Ld + Ls + La);
}
return result_color * 255.f;
}
3.Texture Shading Fragment Shader 实现
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;// 只需要将漫反射系数替换为 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 = texture_color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal;
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular*
// components are. Then, accumulate that result on the *result_color* object.
Eigen::Vector3f light_direc = (light.position - point).normalized();
Eigen::Vector3f vision_direc = (eye_pos - point).normalized();
float r_pow2 = (light.position - point).dot(light.position - point);
// diffuse
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_pow2) * MAX(0, normal.normalized().dot(light_direc));
// specular
Eigen::Vector3f h = (vision_direc + light_direc).normalized();//半程向量
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_pow2) * pow(MAX(0, normal.normalized().dot(h)), p);
// ambient
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += (Ld + Ls + La);
}
return result_color * 255.f;
}
4.Bump mapping 实现
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;
float x = normal.x();
float y = normal.y();
float z = normal.z();
Vector3f t = Eigen::Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
Vector3f b = normal.cross(t);
Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w, v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Vector3f ln(-dU, -dV, 1);
normal = (TBN * ln).normalized();
return normal * 255.f;
}
5.displacement mapping 实现
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;
float x = normal.x();
float y = normal.y();
float z = normal.z();
Vector3f t = Eigen::Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
Vector3f b = normal.cross(t);
Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w, v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Vector3f ln(-dU, -dV, 1);
point += (kn * normal * payload.texture->getColor(u, v).norm());
normal = TBN*ln;
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular*
// components are. Then, accumulate that result on the *result_color* object.
Eigen::Vector3f light_direc = (light.position - point).normalized();
Eigen::Vector3f vision_direc = (eye_pos - point).normalized();
float r_pow2 = (light.position - point).dot(light.position - point);
// diffuse
Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_pow2) * MAX(0, normal.normalized().dot(light_direc));
// specular
Eigen::Vector3f h = (vision_direc + light_direc).normalized();
Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_pow2) * pow(MAX(0, normal.normalized().dot(h)), p);
// ambient
Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
result_color += (Ld + Ls + La);
}
return result_color * 255.f;
}
效果
1.normal map
2.blinn-phong
3. 纹理贴图
4.
5.
(本小节完)