纹理映射、三角形的插值:重心坐标(Barycentric Coordinates)、纹理相关问题、纹理太小了、纹理太大了、Mipmap的流程、纹理的应用

纹理映射

纹理就是一张图,将其贴在三维物体。
纹理映射(Texture Mapping),又称纹理贴图,是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程。简单来说,就是把一幅图像贴到三维物体的表面上来增强真实感。
在这里插入图片描述
任意三角形的顶点都能找到顶点在纹理上哪个位置。
在这里插入图片描述
在这里插入图片描述
不管长宽比和分辨率,U和V的范围都在0到1内。三角形的三个顶点,每个顶点都对应一个UV。
在这里插入图片描述
纹理能被多次使用。如果纹理重复上下左右可以无缝衔接,则称这个纹理是tiled textures。(有一种算法叫Wang tiling)
知道了三角形的三个顶点的UV坐标,如何得出三角形内部的UV坐标,引出三角形的插值。

三角形的插值:重心坐标(Barycentric Coordinates)

插值的作用

如果对三角形的三个顶点都有特定的值,可以得到三角形内任何一点的这个值,并且这些值从一个顶点到另一个顶点是平滑过渡的。
插值的对象?
纹理坐标,颜色,法线向量…
插值的方法?
重心坐标

重心坐标系

在这里插入图片描述
重心坐标是定义在三角形上的,在重心坐标系中,三角形平面的任何一个点(x,y)都可以表示成三角形三个顶点的线性组合(系数分别是α β γ ,且满足这三个系数相加和为1)。如果这个点在三角形内,则需要α β γ都是非负的。
例如:
A点的重心坐标( α , β , γ ) = ( 1 , 0 , 0 )且(x,y)=αA+βB+γC=A

获得三角形的重心

重心的性质:将重心与三个顶点相连,可以把三角形分割成等面积的三个三角形。下面是重心坐标系和普通坐标系下的三角形重心坐标。
在这里插入图片描述

获得三角形任意一点的重心坐标

可以通过面积比求重心坐标。α,β,γ的计算如下所示,其中A点对应A A(表示A点的面积),B点对于BB,C点对于CC
在这里插入图片描述
然后通过叉积求面积,得到重心坐标的一般的表达式。
在这里插入图片描述

使用重心坐标

  1. 先算出重心坐标的位置,确定α β γ
  2. 再利用重心坐标做插值
    在这里插入图片描述
    V可以是任何属性,只要根据位置把α β γ确定下来,就可以确定该位置对应某个属性的插值。
    注意:
    如果三角形是空间里的三角形,投影后三角形会发生变化,重心发生变化。即投影变换下,重心坐标是不能保持不变的。所以想要插值三维空间的属性,就要找三维的坐标,算出相应的重心坐标并做插值,不能在投影后的三角形做插值(比如深度)。

应用纹理

对于每一个光栅化的样本(x,y)的应用流程如下:

  1. 根据三角形顶点的纹理坐标获得该位置的(u,v)。
  2. 在纹理上查询这个(u,v)对应的颜色值
  3. 在该样本设置这个颜色。可以认为这个纹理定义漫反射系数Kd,如果使用Blinn-Phong,将纹理颜色替换kd,便可以将纹理贴上去,同时拥有Blinn-Phong的效果。
    在这里插入图片描述

纹理相关问题

纹理太小了

假设一张 480 * 270 尺寸的纹理要铺满渲染到分辨率为 1920 * 1080 的屏幕上,此时一个纹素的大小可以理解为是像素的4倍,多个像素点对应一个纹理坐标。
对于任何一个点找到纹理上的位置,如果纹理上的这个位置不是整数,采用四舍五入的办法变成整数。这样的话在一定范围内查找的是相同的纹理上的像素(texel 称为纹理元素、纹素 )。
在这里插入图片描述
纹理太小会出现一块一块的效果,没有平滑的过渡。

双线性插值 Bilinear interpolation

如图中红色点是屏幕空间下一像素所对应在纹理空间中的点,会去采样离他最近的那个纹理颜色点,这种近似采样方法是不可取的。
在这里插入图片描述
首先取出离红色点最近的4个黑色顶点,分别算出,该红色点在水平及竖直方向偏移的比率s,t,如下图所示。
在这里插入图片描述
在这里插入图片描述

水平方向插值得u0和u1,竖直方向插值得结果:

  1. 利用水平比例s,线性插值出u00和u10之间u0点的颜色值,以及u01和u11之间的u1点的颜色值。
  2. 最后利用竖直方向比例t,对u0和u1线性插值出红色点的颜色值。
    这样利用两次线性插值,考虑到了周围4个点的颜色值,得到了平滑过渡的颜色,能够很好的缓解走样失真现象,并且计算速度较快。

Bicubic 双向三次插值

在这里插入图片描述
除了双线性插值外,还可以使用双三次插值(Bicubic Interpolation),取的是周围16个像素而非4个像素(非线性),虽然效果好但是带来的弊端就是开销会很大。

纹理太大了

后果严重的走样

如果纹理过大会出现更加严重的走样,远处摩尔纹,近处锯齿
在这里插入图片描述

原因

由于近大远小的透视关系,对于一个像素来说,近处覆盖的纹理区域相对较小,许多个采样点对应了同一个纹理颜色;而远处覆盖的纹理区域则很大,用一个点的采样无法代表一大块纹理区域的颜色变化。
在这里插入图片描述
这种屏幕像素对纹理贴图的覆盖区域,被形象的称为屏幕像素在纹理空间的footprint
屏幕上的像素从近到远,一个像素覆盖纹理上的区域是由少到多的。
但是对于很远的点,一个像素的颜色值以一个中心点来采样是相当不准确的。

从采样来看纹理过大的影响

其实道理类似,纹理过大,每个纹素的大小相较于像素更小了,就导致每个像素覆盖的区域实际上会包含多个纹素,但在片元着色阶段只会进行一次点采样,得到的结果显然是不够“精密”的。从信号学的角度上来理解,每个像素的采样频率远远低于纹理上的变化频率,于是也会形成锯齿的效果。假设一张特别大的纹理贴在地面上,由于透视摄像机的近大远小原理,会放大像素与纹素的信号频率差距,因此远处的地面会形成一种叫做“摩尔纹”的效果。

解决的思路

对于锯齿的效果,上面说过可以采用双线性插值方法修复,通过插值平滑的过渡;对于摩尔纹,由于采样点不足,采样的速度跟不上信号变化的速度,可以采用超采样方式,增加采样点,例如几百倍的超采样,显然可以解决问题,但是付出的代价是极为高昂的计算成本,显然不可取。
解决方法:避免采样,不采样,快速获得一片区域的平均值,范围查询。

Mipmap
引入
  1. 超采样可以解决纹理过大的问题,但是它消耗过大,如果有一种算法可以立刻获得平均而不用进行太多计算就好了。
  2. 从近到远一个像素对应的采样区域大小是不同的,所以这个算法的范围查询应该可以查出任意不同范围的大小。
    在这里插入图片描述
作用

允许进行不同大小的范围查询。(快速的正方形的近似范围查询fast、approx.、square)

定义

Mipmap是一种典型的空间换时间的解决方案,通过「范围查询」来替代之前双线性差值所做的「点查询」,它的好处就是速度快,但是也有缺点:结果不够准确、只能用于正方形(2的幂次方)贴图,以及增加了额外的存储量(增量约为1/3)。
Mip意思是很多不同的小东西。
对于一张贴图,可以不断将分辨率缩小一半。
在这里插入图片描述
在这里插入图片描述
然后只需要找出每个像素在对应层对应位置的映射就可以了。

Mipmap的流程
  1. 获得自己和邻居的中心位置,计算像素覆盖纹理的面积。
    在这里插入图片描述
  2. 将这四个位置在纹理空间上进行映射。
    在这里插入图片描述
    其中,D是层数,只需要知道L就可以计算,代表这个像素应该在第D层Mipmap去寻找颜色。
    在这里插入图片描述
    假设一个三角形覆盖屏幕一定区域的像素,其中选择一红色的采样点以及其相邻的3个采样点(都是红色),映射到纹理坐标系下,求出两个像素采样点在纹理坐标系下的近似长度 L 以及一块粉红色的区域。根据这块区域的近似面积以及纹素的大小,就可以得出该使用哪个层的Mip了。
    比如:
    如果正好是一个纹素的大小,也就是一个像素对应一个纹素,那么就选择第0层的Mip
    如果对应四个纹素的大小,也就是一个像素对应4个纹素,那么就选择第1层的Mip
问题

如下图是Mipmap可视化,可以发现有渐变,但是渐变不连续。
当计算出的层数恰好不是整数的时候(事实上经常会出现这种情况),同一纹理上的不同Mip之间会出现明显的断层现象,如下图底部的红色区域,深红色直接过渡到鲜红色,十分地不自然。
在这里插入图片描述
出现了这种不自然的过渡,解决办法依然是老办法:「差值」 !

三线性插值 Trilinear Interpolation

三线性插值即两次查询,一次插值,找到D和D+1层的结果然后进行插值。
在这里插入图片描述
经过三线性插值后的Mipmap可视化
在这里插入图片描述

使用三线性差值计算出的Mipmap颜色依旧会有问题

出现了Overblur(远处模糊的过分了)
在这里插入图片描述
原因:只能对正方形进行近似,插值不是精确而是近似值。Mipmap只能针对正方形纹理进行Mip分层,当透视效果越明显,越远处的像素对应纹理中的区域会越不接近正方形(更像长条的矩形,甚至连矩形都不是),如果使用面积更大的正方形来包住这个区域,此时的结果就会愈加不准确,造成了越远处越模糊的感觉。

(部分)解决三线性插值问题:各向异性过滤(Anisotropic Filtering)

产生上面这种现象的原因是因为,所采用的不同level的Mipmap默认的都是正方形区域的采样,然而真实情况并不是如此,见下图:
在这里插入图片描述
可以看出不同屏幕空间的像素点所对应的footprint是不同的,有长方形,甚至是不规则图形,那么针对这种情况,有的需要水平方向的高level,有的需要竖直方向上的高level,因此也就出现了各向异性的
在这里插入图片描述
各向异性可以用不同的长宽比进行缩小,也就是可以用矩形区域做范围查询,而代价就是消耗更多的存储。
利用这样不同比例的Mipmap贴图,结果就会明显好很多:
在这里插入图片描述

覆盖不规则的形状 —— EWA filtering

对于不规则的形状可以拆成几个圆形去覆盖这个形状。每次查询一个圆形,然后多次查询来覆盖不规则的形状。
在这里插入图片描述

纹理的应用

纹理的理解

在现代的GPU中, texture = memory + range query,texture是一块数据可以做各种不同的查询。可以定义不同的属性,不限定为颜色,如高度、法线等向量信息

环境光照(贴图) Environment Map

如下茶壶会被环境光照亮,即它可以反射出任何方向来的光。如下图就反射出了窗户和门
在这里插入图片描述
可以把环境光记录到球上,并且展开。
在这里插入图片描述
在这里插入图片描述
展开后出现了扭曲问题(向中间扭曲)
解决办法 —— Cube Map
即不把光照信息存在球的表面上,而是立方体的表面。
在这里插入图片描述
因为立方体各个面是均匀的,所以不会出现扭曲。
在这里插入图片描述
存在的问题:相对于球来说,需要多记录一个方向。
本质:记录不同方向的光照信息。

纹理可以影响着色

纹理可以定义任何不同位置任意不同的属性,比如定义任何一个点的相对高度。
如下图就是一种凹凸贴图(定义法线的不同),这样就可以不用定义复杂的几何形体。
在这里插入图片描述

Bump Mapping 凹凸贴图

Normal Mapping法线贴图不等于Bump Mapping凹凸贴图,法线贴图存储的是法线,凹凸贴图存储的是高度差。

凹凸贴图的作用

凹凸贴图可以定义复杂的纹理,但是不改变任何几何信息(三角形数量不变)。

  • 把任何一个像素的法线做扰动
  • 纹理定义任何一个点相对高度的移动
  • 然后可以通过高度的变化改变法线

在这里插入图片描述

如何扰动法线(in flatland)

在这里插入图片描述

如何扰动法线(in 3D)

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
纹理映射(Texture Mapping)是计算机图形学中的一个重要概念,用于将纹理图像映射到三维模型表面上,从而增强模型的真实感和细节。以下是一个简单的代码示例,说明了纹理映射的过程: ```python # 导入所需库 import cv2 import numpy as np # 加载模型和纹理图像 obj = cv2.imread('model.obj') texture = cv2.imread('texture.jpg') # 定义模型表面的三角形顶点和纹理坐标 vertices = [[0, 0, 0], [0, 1, 0], [1, 0, 0]] tex_coords = [[0, 0], [0, 1], [1, 0]] # 定义相机位置和投影矩阵 camera_pos = [0, 0, 1] projection_matrix = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) # 遍历模型表面的所有三角形,计算纹理映射后的颜色值 for i in range(len(obj) - 2): # 获取当前三角形的顶点和纹理坐标 v1, v2, v3 = vertices[obj[i][0] - 1], vertices[obj[i][1] - 1], vertices[obj[i][2] - 1] t1, t2, t3 = tex_coords[obj[i][3] - 1], tex_coords[obj[i][4] - 1], tex_coords[obj[i][5] - 1] # 计算三角形的法向量和相机到三角形的距离 normal = np.cross(v2 - v1, v3 - v1) distance = np.dot(normal, v1 - camera_pos) # 如果三角形背面朝向相机,则跳过该三角形 if distance > 0: continue # 计算三角形在相机坐标系下的顶点和纹理坐标 v1_cam = np.dot(projection_matrix, v1 - camera_pos) v2_cam = np.dot(projection_matrix, v2 - camera_pos) v3_cam = np.dot(projection_matrix, v3 - camera_pos) t1_cam = np.dot(projection_matrix, np.array([t1[0], t1[1], 1])) t2_cam = np.dot(projection_matrix, np.array([t2[0], t2[1], 1])) t3_cam = np.dot(projection_matrix, np.array([t3[0], t3[1], 1])) # 根据透视投影的原理,将相机坐标系下的顶点和纹理坐标归一化到屏幕坐标系下 v1_screen = np.array([v1_cam[0] / v1_cam[2], v1_cam[1] / v1_cam[2]]) v2_screen = np.array([v2_cam[0] / v2_cam[2], v2_cam[1] / v2_cam[2]]) v3_screen = np.array([v3_cam[0] / v3_cam[2], v3_cam[1] / v3_cam[2]]) t1_screen = np.array([t1_cam[0] / t1_cam[2], t1_cam[1] / t1_cam[2]]) t2_screen = np.array([t2_cam[0] / t2_cam[2], t2_cam[1] / t2_cam[2]]) t3_screen = np.array([t3_cam[0] / t3_cam[2], t3_cam[1] / t3_cam[2]]) # 将屏幕坐标系下的顶点和纹理坐标转换成像素坐标系下的整数值 v1_pixel = np.array([int(v1_screen[0] * texture.shape[1]), int(v1_screen[1] * texture.shape[0])]) v2_pixel = np.array([int(v2_screen[0] * texture.shape[1]), int(v2_screen[1] * texture.shape[0])]) v3_pixel = np.array([int(v3_screen[0] * texture.shape[1]), int(v3_screen[1] * texture.shape[0])]) t1_pixel = np.array([int(t1_screen[0] * texture.shape[1]), int(t1_screen[1] * texture.shape[0])]) t2_pixel = np.array([int(t2_screen[0] * texture.shape[1]), int(t2_screen[1] * texture.shape[0])]) t3_pixel = np.array([int(t3_screen[0] * texture.shape[1]), int(t3_screen[1] * texture.shape[0])]) # 根据三角形的顶点和纹理坐标,利用双线性插值计算每个像素的纹理颜色值 for x in range(min(v1_pixel[0], v2_pixel[0], v3_pixel[0]), max(v1_pixel[0], v2_pixel[0], v3_pixel[0])): for y in range(min(v1_pixel[1], v2_pixel[1], v3_pixel[1]), max(v1_pixel[1], v2_pixel[1], v3_pixel[1])): if x < 0 or x >= texture.shape[1] or y < 0 or y >= texture.shape[0]: continue if point_in_triangle(np.array([x, y]), v1_pixel, v2_pixel, v3_pixel): weight1 = barycentric(np.array([x, y]), v2_pixel, v3_pixel, v1_pixel) weight2 = barycentric(np.array([x, y]), v1_pixel, v3_pixel, v2_pixel) weight3 = barycentric(np.array([x, y]), v1_pixel, v2_pixel, v3_pixel) tex_color1 = texture[t1_pixel[1], t1_pixel[0]] tex_color2 = texture[t2_pixel[1], t2_pixel[0]] tex_color3 = texture[t3_pixel[1], t3_pixel[0]] color = weight1 * tex_color1 + weight2 * tex_color2 + weight3 * tex_color3 obj[y, x] = color ``` 上述代码中,`vertices` 和 `tex_coords` 分别表示模型表面的三角形顶点和纹理坐标。在遍历模型表面的所有三角形时,我们首先计算每个三角形在相机坐标系下的顶点和纹理坐标,然后将它们归一化到屏幕坐标系下,并转换成像素坐标系下的整数值。接下来,我们根据双线性插值的原理,在三角形内部的每个像素位置计算出纹理颜色值,并将其赋值给模型表面的对应像素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值