Lecture 09 Shading 3 (Texture Mapping )
Different Colors at Different Places?
假设有两个光源照到一个球上,这个球上面有不同的颜色,也就是说kd的值不同,我们希望能用一种方式,定义这个球上每一个顶点的属性。因此引入纹理映射。
Surfaces are 2D
Surface lives in 3D world space
Every 3D surface point also has a place where it goes in the 2D image (texture)
任何一个三维物体的表面其实都是二维的,如上图对地球仪的拆解即可以解释这个现象。
通过对二维纹理的拉伸、变换等一系列操作,将其能蒙到三维物体上,我们称这个过程即为纹理映射。纹理映射使物体表面任何一个点和纹理上一个点产生一一对应关系。
Texture Applied to Surface
Visualization of Texture Coordinates
Each triangle vertex is assigned a texture coordinate (u,v)
我们通常用(u,v)坐标来表示纹理上任意一个点,一般u和v的范围都从0到1。
Texture Applied to Surface
三角形三个顶点,每个顶点都对应一个uv。
Textures applied to surfaces
纹理可以重复,也可以做到无缝衔接
Textures can be used multiple times!
(一)、Interpolation Across Triangles: Barycentric Coordinates
1、Interpolation Across Triangles
Why do we want to interpolate?
- Specify values at vertices
- Obtain smoothly varying values across triangles
What do we want to interpolate?
- Texture coordinates, colors, normal vectors, …
How do we interpolate?
- Barycentric coordinates
2、Barycentric Coordinates
A coordinate system for triangles(α,β,γ)
Inside the triangle if all three coordinates are non-negative
三角形所在平面上任意一个点(x,y)都可以用三个顶点A、B、C坐标的线性组合来表示,线性组合的系数α、β、γ之和等于1。如果系数都为非负数,则表示这个点在三角形内部。
Geometric viewpoint — proportional areas
如果知道被某点(x,y)与三个顶点连接将这个三角形划分成三个小三角形各自的面积,那么也可以求出系数α、β、γ。
What’s the barycentric coordinate of the centroid?
这里找一个特殊点:三角形的重心(质心)。三角形重心的重心坐标是(1/3,1/3,1/3)。
Barycentric Coordinates: Formulas
3、Using Barycentric Coordinates
Linearly interpolate values at vertices
当一个点的重心坐标(α,β,γ)求出来以后,就可以将任意属性V在此点的插值求出来,此时只需要将三个顶点的该属性和系数做乘法再相加,即可求出在该点插值出来的属性。
但是要注意,这时候三角形已经投影到屏幕上了,尤其对于在三维空间中的属性(比如深度信息),应该找到像素中心点对应三角形的位置的三维空间坐标,然后在三维空间中将A、B、C的深度(属性)插值好,再放回来。这个过程需要做一次逆变换就可以了。
4、Applying Textures
屏幕上任何一个采样点(像素中心)的位置,我们可以求出在这个位置上插值出来的uv(纹理坐标)的位置。
原来纹理坐标定义在三角形的顶点上,现在对于任何一个三角形内的点,我们都知道其在三角形中的位置,然后用重心坐标做一个插值,可以算出这一个点的uv。算出来这个点的uv以后,在纹理上查询一下这个uv的值,我们就知道对应纹理的颜色了,然后就可以拿来用了(比如用作漫反射的系数kd)。
(二)、Texture Magnification(What if the texture is too small?——Bilinear Interpolation)
1、Texture Magnification - Easy Case
Generally don’t want this — insufficient texture resolution
A pixel on a texture — a texel(纹理元素、纹素)
在高分辨率下(比如4k),如果纹理的分辨率比较低(比如256×256),那么在任意一个点上去查纹理,可能会查到一些非整数的值。纹理分辨率小了,就需要将其拉大。第一种方法是取就近的像素,四舍五入,比如0.4就认为是0,0.6就认为是1,如上图左侧Nearest。但是这个样子很难看。
当查询纹理的时候,如果给了一个非整数的坐标,如何得到它的值?在这里引入双线性插值的概念。
2、Bilinear Interpolation
如上图,红点并没有落在像素中心(非整数的值),我们想得到此时红点处的值
①.Take 4 nearest sample locations, with texture values as labeled
首先,找到这个红点临近的四个像素点的中心。
②.And fractional offsets, (s,t) as shown
这四个点总有一个左下角,可以找到这个红点离左下角的水平距离(s)和垂直距离(t)。s和t的值肯定都是在0到1之间的,因为两个像素之间的距离是1。
③、Two helper lerps(horizontal)
Linear interpolation (1D)
lerp(x, v0, v1) = v0+ x(v1-v0)
首先我们定义一个操作:线性插值。如上图公式。定义v0是0,v1是1,那么x就是位于0到1上的值。当x=0时,lerp的值为v0=0,x=1时,lerp的值为v1=1,x=0.5时,lerp的值为0.5
Two helper lerps(horizontal)
u0 = lerp(s, u00, u10)
u1 = lerp(s, u01, u11)
左下角和右下角可以用s做一次线性插值: 左下角u00有一个颜色,右下角u10有一个颜色,又知道u0离左边的距离是s,所以可以用s对这两个点进行一次线性插值
左上角和右上角也可以用s做一次线性插值: 左上角u01有一个颜色,右上角u11有一个颜色,又知道u1离左边的距离是s,所以可以用s对这两个点进行一次线性插值
④、Final vertical lerp, to get result:
f(x, y) = lerp(t, u0, u1)
上一步已经通过插值得到了 u0和u1,但是最后我们想得到的是红点的值。那么我们再做一次竖直方向的插值即可。知道u0和u1,又知道红点离u0的距离t,所以可以用t对u0和u1做一次线性插值,即可得到红点的值。
整个过程做了两种插值:第一种插值是水平方向的点的插值,做了两个点(一对点),第二种插值是竖直方向的点的插值,做了一个点。这时红点的颜色就综合了u00, u10,u01, u11四个点,得到了一种平滑过渡的颜色。这个进行了两种插值的方法,就是双线性插值。(这里水平和竖直的先后顺序可以反过来)。
第三种方法是Bicubic,如第一幅图的最右侧效果。这种操作取的是周围16个像素而非4个像素(非线性),虽然效果好但是同时带来的弊端就是开销会很大。所以:
Bilinear interpolation usually gives pretty good results at reasonable costs.
(三)Texture Magnification (hard case) (What if the texture is too large?)
1、Point Sampling Textures — Problem
当一张uv纹理的分辨率过大,大于屏幕像素的时候,就会出现这样的情况,近处锯齿远处摩尔纹。
Screen Pixel “Footprint” in Texture
原因是近处一个像素覆盖的纹理区域相对较小,远处一个像素覆盖了很大一片纹理。屏幕上各个像素覆盖的纹理区域各不相同。如果一个像素覆盖的纹理区域较小,用这个像素的中心查询一下纹理的值,没有什么问题。但是一旦一个像素覆盖了很大一片区域的纹理(比如远处接近地平线的纹理),现在我还用一个像素的中心去查这块儿区域,用一个点的值去认为是这一个区域的值,那显然是不对的。用一个点的值无法代表一块纹理区域的颜色变化。
Will Supersampling Do Antialiasing?
虽然说这种问题的解决我们可以用超采样去做,但是开销极大。
Antialiasing — Supersampling?
Will supersampling work?
- Yes, high quality, but costly
- When highly minified, many texels in pixel footprint
- Signal frequency too large in a pixel
- Need even higher sampling frequency
Let’s understand this problem in another way
- What if we don’t sample?
- Just need to get the average value within a range
Point Query vs. (Avg.) Range Query
点查询:以纹理为例,给你一个点,它的值是多少,可以用双线性插值来解决,这是点查询。
范围查询:给你任何一个区域,你立刻可以得到这个范围内的平均值,这是范围查询。(范围查询还有查询一个范围内最大值和最小值,这类问题跟现在所说的完全不一样)
Different Pixels -> Different-Sized Footprints
以实际的渲染图为例,近处的圆圈(右下方)一个像素覆盖的纹理区域较小,远处的圆圈(中上方)一个像素覆盖的纹理区域非常大。假设用同一个纹理,不同像素有不同的在纹理上覆盖的大小,所以范围查询应该能查询任意范围的大小。
2、Mipmap(Allowing (fast, approx., square) range queries)
Mipmap的范围查询三个特点:快(查询速度非常快)、大约(是查询的近似值)、方形(查询区域仅可以是方形)
“Mip” comes from the Latin “multum in parvo", meaning a multitude in a small space
Mipmap其实就是从一张图生成一系列图。如上图,原始图是第0层,每升高一层,图像的分辨率都缩小一半。
What is the storage overhead of a mipmap?
如果原图存储开销是1,因为每一层图分辨率都缩小一半,所以每一层的存储量都是上一层的四分之一。计算下来,总存储量开销是4/3,额外开销仅仅是1/3.
Computing Mipmap Level D
Estimate texture footprint using texture coordinates of neighboring screen samples
任何一个像素都可以映射到纹理上的一个区域,想算一个像素的覆盖面积,可以让该像素中心和其相邻像素中心分别映射到纹理上去,然后做一个近似。我们知道相邻两个像素中心的距离是1,那么对应到右边纹理上占有多远的距离L也就可以求出,这时我们就可以求出一个像素映射到纹理上占据多大的面积。
我们可以用如上图所示的一个正方形框来近似这样一个不规则的区域。
当我们把一个像素在纹理上覆盖区域近似成一个正方形时,如何根据我刚刚已经算好的Mipmap,去查询这样一个边长为L正方形的区域的平均值是多少?
如果这个正方形区域就是1×1,那么就表明一个像素正好对应一个边长为L的正方形区域,也就可以直接在最原始(第0层,D=0)的纹理上找对应的像素,就是它的值。
如歌这个正方形区域是2×2,那么这个区域会在第1层(D=1)上对应一个像素
如果这个正方形区域是4×4,那么这个区域会在第2层(D=2)上对应一个像素
对于L×L大小的正方形,一定会在D=log2L层上对应到一个像素。
因此我们只需要算出D,即在第几层正方形的区域对应一个像素,就可以得出这个区域内平均值是多少。
Visualization of Mipmap Level
将这个分层的过程可视化,如上图所示。
但是会发现,两层之间有很明显的边界,那么在实际纹理映射的过程中可能会出现一些缝。
Trilinear Interpolation
在此处我们的解决方法是,先找D层,再找D+1层,这两层内部分别用双线性插值把对应的在这两层上的查询做出来,做出来之后把这两个双线性插值的值合到一起,然后在层与层之间再做一次插值。总共做了三步插值,在双线性插值上又加了一步插值,这就是三线性插值。这样我就可以在任意层,无论是整数层还是浮点数层(例如第1.8层,D=1.8)。
Visualization of Mipmap Level
可以看到,三线性插值处理后的层级可视化出来,就是一个很漂亮的连续渐变的层级了。
Mipmap Limitations
Overblur Why?
在这里我们可以看到,如果假设用高开销的超采样得到的结果是相对准确的,那么用Mipmap得到的图,很明显在远处过于模糊,远处的所有细节全部糊掉。出现这个问题的原因就在于Mipmap只能查询一个正方形方块的范围。
3、Anisotropic Filtering
各向异性:在不同的方向上表现各不相同(考虑方向性)
各向同性:各个方向上表现完全相同(如刚刚的正方形,水平竖直表现完全相同)
Anisotropic Filtering
Ripmaps and summed area tables
- Can look up axis-aligned rectangular zones
- Diagonal footprints still a problem
Mipmap操作的相当于是上图的对角线图片,每次长宽各缩小一半,而各向异性可以用不同的长宽比进行缩小(上图除了对角线以外的图片),也就是可以用矩形区域做范围查询(总共开销是原来的三倍)。反映到映射关系上即为下图。
Irregular Pixel Footprint in Texture
但是,对于一些斜着的图形,依然没有很好的方法去查询
EWA filtering
- Use multiple lookups
- Weighted average
- Mipmap hierarchy still helps
- Can handle irregular footprints
人们发明了另外一些方法,如EWA,对于任何一个形状,都可以拆成很多不同的圆形去覆盖这个形状。如上图查询一个椭圆,将其拆成三个圆形,每次去查询一个圆形,多次查询自然就可以得到一个区域,但是代价是“多次查询”。可见质量越高的效果,性能开销越大。
(四)、Applications of Textures
Many, Many Uses for Texturing
In modern GPUs, texture = memory + range query (filtering)
- General method to bring data to fragment calculations Many applications
- Environment lighting
- Store microgeometry
- Procedural textures
- Solid modeling
- Volume rendering
- …
1、Environment Map
环境贴图,制造环境光,描述来自不同方向的光照信息,照亮整个环境中的物体,也可以被环境中的物体反射。环境光只记录光的方向,即假设环境光来自无限远处,从同一个方向照过来的光强度都是一样的。
左侧的这个环境光球如何去看?可以假设拿了一个非常光滑的金属球(镜子球),这个球反射出来了周围的环境。
Spherical Environment Map
可以把整个环境光记录在球上,用球去存储环境光的信息。
Spherical Map — Problem
Prone to distortion (top and bottom parts)!
但是我们来看一个环境光球展开后的顶部和底部,可以发现出现了不同程度的变形。
Cube Map
A vector maps to cube point along that direction.
The cube is textured with 6 square texture maps.
用一个立方体盒子,把这个球包围起来。当光从中心打到包围的球上时,我们不管,让光继续走,直到碰到立方体的一个面,记录下来信息。
那么展开后的图像有六个面,就不存在刚刚球展开后产生的大面积扭曲了。
2、Textures can affect shading
Textures doesn’t have to only represent colors
- What if it stores the height / normal?
- Bump / normal mapping
- Fake the detailed geometry
贴图不光可以表示颜色信息,还可以表示高度信息。高度贴图可以定义任意一个点,沿着他的法线向上向下走的相对高度。如右图,原本就是一个很普通的球,可以用很少的三角形来表示,如果想通过直接改变模型的方法得到右边那种凹凸不平的效果,会用很多三角形,开销会很大,那么在这里通过凹凸(高度)贴图,改变高度信息,高度信息变化,就会使法线信息变化,从而使着色结果发生变化(人们看到的明暗对比一定程度上就是因为法线变化),得到右图的效果。
Bump Mapping
Adding surface detail without adding more triangles
- Perturb surface normal per pixel (for shading computations only)
- “Height shift” per texel defined by a texture
- How to modify normal vector?
如上图,假设黑色的线是原本一个比较光滑的面,在这里应用了一个凹凸贴图,凹凸贴图告诉我们一个相对高度如何变化,得到了黄色的线,那么原来点的位置会认为被凹凸贴图改变,那么法线也会改变。
凹凸贴图改变相对高度,从而改变法线。
How to perturb the normal (in flatland)
- Original surface normal n§ = (0, 1)
- Derivative at p is dp = c * [h(p+1) - h§]
- Perturbed normal is then n§ = (-dp, 1).normalized()
在这里我们将复杂问题简单化,首先来看在一维上如何计算。
我们认为,某点p原来的法线n为(0, 1) ,我们要求出改变后的法线,首先就要求出切线,设切向量为(x,y)
由于点p内存储着这一点的高度信息,因此会改变p点的高度,为h(p),利用微分的思想,我们再去找相邻像素点的位置,即p+1处,也有一个高度h(p+1),通过这两点的高度差,就可以算出切向量的y值,即为dp,x的值就是相邻两个像素的x,为1,因此切向量即为(1,dp),所以法向量就是(-dp,1),即法线为(-dp,1)。
How to perturb the normal (in 3D)
- Original surface normal n§ = (0, 0, 1)
- Derivatives at p are
--dp/du = c1 * [h(u+1) - h(u)]
--dp/dv = c2 * [h(v+1) - h(v)]
- Perturbed normal is n = (-dp/du, -dp/dv, 1).normalized()
- Note that this is in local coordinate
将刚刚一维上一条线的思维推广到二维上uv平面内的操作,我们即可得出上述的公式。
Displacement mapping — a more advanced approach
- Uses the same texture as in bumping mapping
- Actually moves the vertices
如上图可以看出,左侧通过法线贴图改变模型表面细节对于边界和阴影是有破绽的,而右侧的位移(置换)贴图,直接通过贴图改变模型各个三角形的顶点位置。但是位移贴图要求三角形数量足够多,否则精度会非常低。
3D Procedural Noise + Solid Modeling
还可以定义三维纹理,如果把这个球砍一半,可以看到其内部的纹理,这里实际定义了空间中任何一点的值,这种纹理实际没有真的生成纹理的图片,而是定义了一个在三维空间中的噪声函数,对于空间中任意一点都有一个解析式可以算出在该点的值。
Provide Precomputed Shading
纹理贴图还可以记录一些提前算好的信息,比如模型自己本身互相遮挡产生的阴影(环境光遮蔽)。
3D Textures and Volume Rendering
(五)、Shadow Mapping
-
An Image-space Algorithm
--no knowledge of scene’s geometry during shadow computation
--must deal with aliasing artifacts
-
Key idea:
--the points NOT in shadow must be seen both by the light and by the camera
Shadow Mapping最重要的一个思想是如果有个点不在阴影里,但又能被你看到,那么说明你可以从摄像机看到这个点,并且光源也可以看到这个点。
1、Step
Pass 1: Render from Light
- Depth image from light source
首先,从光源看向场景(可以假设光源有一个虚拟的摄像机看向场景)(如上图 ),绿色的圆圈就挡住了部分视线,然后得到一张光源处可以看到的图,这张图我们不需要着色,只需要把深度记录下来即可,得到一张从光源看向场景的深度图。
Pass 2A: Render from Eye
- Standard image (with depth) from eye
然后从摄像机(眼睛)再向场景看。
Pass 2B: Project to light
- Project visible points in eye view back to light source
(Reprojected) depths match for light and eye. VISIBLE
当从摄像机(眼睛)看到一个点时,我们可以将其投影回刚刚光源处虚拟相机成像平面上(点与光源连线,如上图橙色线),得到光源到这个点的深度信息。
这一步同时可以得到光源到这个点所在方向上的深度信息原来记录在虚拟相机生成的深度图上的像素位置,从而得到这个像素上原来从光源看向场景(Pass 1中)的深度信息。
比较二者的深度信息,如果是一致的,这说明这个点完全可以被光源所看到。
(Reprojected) depths from light and eye are not the same. BLOCKED!!
如果是这样一个点,同样我们可以投影回光源所在的虚拟相机所成像的图像上,我们找到那个像素,但是我们之前在那个像素上记录的深度是到绿色圆圈的,眼睛看向这个点投影回光源所在的虚拟相机的实际深度与从光源看向这个方向的最大深度不一致,这就说明这个点一定是之前从光源往这个方向看不到的点。因此这个点在阴影中。
2、Visualizing Shadow Mapping
①、The scene from the light’s point-of-view
The depth buffer from the light’s point-of-view
首先从光源看向场景,得到深度图
②、Comparing Dist(light, shading point) with shadow map
Green is where the distance(light, shading point) ≈ depth on the shadow map
Non-green is where shadows should be
然后从摄像机看向场景,投影回光源也得到一张深度图
两张深度图相同方向的信息比较,得到阴影区域 。
3、Problems with shadow maps
-
Hard shadows (point lights only)
-
Quality depends on shadow map resolution (general problem with image-based techniques)
-
Involves equality comparison of floating point depth values means issues of scale, bias, tolerance
这种方式会导致阴影的硬边缘、分辨率、数值精度等问题。
另一种方式叫软阴影
Hard shadows vs. soft shadows
效果如上图所示。软阴影会有过度,越靠近物体根部位置会越硬,离得越远就会越虚化(越软)。
其效果的原理其实是一种自然现象,软阴影的部分其实是物理中的半影。如果一个地方完全看不到光源,这个地方就被叫作本影,如果能部分看到光源就被称为半影。这里我们不能把光源简单看成一个点,而是有体积的。