流水线¶
- 基本流程:
- CPU 准备要渲染你的数据(位置信息、法线方向、顶点颜色等),并将数据加载到显存
- CPU 设置渲染状态(顶点着色器、片元着色器、光源、材质等)
- 调用 Draw Call 使用 GPU 开始绘制
- CPU 准备要渲染你的数据(位置信息、法线方向、顶点颜色等),并将数据加载到显存
- 逐片元操作中通过模板测试、深度测试决定可见性,之后进行混合,输出到颜色缓冲区
光栅化¶
- 光栅化:将图像显示在屏幕上,矢量图形转化为像素网格
- 视锥
- 影响因素:长宽比、垂直可视角度(红线角度)
- 屏幕
- 将标准正方体映射到屏幕上
基本图形绘制¶
绘制线段¶
- 这里使用一种中点算法的隐式方程进行表示
- 对于直线 \((y_1-y_0)(x-x_0)=(x_1-x_0)(y-y_0)\)
- 展开整理后就可以表示为隐式方程:\(f(x,y)=(y_0-y_1)x+(x_1-x_0)y+x_0y_1-x_1y_0\quad and \quad x_0\le x_1\)
- 从 \(x_{0}\) 到 \(x_{1}\) 每列有且仅有一个像素
- 在从左到右就你行遍历时,对比表达式计算结果的值和两个候选像素的中点,如果直线低于该中点则绘制下面的像素,否则绘制上面的像素
- 为了提升效率,可以重复使用上一步的计算结果,根据线段的隐式表达,有 \(f(x+1,y+1)=f(x,y)+(y_0-y_1)+(x_1-x_0)\)
绘制三角形¶
- 使用最简单的多边形——三角形来表示一切
-
最简单多边形,任何图形都可以划分为三角形
-
判断点是否在三角形内部
- 通过叉乘得到三个向量后,可以通过两两点乘来确定方向是否相同
static bool insideTriangle(int x, int y, const Vector3f* _v)
{
Vector3f AB = _v[1] - _v[0];
Vector3f BC = _v[2] - _v[1];
Vector3f CA = _v[0] - _v[2];
Vector3f AP = Vector3f(x, y, 0) - _v[0];
Vector3f BP = Vector3f(x, y, 0) - _v[1];
Vector3f CP = Vector3f(x, y, 0) - _v[2];
Vector3f cross1 = AB.cross(AP);
Vector3f cross2 = BC.cross(BP);
Vector3f cross3 = CA.cross(CP);
if(cross1.dot(cross2) > 0 && cross2.dot(cross3) > 0 && cross3.dot(cross1) > 0)
return true;
return false;
}
- 简易采样(就是离散化)决定像素是否显示
- 使用像素中心进行采样
-
- 判断中心点是否在三角形内部(使用向量叉乘,在三条边同方向)
- 不需要遍历所有的点,可以使用包围盒进行优化
- 更加优秀的算法
-
但是这种方法会出现严重的走样问题
-
使用重心坐标进行绘制,可以很方便的利用图元端点插值计算出像素的属性 \(c=\alpha c_0+\beta c_1+\gamma c_2\),即Gouraud 插值
- 在绘制共享边线和顶点的三角形时,如果相邻三角形的颜色不同,则图像将取决于绘制这两个三角形的顺序。
- 为了避免顺序问题和消除孔洞的栅格化三角形最常见的方法是使用当且仅当像素的中心位于三角形内部时才绘制像素的惯例。也就是说,像素中心的重心坐标都在(0,1)区间内。
- 蛮力计算
- 如果像素重心正好落在三角形边缘,则要特殊处理,防止出现空洞或重复绘制:最常见的是Top-Left Rule(左上为内、右下为外)。
反走样&抗锯齿¶
- 时间、空间上的问题,采样频率不足就会引起走样
-
先模糊后采样
-
- 傅里叶级数展开
- 傅里叶变换:实现函数在时域和频域之间的变化
- 仅仅有频谱(振幅谱)是不够的,我们还需要一个相位谱(不同波的起始相位)
- 对于图片来说
- 时域表示图片上的像素变化快慢
-
频域用黑色图表示,中间为低频四周为高频
-
滤波
- 在频域可以十分方便的实现滤波(去除指定竖线(去除某个频率))
- 卷积
- 信号范围内平均处理
- 时域的相乘等于频域的卷积,时域的卷积等于频域相乘
- 采样就是重复原始信号频谱
- 左侧的在时域上相乘,域右侧上做卷积是等价的
- 采样率过低时会发生频谱重叠
- 模糊(卷积)可以减少重叠,减少走样(屏幕分辨率高时走样少,是因为采样频率高)
- MSAA多采样反走样(卷积计算开销大)
- 像素内部添加更多的采样点
- 覆盖采样点数目来决定模糊状态(抗锯齿)
TAA¶
- 将多次采样的过程分布到每一帧当中去,也即每一帧都会采样历史几帧的数据(而不是)
- \(P_t=(1-\alpha)\cdot P_{t-1}+\alpha\cdot c_t\) 动态混合,结合上一帧的历史信息 \(P_{t-1}\) 和当前帧的颜色结果 \(c_{t}\)
- 由于物体运动,遮挡状况会发生变化,可能产生鬼影和闪烁问题,有以下优化
- 设置色差阈值,对比当前帧和历史帧,将历史帧的颜色截断在合理的范围内。
- 抖动投影 (Jitter),在每一帧渲染时,给相机投影矩阵加入亚像素级的偏移(通常使用预先定义的抖动序列,如 Halton、蓝噪声),使得同一场景多帧渲染的采样点稍有不同。
- 使用运动向量修正坐标,根据运动向量,将上一帧累积的颜色、深度等信息重投影到当前帧像素上。
- 基本思路
输入: 当前帧渲染颜色 C_cur[i,j] 深度 D_cur[i,j] 运动向量 MV[i,j] (从前帧到当前帧的屏幕空间偏移) 历史累积颜色 C_hist_last 历史累积深度 D_hist_last 输出: 当前累积颜色 C_accum 更新后的历史颜色 C_hist_new, 深度 D_hist_new 参数: α(历史权重系数,0~1) 步骤: 1. 投影抖动 // 在渲染过程中,已对投影矩阵进行 jitter 偏移 2. 重投影历史颜色 for each 像素 p = (i,j): p_prev = p + MV[i,j] // 将当前像素映射回前一帧位置 C_reproj = sample(C_hist_last, p_prev) D_reproj = sample(D_hist_last, p_prev) 3. 验证有效性(抗鬼影处理) 如果 |D_reproj - D_cur[i,j]| > depthThreshold 或 |C_reproj - C_cur[i,j]| > colorThreshold: C_reproj = C_cur[i,j] α_effective = 0 否则: α_effective = α 4. 融合 C_accum[i,j] = lerp(C_cur[i,j], C_reproj, α_effective) // lerp(a,b,α) = α*b + (1-α)*a 5. 更新历史缓冲 C_hist_new[i,j] = C_accum[i,j] D_hist_new[i,j] = D_cur[i,j] 6. 输出 C_accum 作为最终颜色
采样插值¶
- 按照参数的多项式的最高次数可以分成一次插值(如双线性插值)、二次插值等
Lanczos 插值¶
- 权重分布是钟形(近似sinc函数形状),中心大、远离中心迅速减小。
- 通常会采样更多邻居像素(如3x3、4x4),不是只看最近的四个。
- 能更好地保留高频细节、抗混叠,画质更佳,但计算略复杂。
- \(L(x)=\begin{cases}\frac{a\cdot\sin(\pi x)\cdot\sin(\pi x/a)}{(\pi x)^2},&|x|<a\\0,&\mathrm{otherwise}&\end{cases}\)
- 其中 x 为距离中心的归一化距离
- a 为窗口参数,决定核的宽度
-
代码示例
float lanczos_weight(float x) { //return fastLanczos(x); float a = 0.75; float pi = 3.141592653589793; x = abs(x); /*if (x == 0.0) return 1.0;*/ /*if (x > a) return 0.0;*/ float x_safe = x; float pi_x = pi * x_safe; float pi_x_over_a = pi * x_safe / a; float xe0 = step(x, 0); return (a * sin(pi_x) * sin(pi_x_over_a)) / (pi_x * pi_x); // * (1 - xe0) * step(x, a) + xe0; }
-
快速(近似)Lanczos 插值
//输入到中心店的偏移距离,输出对应的权重 float fastLanczos(float base) { float y = base - 1.0; float y2 = y * y; float y_temp = 0.75 * y + y2; return abs(y_temp * y2); }
基于 Catmull-Rom 样条插值的双三次采样¶
float4 BiCubicCatmullRom5Tap(Texture2D<float4> iChannel0, float2 P, float2 c_textureSize)
{
float2 InvSize = 1.0 / c_textureSize;
float2 Weight[3];
float2 Sample[3];
float2 UV = P * c_textureSize;
//计算得到所在格子的中心
float2 tc = floor(UV - 0.5) + 0.5;
//预计算
float2 f = UV - tc;
float2 f2 = f * f;
float2 f3 = f2 * f;
float2 w0 = f2 - 0.5 * (f3 + f);
float2 w1 = 1.5 * f3 - 2.5 * f2 + float2(1.0f, 1.0f);
float2 w3 = 0.5 * (f3 - f2);
float2 w2 = float2(1.0f, 1.0f) - w0 - w1 - w3;
Weight[0] = w0;
Weight[1] = w1 + w2;
Weight[2] = w3;
Sample[0] = tc - float2(1.0f,1.0f);
Sample[1] = tc + w2 / Weight[1];
Sample[2] = tc + float2(2.0f,2.0f);
Sample[0] *= InvSize;
Sample[1] *= InvSize;
Sample[2] *= InvSize;
float sampleWeight[5];
sampleWeight[0] = Weight[1].x * Weight[0].y;
sampleWeight[1] = Weight[0].x * Weight[1].y;
sampleWeight[2] = Weight[1].x * Weight[1].y;
sampleWeight[3] = Weight[2].x * Weight[1].y;
sampleWeight[4] = Weight[1].x * Weight[2].y;
float4 Ct = iChannel0.SampleLevel(smpLinearClamp,float2(Sample[1].x, Sample[0].y),0) * sampleWeight[0];
float4 Cl = iChannel0.SampleLevel(smpLinearClamp,float2(Sample[0].x, Sample[1].y),0) * sampleWeight[1];
float4 Cc = iChannel0.SampleLevel(smpLinearClamp,float2(Sample[1].x, Sample[1].y),0) * sampleWeight[2];
float4 Cr = iChannel0.SampleLevel(smpLinearClamp,float2(Sample[2].x, Sample[1].y),0) * sampleWeight[3];
float4 Cb = iChannel0.SampleLevel(smpLinearClamp,float2(Sample[1].x, Sample[2].y),0) * sampleWeight[4];
float WeightMultiplier = 1. / (sampleWeight[0] + sampleWeight[1] + sampleWeight[2] + sampleWeight[3] + sampleWeight[4]);
return (Ct + Cl + Cc + Cr + Cb) * WeightMultiplier;
}
反走样的方法总结¶
- 抗锯齿
- MSAA:像像素内添加更多的采样点,根据覆盖采样点的数目决定显示状态(不透明度)
- FXAA:分析渲染后的图像来检测锯齿边缘,性能开销较低
- TAA:通过多帧信息提高图像质量,在时间上的累计和平滑,减少锯齿同时保持图像细节
- 超分辨率
- 深度学习(DLSS)::通过分析图像的局部模式和纹理信息,超分辨率算法尝试推断高分辨率图像中可能出现的细节和结构。通过学习大量低分辨率与高分辨率图像对应的关系,能够生成高质量的高分辨率图像。
深度缓冲(测试)¶
- 如何绘制深度不同具有遮挡关系的不同图像(三角形)?
- 深度缓冲
- 生成图象时额外生成一个深度图,用于表示每个像素的深度信息
- 根据深度信息判断是否要进行覆盖
着色¶
- 对不同物体应用不同材质
- 着色只考虑物体自身,不考虑其他的物体的影响(如阴影等)
Blinn-Phong 反射模型¶
- 高光、漫反射、环境光
- 漫反射
- 平面与光线的夹角会影响反射的强度,接受的光的比率可以使用 \(cos\theta\) 表示
- 对于点光源,距离光源的距离也会影响反射的强度(总能量一定,距离越大半径越大,单位面积自然越小)对于半径为 r 的位置,光照强度使用 \(I/r^2\) 来表示
- 漫反射与 v 的方向无关(均匀反射)
- 高光
- 观察方向和镜面反射方向接近时看到高光
- 通过半程向量(角平分线)和法线的夹角判断接近程度
-
用指数表示高光衰减
-
环境光
- 认为环境光恒定
-
-
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 = amb_light_intensity.cwiseProduct(ka); Eigen::Vector3f light_dir = (light.position - point).normalized(); float r2= (light.position - point).squaredNorm(); Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity) / r2 * std::max(0.0f, normal.normalized().dot(light_dir)); Eigen::Vector3f view_dir = (eye_pos - point).normalized(); Eigen::Vector3f half_dir = (view_dir + light_dir).normalized(); Eigen::Vector3f specular = ks.cwiseProduct(light.intensity) / r2 * std::pow(std::max(0.0f, normal.normalized().dot(half_dir)), p); result_color += ambient + diffuse + specular; } return result_color * 255.f; }
着色频率¶
- 面着色
- 点着色
- 像素着色
- 确定顶点的法线
- 使用相邻面的法线来求平均
- 像素的法线:使用重心坐标确定
- 随着采样频率提升(模型面数)不同着色频率之间的差距越来越小
实时渲染管线¶
- 指渲染的一系列过程,图像是如何渲染出来的
- 输入三维空间的点
- 投影到二维平面上(mvp 矩阵变换)
- 点构成三角形
- 对三角形进行光栅化(采样(反走样)+深度缓冲)
- 对三角形进行着色(如布林冯反射模型、纹理摸映射)
纹理映射¶
- 定义任何一个点的属性
- 纹理映射:3 维->2 维
-
通过坐标三角形顶点颜色映射
-
知道了顶点的着色,还需要插值,对内部其他点进行着色(如何通过三角形的顶点得到内部参数的平滑过渡)
-
重心坐标
- 三角型内任一点可以使用顶点坐标的线性组合表示(参数和为 1(在三角形所在平面上)且非负(在三角形内部))
- 可以使用三角形面积之比计算出来
- 将任何一点的转化为用顶点表示
- 计算得到参数后可以用这几个参数做插值 \(V=\alpha V_A+\beta V_B+r V_C\)
- 问题:投影之后的重心坐标会发生变化,因此要先插值再投影
-
如果需要再投影之后进行插值,则需要进行透视矫正插值
-
计算质心坐标
- \(P=(1-u-v)A+uB+vC\) (对于三角形内的点 u, v, 1-u-v 均大于 0)
- \(P=A+uAB+vAC\to uAB+vAC+PA=0\)
- 本质上就是求解这个方程组(之后得到 uv 判断是否大于零,进而判断是否在三角形内部)对于三维点也是一样的,因为两个方程就能解出两个量了
- 求解方程就是找一条直线与 (ABx, ACx, PAx) and (ABy, ACy, PAy) 垂直,这可以通过一次叉积解决
-
透视矫正
- 因为线性插值假设了所有点在同一平面上,但实际上,由于透视效果,远处的物体应该在视觉上显得更小,这导致了深度(z 值)和其它依赖于深度的属性不能简单地线性插值。(比如深度变化的的位置的颜色变化在投影之后被压密了)
- 透视变换和透视除法确保了三维场景在二维屏幕上的正确投影,但是在处理纹理映射和顶点属性插值时,直接应用线性插值会因为透视效果的非线性特征而导致失真。
-
TODO¶
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4();
float aabb_min_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));
float aabb_min_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));
float aabb_max_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));
float aabb_max_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));
for(int i=aabb_min_x; i<=aabb_max_x; i++){
for(int j=aabb_min_y; j<=aabb_max_y; j++){
if(!insideTriangle(i, j, t.v))
continue;
auto[alpha, beta, gamma] = computeBarycentric2D(i, j, 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;
int index = get_index(i, j);
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.0);
auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1.0);
auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1.0);
auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1.0);
fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
auto pixel_color = fragment_shader(payload);
set_pixel(Eigen::Vector2i(i, j), pixel_color);
}
}
}
}







纹理应用¶
- 环境光照
- 上下扭曲严重,不是均匀描述
- 立方体包围法
- 凹凸贴图
- 定义点相对基础面的相对高度
- 通过法线差异模拟高度变化
- 扰动法线
- 法线通过求导得到的切线旋转得到
- 二维图像上同理通过黑白变化计算
- 位移贴图
- 与凹凸贴图的输入相同,但是位移贴图真正改变几何信息,对顶点做位移相比上更逼真,因为凹凸贴图在边界上会露馅
剔除¶
- 在对象顺序渲染中,面对复杂场景可能会浪费大量时间在实际上看不到的对象上,识别和丢弃不可见的几何图形,以节省处理它所花费的时间,被称为剔除。
- 视体积剔除:移除视图体外的几何体。
- 遮蔽剔除:移除可能在视图体内,但被更近的几何体遮挡的几何体。
- 背面剔除:移除背对相机的图元。
视体积剔除¶
- 视锥之外的三角形不应该被渲染,投影后可能变成几段错误的位置,如无限远、翻转等(特别是摄像机后方的),因此要进行裁剪
- 裁剪(Clipping)就是在投影和光栅化之前,把所有超出视锥体的部分切除掉,只保留视锥体内或与视锥体相交的部分。
- 在透视变换之前裁剪:直接对视图体积的 8 个顶点(\([l,r]\times[b,t]\times[n,f]\))做逆透视变换,再求解出 6 个平面的方程。
- 在透视除法钱进行裁剪:在现代 3 D 渲染管线中,顶点经过投影变换后会处于四维齐次坐标 \((x, y, z, w)\)。在执行透视除法和光栅化之前,系统会对每个三角形的顶点进行裁剪,检查其坐标是否在裁剪范围(如 \(-w \leq x \leq w\),\(-w \leq y \leq w\),\(-w \leq z \leq w\))之内。若三角形部分顶点超出裁剪体积,则在裁剪边界与三角形边的交点处插入新顶点,将超出部分剔除,只保留在裁剪体积内的部分。这样能确保所有即将光栅化的三角形都落在可见区域内,避免渲染错误。(透视投影后视锥变成立方体,计算更加方便)
信号处理¶
- 连续信号->离散信号:采样
- 离散信号->连续信号:滤波
采样¶
傅里叶变换¶
防止混叠伪影¶
理想与实用滤波器¶
图像采样¶
- 图像采样指的是把一个连续的图像转化为像素网格,如果直接在每个像素中心点取值可能出现很多问题
- 锯齿
- 摩尔纹
- 这是因为连续图像里有比像素网格更细的结构,这些高频内容在采样时被折叠进低频,结果就是锯齿和莫尔纹。
- 也就是图像中包含了太多小尺度特征,需要在采样前通过滤波进行平滑,即在像素位置周围的区域内对图像进行平均,而不仅仅是在单个点处取值。
卷积¶
- 卷积可以用于连续函数 \(Convolution=f\,\star\,g\) 也可以用于离散序列 \((a\star b)[i]=\sum_{j=i-r}^{i+r} a[j]\,b[i-j]\)
- 性质:
- \((a\star b)[i]=(b\star a)[i]\)
- \((a\star(b\star c))[i]=((a\star b)\star c)[i]\)
- \((a\star(b+c))[i]=(a\star b+a\star c)[i]\)
- \((f\star g)(x)=\int_{-\infty}^{+\infty} f(t)g(x-t)dt\)
- 离散-连续函数卷积 \((a\star f)(x)=\int_{i}a[i]f(x-i)\)
- 二维卷积
- \((a\star b)[i,j]=\sum_{i'}\sum_{j'}a[i',j']\,b[i-i',j-j']\)
- \((f\star g)(x,y)=\int\int f(x',y')g(x-x',y-y')dx'dy'\)
- \((a\star f)(x,y)=\sum_i\sum_j a[i,j]f(x-i,y-j)\)
卷积滤波器¶
- 当一个连续的过滤器被用来从离散的序列重建一个连续的函数时,如果它严格通过采样点,这种滤波器称为内插滤波器
- 滤波器函数为 \(f(x)\) 离散采样点 \(a[i]\),重建连续信号的卷积为 \(g(x)=\sum_ia[i]\left.f(x-i)\right.\)
- 滤波器函数的上凸波形决定了离采样点越远 \(a[i]\) 的影响力越小,比如 Catmull-Rom 的权重在 \(|x-i|>2\) 时为 0,\(|x-i|\) 越小 \(f(x-i)\) 越大
-
这个计算过程的本质就是卷积计算,就是用滤波器对离散采样进行卷积计算,得到连续信号
-
一个具有负值的过滤器有振铃或超调:它会在被过滤函数值的急剧变化周围产生额外的振荡。
- 这是因为如果权重有负值,就可能出现加权和大于最大值或小于最小值的情况。
- 在突变处,正负权重因信号剧变而无法相互抵消,造成局部极值(振铃)。
-
无波纹滤波器:用一个滤波器去重建一个常数序列,重建出来的连续函数仍然是常数,这个滤波器就是无波纹的,即不会产生多余的波动
- 即滤波器在证书网格上的和为 1(即系数和)
- 可以通过归一化将普通滤波器转化为无波纹滤波器 \((\bar{a\star f})(x)=\frac{\sum_i a[i]f(x-i)}{\sum_ia[i]}\)
图像的信号处理¶
卷积滤波的应用¶
- 卷积滤波器在图像的处理上有很多应用,如低通滤波器可以实现平滑的模糊效果。
- 与模糊相反的就是锐化操作,一种方法是使用“反锐化蒙版”程序:从原图中减去一张模糊图像(即先对原图做一次模糊,如高斯模糊)。 $$ \begin{aligned}I_{\mathrm{sharp}}&=(1+\alpha)I-\alpha(I\star f_{g,\sigma})\&=I\star\left((1+\alpha)d-\alpha f_{g,\sigma}\right)\&=I\star f_{\mathrm{sharp}}(\sigma,\alpha),\end{aligned} $$
- 为了保障亮度不换偏移,先加权原图后减去高斯模糊后的原图,从而实现对高频细节的加强,这两次操作也可以合并为一次卷积
重采样¶
- 重采样就是改变采样率或改变图像的大小,比如将一张大尺寸的图片显示在显示器上
- 一般的流程是先重构图像从离散像素中恢复出一个连续函数,然后再对这个新的连续函数进行采样
- 即:离散像素序列 → (重构滤波器) → 连续图像 → (采样滤波器) → 新的离散像素序列
- 最简单的重构方法就是只取最近的像素作为输出值,其实等价于每个新像素的区域都用1像素宽的Box滤波器重构,再点采样。这种方法快但质量差,容易有锯齿和马赛克。
- 合并重构滤波和采样滤波得到的就是重采样滤波
- 具体过程为:
- 本质上就是对新的大小间隔每个位置对原图像做滤波
- 具体过程为:
- 对于图像边缘,无法进行完整的滤波,因此在对边缘采样时应该修改滤波器使其不会超出序列的边界,同时重新进行规范化(保证相加总和为 1)防止亮度发生变化
- 通常选定一个滤波器形状,并根据输入和输出的相对分辨率对其进行缩放。当输出比输入采样更粗(下采样,或缩小图像)时,适当采样所需的平滑大于重建所需的平滑,因此我们根据输出样本间距来调整滤波器的大小。另一方面,当输出更精细采样(上采样,或放大图像)时,重构所需的平滑占主导地位,因此滤波器的大小由输入采样间隔决定。