## PBRT笔记(9)——贴图 转

o
osc_y8yehimr

#### 采样与抗锯齿

1. 必须计算贴图空间的采样率，以及获得贴图分辨率，之后就可以计算出屏幕空间的采样率，最后为了获得图元表面的采样率就必须对贴图进行采样，以获得贴图采样率。
2. 对于给定的贴图采样率，必须使用采样理论去引导计算贴图值，不能有高频的贴图。

#### 寻找合适的图片采样率

void SurfaceInteraction::ComputeDifferentials(
const RayDifferential &ray) const {
if (ray.hasDifferentials) {
//计算微分偏移光线与平面的交点px与py，之后计算微分
Float d = Dot(n, Vector3f(p.x, p.y, p.z));
Float tx =
-(Dot(n, Vector3f(ray.rxOrigin)) - d) / Dot(n, ray.rxDirection);
if (std::isinf(tx) || std::isnan(tx)) goto fail;
Point3f px = ray.rxOrigin + tx * ray.rxDirection;
Float ty =
-(Dot(n, Vector3f(ray.ryOrigin)) - d) / Dot(n, ray.ryDirection);
if (std::isinf(ty) || std::isnan(ty)) goto fail;
Point3f py = ray.ryOrigin + ty * ray.ryDirection;
dpdx = px - p;
dpdy = py - p;

int dim[2];
if (std::abs(n.x) > std::abs(n.y) && std::abs(n.x) > std::abs(n.z)) {
dim[0] = 1;
dim[1] = 2;
} else if (std::abs(n.y) > std::abs(n.z)) {
dim[0] = 0;
dim[1] = 2;
} else {
dim[0] = 0;
dim[1] = 1;
}

Float A[2][2] = {{dpdu[dim[0]], dpdv[dim[0]]},
{dpdu[dim[1]], dpdv[dim[1]]}};
Float Bx[2] = {px[dim[0]] - p[dim[0]], px[dim[1]] - p[dim[1]]};
Float By[2] = {py[dim[0]] - p[dim[0]], py[dim[1]] - p[dim[1]]};
if (!SolveLinearSystem2x2(A, Bx, &dudx, &dvdx)) dudx = dvdx = 0;
if (!SolveLinearSystem2x2(A, By, &dudy, &dvdy)) dudy = dvdy = 0;
} else {
fail:
dudx = dvdx = 0;
dudy = dvdy = 0;
dpdx = dpdy = Vector3f(0, 0, 0);
}
}


#### 反射与透射的光线微分

$\omega=\omega_i+\frac{\partial_{\omega_i}}{\partial_x}$

$\frac{\partial_{\omega_i}}{\partial_x}=\frac{\partial}{\partial_x}(-\omega_o+2(\omega_o \cdot n)n)=-\frac{\partial_{\omega_o}}{\partial_x}+2((\omega_o \cdot n)\frac{\partial_n}{\partial_x}+\frac{\partial_{(\omega_o \cdot n)}}{\partial_x}n)$

//计算反射微分光线的方向
RayDifferential rd = isect.SpawnRay(wi);
if (ray.hasDifferentials) {
rd.hasDifferentials = true;
rd.rxOrigin = isect.p + isect.dpdx;
rd.ryOrigin = isect.p + isect.dpdy;
//计算x、y分量上的方向
Normal3f dndx = isect.shading.dndu * isect.dudx +
Normal3f dndy = isect.shading.dndu * isect.dudy +
Vector3f dwodx = -ray.rxDirection - wo,
dwody = -ray.ryDirection - wo;
Float dDNdx = Dot(dwodx, ns) + Dot(wo, dndx);
Float dDNdy = Dot(dwody, ns) + Dot(wo, dndy);
rd.rxDirection =wi - dwodx + 2.f * Vector3f(Dot(wo, ns) * dndx + dDNdx * ns);
rd.ryDirection =wi - dwody + 2.f * Vector3f(Dot(wo, ns) * dndy + dDNdy * ns);
}
return f * Li(rd, scene, sampler, arena, depth + 1) * AbsDot(wi, ns) /pdf;


#### 贴图类与接口

1. 基类为Texture，唯一接口Evaluate(),根据图元表面位置获取对应贴图坐标的值。
2. 子类ConstantTexture，位于constant.h中。返回相同值，因为这个特性，所以他不需要抗锯齿。
3. 子类ScaleTexture，位于scale.h中。返回两个贴图值的乘机。
4. 子类MixTexture，位于mix.h中。使用一个alpha贴图值，返回两个贴图值的线性插值。
5. 子类BilerpTexture，位于bilerp.h中，设定四个贴图坐标，返回以此计算的双线性插值。
##### ImageTexture

###### 贴图内存管理

PBRT维护了一个map用来管理贴图资源，避免资源被多次加载的情况。

    TexInfo texInfo(filename, doTrilinear, maxAniso, wrap, scale, gamma);
if (textures.find(texInfo) != textures.end())
return textures[texInfo].get();
/*
中间略
*/
/*
中间略
*/
MIPMap<Tmemory> *mipmap = nullptr;
if (texels) {
std::unique_ptr<Tmemory[]> convertedTexels(
new Tmemory[resolution.x * resolution.y]);
for (int i = 0; i < resolution.x * resolution.y; ++i)
convertIn(texels[i], &convertedTexels[i], scale, gamma);
mipmap = new MIPMap<Tmemory>(resolution, convertedTexels.get(),
doTrilinear, maxAniso, wrap);
} else {
// Create one-valued _MIPMap_
Tmemory oneVal = scale;
mipmap = new MIPMap<Tmemory>(Point2i(1, 1), &oneVal);
}
textures[texInfo].reset(mipmap);
return mipmap;


convertIn函数中还涉及到了缩放以及gamma矫正，以便将像素值映射到指定范围。pbrt遵循sRGB标准，该标准规定了一条特定的曲线来匹配CRT显示器的显示。sRGB gamma曲线是一个分段函数，其低值为线性项，其大中型值为幂项。 $\gamma= \left{ \begin{array}{cc} 12.92x, & x\leq 0.0031308\ 0, & x >0.0031308 \end{array} \right.$

inline Float GammaCorrect(Float value) {
if (value <= 0.0031308f) return 12.92f * value;
return 1.055f * std::pow(value, (Float)(1.f / 2.4f)) - 0.055f;
}


inline Float InverseGammaCorrect(Float value) {
if (value <= 0.04045f) return value * 1.f / 12.92f;
return std::pow((value + 0.055f) * 1.f / 1.055f, (Float)2.4f);
}


#### mipmap

PBRT使用了两种方法实现mipmap,第一种是三线性插值法，快速而且容易实现，在早期显卡中广泛使用。第二种是椭圆加权平均算法，速度慢且复杂，但是效果好。

ImageWrap枚举的作用是：当提供的贴图坐标不在合法的[0,1]范围中时，传递给MIPMap构造函数的指定行为。

MipMap使用一个可分离的重构过滤器来完成这个任务。可分离过滤器可以写成以为1维过滤器的乘积f(x,y)=f(x)f(y)。实现重采样可以分为两个一维重采样：第一步：重采样s完成（s',t）分辨率的图片,第二步重采样t完成（s',t'）分辨率 图片。(s,t)=>(s',t') 这样可以大大减少计算复杂度。

resampleWeights()方法确定所有原始像素对新的像素的贡献值权重值。它返回一个ResampleWeight结构体数组。这里的重构器会计算4个原始像素的贡献权重，因为4个像素紧挨在一起，所以只需要一个偏移值和一个权重数组。（以上内容都在构造函数中）

struct ResampleWeight {
int firstTexel;
Float weight[4];
};
std::unique_ptr<ResampleWeight[]> resampleWeights(int oldRes, int newRes) {
CHECK_GE(newRes, oldRes);
std::unique_ptr<ResampleWeight[]> wt(new ResampleWeight[newRes]);
Float filterwidth = 2.f;
for (int i = 0; i < newRes; ++i) {

Float center = (i + .5f) * oldRes / newRes;
wt[i].firstTexel = std::floor((center - filterwidth) + 0.5f);
for (int j = 0; j < 4; ++j) {
Float pos = wt[i].firstTexel + j + .5f;
wt[i].weight[j] = Lanczos((pos - center) / filterwidth);
}
//规整化操作保证了图像亮度统一
Float invSumWts = 1 / (wt[i].weight[0] + wt[i].weight[1] +
wt[i].weight[2] + wt[i].weight[3]);
for (int j = 0; j < 4; ++j) wt[i].weight[j] *= invSumWts;
}
return wt;
}


//存储了mipmap的图像金字塔结构
std::vector<std::unique_ptr<BlockedArray<T>>> pyramid;


const T &MIPMap<T>::Texel(int level, int s, int t) const {
CHECK_LT(level, pyramid.size());
const BlockedArray<T> &l = *pyramid[level];
switch (wrapMode) {
case ImageWrap::Repeat:
s = Mod(s, l.uSize());
t = Mod(t, l.vSize());
break;
case ImageWrap::Clamp:
s = Clamp(s, 0, l.uSize() - 1);
t = Clamp(t, 0, l.vSize() - 1);
break;
case ImageWrap::Black: {
static const T black = 0.f;
if (s < 0 || s >= (int)l.uSize() || t < 0 || t >= (int)l.vSize())
return black;
break;
}
}
return l(s, t);
}


##### 各项同性三角形过滤器

//那么可以求出l为：
Float level = Levels() - 1 + Log2(std::max(width, (Float)1e-8));

if (level < 0)
return triangle(0, st);
else if (level >= Levels() - 1)
return Texel(Levels() - 1, 0, 0);
else {
//通过插值计算来实现不同mipmap级别过度效果
int iLevel = std::floor(level);
Float delta = level - iLevel;
return Lerp(delta, triangle(iLevel, st), triangle(iLevel + 1, st));
}


MIPMap::triangle(int level, const Point2f &st) const {
level = Clamp(level, 0, Levels() - 1);
Float s = st[0] * pyramid[level]->uSize() - 0.5f;
Float t = st[1] * pyramid[level]->vSize() - 0.5f;
int s0 = std::floor(s), t0 = std::floor(t);
Float ds = s - s0, dt = t - t0;
return (1 - ds) * (1 - dt) * Texel(level, s0, t0) +
(1 - ds) * dt * Texel(level, s0, t0 + 1) +
ds * (1 - dt) * Texel(level, s0 + 1, t0) +
ds * dt * Texel(level, s0 + 1, t0 + 1);
}

o

### osc_y8yehimr

“咚咚”，一阵急促的敲门声， 我从睡梦中惊醒，我靠，这才几点，谁这么早， 开门一看，原来我的小表弟放暑假了，来南京玩，顺便说跟我后面学习一个网站是怎么做出来的。 于是有了下面的一段...

2014/05/31
976
3
Nutch学习笔记4-Nutch 1.7 的 索引篇 ElasticSearch

2014/06/26
712
0

2013/11/09
6.6K
8

GloboNote 是一个桌面记事软件，可帮你创建待办事宜、提醒和其他笔记信息。无限制即时贴的数量，可分组整理，支持搜索，可定制文本的显示格式（字体、颜色和大小），可将某个即时贴始终显示在...

2013/01/21
6.7K
1

Castle Game Engine 是一个用 Object Pascal 开发的跨平台 3D 游戏引擎。包含一个灵活的 3D 对象系统与开箱即用的水平,项目,智能生物等等。使用 X3D、VRML、Collada 和其他格式实现渲染和处理...

2013/02/05
2K
0

matplotlib基础绘图命令之imshow

0
0
[Bazel]自定义工具链

1 前言 2 Non-Platform 方式 3 Platform 方式 3.1 平台 3.2 工具链 3.3 Platform + Toolchain 实现平台方式构建 4 小结 1 前言 本文会讲述 Bazel 自定义工具链的两种方式，Platform 和 Non-...

0
0

lintao111

0
0

0
0

0 1 公司简介 字节跳动AI Lab，成立于2016年，致力于开发为字节跳动内容平台服务的创新技术，不仅仅是进行理论研究，我们的想法还可以通过实验证明和快速跟踪用于产品部署。 人工智能涉及的研...

0
0