Blinn-Phong Reflectance Model

  1. 反射模型中三种光照类型:

    • Specular highlights-镜面反射高光
    • Diffuse reflection-漫反射
    • Ambient lighting-环境光
  2. shading point着色点的参数定义(单位向量)

    • Viewer direction-观测方向, v\bm{v}
    • Surface normal-表面法线, n\bm{n}
    • Light direction-光线方向, l\bm{l}
    • Surface parameters(color, shininess,…)
      ShadingPoint
  3. Shading is Local,即着色是局部的,不考虑其他物体的存在,因此也不会产生阴影。

三种光照类型

Lambertian(Diffuse) Term-漫反射项

使用Lambert’s cosine law描述着色点接收的能量。
Lambert

假定光源辐射出的能量集中在球壳上,因此能量与距离的平方成反比。
LightFalloff

漫反射项公式

Ld=kd(I/r2)max(0,nl)L_d=k_d(I/r^2)\max(0,\bm{n}\cdot\bm{l})

式中,LdL_d为漫反射能量;kdk_d为漫反射项系数,表示着色点对光能的反射程度;I/r2I/r^2到达着色点的能量;max(0,nl)\max(0,n \cdot l)为着色点吸收能量的比例,并且保证只计算从着色点正面入射的光。
漫反射项与观测方向无关,因为漫反射向四面八方反射的量是一样的。
Diffuse

Specular Term-镜面反射项

观测方向与镜面反射方向接近时可以看到高光,即表面法线n\bm{n}与半程向量h\bm{h}接近时可以看到高光。

h=bisector(v,l)=(v+l)/v+l\bm{h}=bisector(\bm{v},\bm{l})= (\bm{v}+\bm{l})/{\begin{Vmatrix}\bm{v}+\bm{l}\end{Vmatrix}}

镜面反射项公式

Ls=ks(I/r2)max(0,cosα)p=ks(I/r2)max(0,nh)pL_s=k_s(I/r^2)\max(0,\cos\alpha)^p=k_s(I/r^2)\max(0,\bm{n}\cdot\bm{h})^p

式中,LsL_s为镜面反射能量;ksk_s为镜面反射项系数;I/r2I/r^2到达着色点的能量;max(0,nh)\max(0,\bm{n}\cdot\bm{h})用来表示表面法线与半程向量的接近程度,以描述高光程度;指数pp可以降低容忍度,这样只有在两个向量很接近时才会出现高光,与实际较为符合,Blinn-Phong模型中pp取值通常在100-200之间。
Specular

在镜面反射项的计算时,如果使用镜面反射方向与观测方向的接近程度描述高光程度,那便是Phong模型,Blinn-Phong模型使用半程向量是一个改进(Blinn-Phong模型使用半程向量与法线的点乘代替观察方向与镜面反射入射方向的点乘,避免了高光区域的截断问题,具体可参考Learn OpenGL - Advanced Lighting部分中的解释,此处不做展开)

Ambient Term-环境光项

Blinn-Phong模型中认为环境光强度IaI_a永远相同。
环境光项公式

La=kaIaL_a=k_aI_a

式中,LaL_a为环境光反射能量;kak_a为环境光系数。
Ambient

总结

Blinn-Phong

L=La+Ld+Ls=kaIa+kd(I/r2)max(0,nl)+ks(I/r2)max(0,nh)pL=L_a+L_d+L_s=k_aI_a+k_d(I/r^2)\max(0,\bm{n}\cdot\bm{l})+k_s(I/r^2)\max(0,\bm{n}\cdot\bm{h})^p

Shading Frequencies-着色频率

常见着色频率

Flat Shading

对整个三角形进行着色,每个面中着色结果完全一样。

Gouraud shading

对每个顶点进行一次着色,三角形内部的颜色通过三个顶点颜色的插值计算。

Phong shading

对每个像素进行一次着色,每个像素的法线通过插值计算。

此处Phong shading与Blinn-Phong模型讲的不是一回事,前者是一种着色频率,后者是一种着色模型,只是恰好都有同一个人——Bui Tuong Phong的贡献。

着色频率对比

不同着色频率效果的好坏是相对的,一定程度上也取决于具体模型,模型的面元数量、大小也会影响着色的效果。如下图对比
ShadingFreqs

法线计算

顶点法线

顶点的法线可以通过相邻面的法线的(加权)平均来计算。

Nv=(iNi)/iNiN_v=(\sum_iN_i)/{\begin{Vmatrix}\sum_iN_i\end{Vmatrix}}

VertexNorm

法线插值

已知顶点法线,可以使用重心坐标插值出面内各点的法线。
NormInter

Graphics Pipeline-图形管线

GraphicsPipline

Shader Programs-着色器程序
Shader本质上是一类能在硬件上执行的语言。以GLSL fragment shader为例,程序是一个通用程序,表示单个fragment的操作,每个fragment都会照此执行操作,无需for循环。
如果Shader程序中指定的是顶点操作,则它就是Vertex Shader顶点着色器;如果是fragment的操作,则是Fragment Shader片元着色器。

GPU
GPU可以认为是图形管线的硬件实现,其本身是高度并行化的处理器。
GPU中的Shader仍然是可编程的,比如Geometry Shader可以定义几何操作;Compute Shader可以执行通用计算,GPGPU的概念也由此而来。

Texture Mapping-纹理映射

纹理属性定义在物体表面上,可以说纹理是定义着色时各个着色点的属性。

任何3D物体的表面都可以认为是2D的,纹理上的点映射到物体表面上的点,类似于地球仪与展开的长方形地图的关系。
Surface2D

纹理上的点用坐标(u,v)(u,v)表示,u,v[0,1]u,v\in[0,1]
应用时,对于每个光栅化采样点,计算其对应的纹理坐标(u,v)(u,v),在纹理中查询(u,v)(u,v)坐标的纹理的值,最后设置采样点颜色。其中,纹理常常被定义为漫反射系数kdk_d
UVTexture

Barycentric Coordinates-重心坐标

三角形所在平面上任意点的坐标都可以表示为三个顶点坐标的线性组合,其线性组合的系数之和为1,当三个系数都为非负时,该点在三角形内(包括三角形边上的点)
BarycentricCoordinates

重心坐标可由面积比计算,如下图
BarycentricCoordinatesCalc
用叉乘计算上图面积即可推得如下公式:

α=(xxB)(yCyB)+(yyB)(xCxB)(xAxB)(yCyB)+(yAyB)(xCxB)β=(xxC)(yAyC)+(yyC)(xAxC)(xBxC)(yAyC)+(yByC)(xAxC)γ=1αβxxxxxxxxxxxxxxxxxxxxxxxxxxi\begin{aligned} \alpha&=\frac{-(x-x_B)(y_C-y_B)+(y-y_B)(x_C-x_B)}{-(x_A-x_B)(y_C-y_B)+(y_A-y_B)(x_C-x_B)} \\ \beta&=\frac{-(x-x_C)(y_A-y_C)+(y-y_C)(x_A-x_C)}{-(x_B-x_C)(y_A-y_C)+(y_B-y_C)(x_A-x_C)} \\ \gamma&=1-\alpha-\beta \phantom{xxxxxxxxxxxxxxxxxxxxxxxxxxi} \end{aligned}

利用重心坐标可以对任何属性(属性,位置、颜色、法线、深度等等)直接进行插值
作业2代码框架中深度插值正是使用了重心坐标
BarycentricCoordinatesInter
特别注意重心坐标并不具有投影不变性。先计算三维中的重心坐标再投影和直接计算投影后的重心坐标结果往往是不同的,因此三维空间中的属性要先在三维空间中进行插值,再进行投影。

Texture Magnification-纹理放大

A pixel on a texture —— a texel(纹素)

如果纹理太小,则许多像素点会对应到纹素之间的非整数纹理坐标,如果四舍五入选取最近的纹素,则多个像素对应同一个纹素,从而产生明显的不连续,导致模糊。因此需要一定的插值方法来获得较为连续的结果。
TextureMagnification

Bilinear Interpolation-双线性插值

取当前查询的纹理点周围临近的4个点u00,u10,u01,u11u_{00},u_{10},u_{01},u_{11}进行插值,即将当前点表示为临近四个点的线性组合以得到较连续的结果。
u00,u10u_{00},u_{10}插值得到u0u_0u01,u11u_{01},u_{11}插值得到u1u_1,再对u0,u1u_0,u_1插值得到所需纹理(水平和竖直两趟插值,即为双线性插值,水平竖直顺序可变)
BilinearInterpolation

Bicubic Interpolation-双三次插值
双三次插值取的是临近的16个点,水平与竖直各进行一次立方插值。

Mipmap纹理过滤

纹理过大问题

TextureTooLarge
上图中近处的像素覆盖不足一个纹素,这就是上一节纹理放大的问题,因此出现锯齿;而远处像素覆盖许多个纹素,从数字信号的角度分析,相当于用较低频的像素信号去采样较高频的纹理信号,势必会因为采样率过低导致纹素信息丢失,因此远处出现摩尔纹。

通过类似MSAA的超采样方法,将远处像素再分成许多个采样点确实可以解决上述采样率过低的问题,但是开销巨大。

注:上一节中的双线性插值等就是点查询(Point Query)问题,需要求出某一点;而本节纹理过滤是范围查询(Range Query)问题,需要求出某个范围内点的平均。

预处理

Mipmap
Mipmap是在渲染之前对纹理进行预处理,生成不同大小的纹理,如上图,每一层纹理分辨率宽高都是前一层的一半(图中为了展示分辨率变化将其显示为相同尺寸,实际上像素大小不变则纹理尺寸变成1/4)。
Mipmap相比原来的纹理,只多占用了1/3的内存。

层级计算

MipmapLevelCompute
将当前像素及其相邻像素(图中为相邻的右、上像素)映射到对应的纹素,计算当前纹素与相邻纹素距离的最大值LL,当前像素对应覆盖纹理的范围就是L×LL \times L大小的正方形。
上述计算结果实际上就是表示当前一个像素在纹理中覆盖了几个纹素,而D=log2LD=log_2L则是表示在第D层Mipmap中,当前像素可以刚好覆盖一个纹素。这就是MapMip精妙之处,即使用预处理的纹理代替范围查询过程中的平均值计算,用空间换时间。

Trilinear Interpolation-三线性插值

Mipmap中的层级是不连续的,有可能计算出的层级D不是整数。
此时,就需要进行插值。对相邻两层对应点周围的四个纹素进行双线性插值,再对两层结果进行线性插值得到最终结果,这就是三线性插值。
TrilinearInterp

Anisotropic Filtering-各向异性过滤

MipmapProblem
Mipmap结果可能会出现模糊的情况,因为Mipmap是水平与竖直方向同时压缩,即各向同性,然而屏幕空间中的正方形对应纹理空间并非总是一个正方形,如下图所示。
ScreenToTexture
各向异性过滤则增加了对纹理在水平或竖直方向单独进行压缩的结果,可以更好地适应上图中的情况。
AnisotropicFiltering
各向异性过滤也被称为Ripmap,相比原始纹理多占用了3倍内存。
注:游戏中常见的各向异性过滤2X,4X,8X…,实际上就是压缩的层数不同,与Mipmap类似,这也是一种用空间换时间的方法,因此它对显卡性能的要求并未大幅提高,只要显存足够,各向异性过滤开多高对性能并无太大影响。

还有许多其他的纹理过滤方法,如EWA filtering,可以更好适应不规则形状,但是开销随之增加许多。

Applications of Textures-纹理的应用

在现代GPU中,纹理可以理解成一块内存,我们可以对这块内存进行范围查询、过滤等。
texture=memory+range query(filtering)

Environment Map-环境贴图

纹理可以用来描述环境光。
EnvironmentMap
EnvironmentalLighting

Spherical Environment Map
环境光可以记录在球面上,再将其展开,但是这会产生扭曲,就像地球仪展开成地图会导致比例变化。
SphericalEnvironmentMap

Cube Map
CubeMap
将Spherical Environment Map上的环境光信息向外投射至包围球体的正方体表面就变成了Cube Map,这样展开后的扭曲现象明显减少。

Bump/Normal Mapping-凹凸/法线贴图

纹理除了表示颜色,也可以定义其对于其基础表面沿法线上下的相对高度。高度的变化使得法线变化,从而着色的结果也产生变化。使用凹凸贴图就可以在不改变几何的情况下改变着色结果实现凹凸表面的效果,也就是用假的法线渲染出假的结果欺骗人的眼睛。
BumpMapping

具体来说,就是根据凹凸贴图定义的高度变化,计算每个像素的法线的变化。
BumpTexture

二维下,

  • 设原始法线n(p)=(0,1)n(p)=(0,1)
  • 用差分方法求该点近似的切线(1,dp)(1,\mathrm{d}p)。其中dp=c[h(p+1)h(p)]\mathrm{d}p=c*[h(p+1)-h(p)]
  • 则该点的法线n(p)=(dp,1)n(p)=\bm{(-\mathrm{d}p,1)}.normalized()
    BumpNormal

推广至三维,

  • 设原始法线n(p)=(0,0,1)n(p)=(0,0,1)
  • 用差分方法求该点近似的切线,三维中有两个方向的切线(1,0,dp/du),(0,1,dp/dv)(1,0,\mathrm{d}p/\mathrm{d}u),(0,1,\mathrm{d}p/\mathrm{d}v)
    • dp/du=c1[h(u+1)h(u)]\mathrm{d}p/\mathrm{d}u=c1*[h(u+1)-h(u)]
    • dp/dv=c2[h(v+1)h(v)]\mathrm{d}p/\mathrm{d}v=c2*[h(v+1)-h(v)]
  • 则该点的法线为两个切线的叉乘,n(p)=(dp/du,dp/dv,1)n(p)=\bm{(-\mathrm{d}p/\mathrm{d}u,-\mathrm{d}p/\mathrm{d}v,1)}.normalized()

:实际中法线可能朝各个不同方向,为便于计算,对于每一个法线定义局部坐标系,设法线n(p)=(0,0,1)n(p)=(0,0,1),计算完成后再将其变换回世界坐标系。

Displacement mapping-位移贴图

与凹凸贴图相同,位移贴图也使用纹理定义相对高度,但是位移贴图会实际移动顶点位置,改变了几何信息,而凹凸贴图并没有实际改变几何信息。也就是说凹凸贴图是做出一种“凹凸的感觉”以欺骗人的眼睛,而位移贴图真的做出了凹凸的几何效果。
如下图,凹凸贴图在边缘位置露馅了,可以明显看出其几何信息实际上并未改变,但是位移贴图的边缘也能看出凹凸效果。
BumpAndDisplacement
位移贴图要求模型三角形足够细致。三角形的间隔要小于纹理定义的各点位移间的间隔,用采样的原理解释,即模型必须足够细致才能完整地反映纹理定义的凹凸的信息。
但是,这样的开销比较大。DirectX使用了一种叫做动态曲面细分(Dynamic Tessellation)的方法,先使用较粗糙的模型,根据后续实际计算需要再细分出更多三角形。

Ambient Occlusion Texture Mapping-环境光遮蔽纹理贴图

AO

3D Texture and Volume Rendering-3D纹理与体渲染

3DTexture