SPD 光谱功率分布

CoefficientSpectrum

根据给定采样数表示光谱,为RGBSpectrum、SampledSpectrum的父类。

重载大量的基础代码,比较简单不做赘述。其中为了方便访问对应区域的SPD,而重载了[]操作符。(TabulatedBSSRDF等会用到)

该类只要以各种基础运算函数、重载各种操作符为主,以及一个 Float c[nSpectrumSamples];用于保存SPD信息。

SampledSpectrum

SampledSpectrum则将光谱表达为波长范围上的采样点集合。(通过采样求得光谱系数)

人眼对400~700纳米波长的光最敏感,通常采样量为30即可准确地表示渲染时的SPD

  1. static SampledSpectrum FromSampled(const Float *lambda, const Float *v,int n) {
  2. //如果处于无序状态,则在排序后在返回结果
  3. spectrum
  4. if (!SpectrumSamplesSorted(lambda, v, n)) {
  5. std::vector<Float> slambda(&lambda[0], &lambda[n]);
  6. std::vector<Float> sv(&v[0], &v[n]);
  7. //SortSpectrumSamples:讲sLambda与sv放入一个map中,使用sort进行排序后在放回对应的Vector
  8. //这里出现了一个骚操作,因为Vector存储的数据是紧挨着存储的和数组一样,所以可以直接用float*取地址进行操作
  9. SortSpectrumSamples(&slambda[0], &sv[0], n);
  10. return FromSampled(&slambda[0], &sv[0], n);
  11. }
  12. SampledSpectrum r;
  13. for (int i = 0; i < nSpectralSamples; ++i) {
  14. //计算该采样区域的平均值
  15. Float lambda0 = Lerp(Float(i) / Float(nSpectralSamples),
  16. sampledLambdaStart, sampledLambdaEnd);
  17. Float lambda1 = Lerp(Float(i + 1) / Float(nSpectralSamples),
  18. sampledLambdaStart, sampledLambdaEnd);
  19. r.c[i] = AverageSpectrumSamples(lambda, v, n, lambda0, lambda1);
  20. }
  21. return r;
  22. }
  1. Float AverageSpectrumSamples(const Float *lambda, const Float *vals, int n,
  2. Float lambdaStart, Float lambdaEnd) {
  3. for (int i = 0; i < n - 1; ++i) CHECK_GT(lambda[i + 1], lambda[i]);
  4. CHECK_LT(lambdaStart, lambdaEnd);
  5. //处理越界以及单一采样的情况
  6. if (lambdaEnd <= lambda[0]) return vals[0];
  7. if (lambdaStart >= lambda[n - 1]) return vals[n - 1];
  8. if (n == 1) return vals[0];
  9. Float sum = 0;
  10. //如果头尾采样都在区间内,则将其加入结果中
  11. if (lambdaStart < lambda[0]) sum += vals[0] * (lambda[0] - lambdaStart);
  12. if (lambdaEnd > lambda[n - 1])
  13. sum += vals[n - 1] * (lambdaEnd - lambda[n - 1]);
  14. //移动到对应的区间
  15. int i = 0;
  16. while (lambdaStart > lambda[i + 1]) ++i;
  17. CHECK_LT(i + 1, n);
  18. //遍历各个区间,通过插值计算平均值,最终加到结果中
  19. auto interp = [lambda, vals](Float w, int i) {
  20. return Lerp((w - lambda[i]) / (lambda[i + 1] - lambda[i]), vals[i],
  21. vals[i + 1]);
  22. };
  23. for (; i + 1 < n && lambdaEnd >= lambda[i]; ++i) {
  24. Float segLambdaStart = std::max(lambdaStart, lambda[i]);
  25. Float segLambdaEnd = std::min(lambdaEnd, lambda[i + 1]);
  26. sum += 0.5 * (interp(segLambdaStart, i) + interp(segLambdaEnd, i)) *
  27. (segLambdaEnd - segLambdaStart);
  28. }
  29. return sum / (lambdaEnd - lambdaStart);
  30. }

针对任意SPD转化为$ X_\lambda Y_\lambda Z _\lambda$的计算,PRBT通过三条曲线进行适配。

所有的Spectrum都必须提供这个方法。在渲染图片的一个像素时,一束携带光谱信息的光线射入摄像机中的胶片,第一步胶片会将SPD转化为xyz系数,再经过一系列处理,最终将其转化为可以显示的RGB值。

RGB系数根据基于SPD相应曲线积分计算获得。对于既定曲线,乘积积分可通过预计算实现,并将全转换表示为一个矩阵。

  1. inline void XYZToRGB(const Float xyz[3], Float rgb[3]) {
  2. rgb[0] = 3.240479f * xyz[0] - 1.537150f * xyz[1] - 0.498535f * xyz[2];
  3. rgb[1] = -0.969256f * xyz[0] + 1.875991f * xyz[1] + 0.041556f * xyz[2];
  4. rgb[2] = 0.055648f * xyz[0] - 0.204043f * xyz[1] + 1.057311f * xyz[2];
  5. }
  6. inline void RGBToXYZ(const Float rgb[3], Float xyz[3]) {
  7. xyz[0] = 0.412453f * rgb[0] + 0.357580f * rgb[1] + 0.180423f * rgb[2];
  8. xyz[1] = 0.212671f * rgb[0] + 0.715160f * rgb[1] + 0.072169f * rgb[2];
  9. xyz[2] = 0.019334f * rgb[0] + 0.119193f * rgb[1] + 0.950227f * rgb[2];
  10. }
  11. void ToRGB(Float rgb[3]) const {
  12. Float xyz[3];
  13. ToXYZ(xyz);
  14. XYZToRGB(xyz, rgb);
  15. }

RGBSpectrum

讲光谱系数转化为RGB值(先转成x、y、z,再转成RGB值)

  1. static RGBSpectrum FromSampled(const Float *lambda, const Float *v, int n) {
  2. if (!SpectrumSamplesSorted(lambda, v, n)) {
  3. std::vector<Float> slambda(&lambda[0], &lambda[n]);
  4. std::vector<Float> sv(&v[0], &v[n]);
  5. SortSpectrumSamples(&slambda[0], &sv[0], n);
  6. return FromSampled(&slambda[0], &sv[0], n);
  7. }
  8. Float xyz[3] = {0, 0, 0};
  9. for (int i = 0; i < nCIESamples; ++i) {
  10. Float val = InterpolateSpectrumSamples(lambda, v, n, CIE_lambda[i]);
  11. xyz[0] += val * CIE_X[i];
  12. xyz[1] += val * CIE_Y[i];
  13. xyz[2] += val * CIE_Z[i];
  14. }
  15. Float scale = Float(CIE_lambda[nCIESamples - 1] - CIE_lambda[0]) /
  16. Float(CIE_Y_integral * nCIESamples);
  17. xyz[0] *= scale;
  18. xyz[1] *= scale;
  19. xyz[2] *= scale;
  20. return FromXYZ(xyz);
  21. }
  22. Float InterpolateSpectrumSamples(const Float *lambda, const Float *vals, int n,
  23. Float l) {
  24. for (int i = 0; i < n - 1; ++i) CHECK_GT(lambda[i + 1], lambda[i]);
  25. if (l <= lambda[0]) return vals[0];
  26. if (l >= lambda[n - 1]) return vals[n - 1];
  27. int offset = FindInterval(n, [&](int index) { return lambda[index] <= l; });
  28. CHECK(l >= lambda[offset] && l <= lambda[offset + 1]);
  29. Float t = (l - lambda[offset]) / (lambda[offset + 1] - lambda[offset]);
  30. return Lerp(t, vals[offset], vals[offset + 1]);
  31. }

因为这里的代码都要后面几章才会用到,看得不太明白,待看到后面几章后再补充。

剩下的辐射度部分比较简单(稍微介绍了一下brdf、btdf、bsdf、bssrdf,而且和第二版是一样的),而且知乎上已经有一些比较好的解释了,不做赘述。不过我依然建议去看原文。

PBRT笔记(4)——颜色和辐射度的更多相关文章

  1. PBRT笔记(8)——材质

    BSDF类 表面着色器会绑定场景中每一个图元(被赋予了这个着色器),而表面着色器则由Material类的实例来表示.它会拥有一个BSDF类对象(可能是BSSDF),用于计算表面上每一点的辐射度(颜色) ...

  2. PBRT笔记(1)——主循环、浮点误差

    PBRT2与3之间的改动 增加了一个功能完备的BRDF模型,支持体积光照与重要性多重路径采样. 次表面散射,基于光线追踪技术,无需预处理. 解决浮点数四折五入的问题 光子映射 样本生成 第一章多了讲并 ...

  3. PBRT笔记(13)——光线传播1:表面反射

    采样反射函数 BxDF::Sample_f()方法根据与相应的散射函数相似的分布来选择方向.在8.2节中,该方法用于寻找来自完美镜面的反射和透射光线;在这里讲介绍实现其他类型的采样技术. BxDF:: ...

  4. PBRT笔记(11)——光源

    自发光灯光 至今为止,人们发明了很多光源,现在被广泛使用的有: 白炽灯的钨丝很小.电流通过灯丝时,使得灯丝升温,从而使灯丝发出电磁波,其波长的分布取决于灯丝的温度.但大部分能量都被转化为热能而不是光能 ...

  5. PBRT笔记(10)——体积散射

    体散射处理过程 3个影响参与介质在环境中的辐射度分布的主要因素: 吸收:减少光能,并将其转化为别的能量,例如热量. 发光:由光子发射光能至环境中. 散射:由于粒子碰撞,使得一个方向的辐射度散射至其他方 ...

  6. PBRT笔记(7)——反射模型

    基础术语 表面反射可以分为4大类: diffuse 漫反射 glossy specular 镜面反射高光 perfect specular 完美反射高光 retro-reflective distri ...

  7. advance shading——基础(辐射度测定)

    辐射度测定(radiometry) <real time rendering>在这章上来就说了一大堆光照方面的物理术语,不知该怎么翻译.后来在维基百科上看到这个表,清楚了很多(这里的w是瓦 ...

  8. PBR Step by Step(二)辐射度

    转载请注明出处:http://www.cnblogs.com/jerrycg/p/4929119.html  基于物理的渲染要尽量遵循能量守恒原则,主要的测量单位为辐射度. 辐射能Radiant en ...

  9. PBRT笔记(14)——光线传播2:体积渲染

    传输公式 传输方程是控制光线在吸收.发射和散射辐射的介质中的行为的基本方程.它解释了第11章中描述的所有体积散射过程--吸收.发射和内.外散射.并给出了一个描述环境中辐射分布的方程.光传输方程实际上是 ...

随机推荐

  1. JQuery 的遍历方法 $.each

    博主呢最近在公司实习,发现公司基本上都会统一代码风格,今天看到还有很多事用JQuery写的js 请求Ajax与后台进行数据交互的方式 当我看到$each 遍历时 然我想起我学JQuery的时候 于是复 ...

  2. Pandas系列(一)-Series详解

    一.初始Series Series 是一个带有 名称 和索引的一维数组,既然是数组,肯定要说到的就是数组中的元素类型,在 Series 中包含的数据类型可以是整数.浮点.字符串.Python对象等. ...

  3. css实现移动端水平滚动导航

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  4. 🍓 移动端调试工具之vconsole的使用~ 🍓

    这里以在vue项目中的使用为例⬇️ 嗯模块化的. 不消多说,先cnpm install vconsole -S 然后在mian.js中配置之- ok啦-- 开发混合app的筒子,使用mac的话也有别的 ...

  5. Vue2.0的三种常用传值方式、父传子、子传父、非父子组件传值

    参考链接:https://blog.csdn.net/lander_xiong/article/details/79018737

  6. 设计模式四: 抽象工厂(Abstract Factory)

    简介 抽象工厂模式是创建型模式的一种, 与工厂方法不同的是抽象工厂针对的是生产一组相关的产品, 即一个产品族. 抽象工厂使用工厂方法模式来生产单一产品, 单一产品的具体实现分别属于不同的产品族. 抽象 ...

  7. 分布式系列七: zookeeper简单用法

    zookeeper是分布式开源框架, 是Google Chubby的一个实现, 主要作为分布式系统的协调服务. Dobbo等框架使用了其功能. zookeeper特性 顺序一致性: 事务请求最终会严格 ...

  8. mongoose 连接数据库操作

    连接数据库 var mongoose = require('mongoose'); var schema = mongoose.Schema; // 连接MongoDB mongoose.connec ...

  9. 在浏览器中查看.vue文件的源码

  10. [JavaScript]ECMA-6 yield语法

    概述 yield关键字用于并且仅限于生成器函数(generator)内部,作用是暂停(并返回)/重启(可选修改该栈环境变量)该函数栈环境. 一般语法 调用生成器函数时返回一个可迭代对象,当调用该对象的 ...