Posts Computer Graphics Shading Texture
Post
Cancel

Computer Graphics Shading Texture

资料来源:https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html


上一篇描述了着色的整体大概流程,这篇最后描述下着色最后一个环节,材质贴图映射的细节

一张2D贴图纹理会映射到一个3D物体表面,3D物体表面的三维坐标是怎么映射到2D贴图的二维坐标的?

使用重心坐标(Barycentric Coordinates)

用三角形的三个顶点坐标进行内部坐标插值

1 \((x,y) = \alpha A + \beta B + \gamma C\)

\[\alpha + \beta + \gamma = 1\] \[\alpha、\beta、\gamma加起来的总和为1,它们的占比越大越靠近对应的顶点,以此可以插值得到三角形内部所有坐标\]

2

3

4

当然,重心坐标不仅可以插值坐标,还可以插值其他线性变化的属性,例如颜色、三角形法线、三角形面深度、材质其他属性

简单的贴图映射伪代码:

1
2
3
4
5
for each rasterized screen sample (x,y):  //Usually a pixel’s center
(u,v) = evaluate texture coordinate at (x,y)  //Using barycentric coordinates!
texcolor = texture.sample(u,v);
set sample’s color to texcolor;   //Usually the diffuse albedo Kd (recall the Blinn-Phong reflectance model)
Using barycentric coordinates!
1
2
3
4
5
6
7
8
9
10
11
12
static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector4f* v){
    float c1 = (x*(v[1].y() - v[2].y()) + (v[2].x() - v[1].x())*y + v[1].x()*v[2].y() - v[2].x()*v[1].y()) / (v[0].x()*(v[1].y() - v[2].y()) + (v[2].x() - v[1].x())*v[0].y() + v[1].x()*v[2].y() - v[2].x()*v[1].y());
    float c2 = (x*(v[2].y() - v[0].y()) + (v[0].x() - v[2].x())*y + v[2].x()*v[0].y() - v[0].x()*v[2].y()) / (v[1].x()*(v[2].y() - v[0].y()) + (v[0].x() - v[2].x())*v[1].y() + v[2].x()*v[0].y() - v[0].x()*v[2].y());
    float c3 = (x*(v[0].y() - v[1].y()) + (v[1].x() - v[0].x())*y + v[0].x()*v[1].y() - v[1].x()*v[0].y()) / (v[2].x()*(v[0].y() - v[1].y()) + (v[1].x() - v[0].x())*v[2].y() + v[0].x()*v[1].y() - v[1].x()*v[0].y());
    return {c1,c2,c3};
}

auto[alpha,beta,gamma] = computeBarycentric2D(pixel_pos_x,pixel_pos_y,v4f);
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;

接下来是关于处理纹理映射中出现的一些问题,纹理倍率

1.纹理分辨率太小,显示不清晰

2.纹理分辨率太大,显示出现锯齿、摩尔纹

定义一下在贴图上的一个像素称为

A pixel on a texture — a texel (纹理元素、纹素)

1.too small

贴图纹理的分辨率太小,没有足够的像素点对应屏幕显示的像素点

当屏幕上某一个像素要采样纹理的某一个像素,映射到某一个纹理元素位置内,但是纹理坐标是整数索引,就会出现多个像素索引同一个纹理像素,我们可以使用插值解决这个问题.

5

Bilinear Interpolation 双线性插值

取周围4个最近的像素点样本,纹理值如标示。

两个纹理像素之间的距离定义为1

红点是映射投影到纹理上的点

s为红点在横坐标上的分量,在0-1之间

t为红点在纵坐标上的分量,在0-1之间

lerp(x,v0,v1)插值单维度(横轴)的值后,求的u0、u1,是红点在上下两个横坐标之间的值

再在u0、u1两个坐标之间进行纵向插值,即可得到红点的值

6

7

2.too large

当纹理分辨率太大时超过屏幕分辨率,

因为显示近大远小的透视原因

近处物体较大,需要更多的屏幕像素覆盖物体来完全显示

所以,近处的像素映射到纹理上的覆盖范围较小

远处处物体较小,需要很少的像素覆盖物体即可完全显示

所以,远处的像素映射到纹理上的覆盖范围较大

一个屏幕像素内部就包含很大一块的纹理像素,这块纹理是一直在变化的,它的变化频率很高,可是只用一对一的一个屏幕像素采样一个纹理像素,这样屏幕的采样频率跟不上纹理的变化频率,就会出走样,我们应该用更多的纹理像素平均起来对应作为一个屏幕像素的采样,这样才不会出问题

但是不想用这么多采样点采样纹理怎么办?

Mipmap - 一个屏幕像素覆盖纹理很大一块区域像素,我们立刻就可以知道这块区域的平均值,不就可以不采样了嘛!预处理!

8


MipMap

允许做范围查询,快、不准确、方形区域

从一张高分辨纹理生成一系列成倍缩小的不同分辨率的纹理,提前计算生成

原始纹理为第0层,分辨率往下,层数增高

10

那么如何知道怎么查询第几层的,当我们从屏幕像素映射查询纹理像素时,计算得到对应到原始纹理像素的坐标点,然后再计算不同坐标点之间的距离,获得一个大概的矩形面积,纹理坐标上两个坐标之间的距离是一,假设原始纹理分辨率是4x4,如果映射到原始纹理上的近似的矩形面积是4x4,那么在第二层就是我们原始纹理放大了4倍的对应一个像素需要的纹理了

Level 0 4x4

Level 1 2x2

Level 2 1x1

总结: 屏幕像素 1x1 对应原始纹理像素 占4x4 就需要将原始纹理像素放大4倍对应一个屏幕像素,刚好是level 2 的 1x1

11

12

但是层与层之间没有过渡,level1和levle2之间没有1.0-2.0层级的过渡,就会出现明显的边界,因此还是需要用Trilinear Interpolation来再不用的层级纹理中进行插值来过渡

13

14

以上是MipMap解决的问题,但是MipMap也有不能解决的问题,因为MipMap只能查询正方形区域,当屏幕一个像素(正方形区域)显示的是斜着角度拉伸的直线,对应到纹理应该是一个长方形的区域,那MipMap就不能正确查询到对应区域了,因为MipMap只能查询正方形区域,而各向异性过滤能解决某个轴 (x或y) 变窄或者变长后查询纹理显示不正确的问题,通过将原始纹理在各个轴方向生成不同的压缩层级进行查询显示,但是斜着的长条形各项异性过滤也不能很好的解决

各向异性过滤会增加额外的1/3开销,

16

15

17

This post is licensed under CC BY 4.0 by the author.