上面两篇博客主要讲述了路径追踪渲染的理论,这次来展示一下它的代码实现。这部分代码主要是我参考PBRT写的,没有实现光的折射,但也足以帮助理解路径追踪算法的原理。
首先,是遍历图片中的所有像素,对每个像素进行采样,注意需要将得到的RGB值进行gamma矫正:
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
Pixel p = sample_pixel(camera, i, j, width, height);
XMFLOAT3 rgb = Spectrum::XYZToRGB(p.xyz);
if (p.filterWeightSum > 0.0)
{
rgb.x /= p.filterWeightSum;
rgb.y /= p.filterWeightSum;
rgb.z /= p.filterWeightSum;
}
int r = int(MathHelper::Clamp<float>(GammaCorrect(rgb.x) *255.0f + 0.5f, 0.0, 255.0));
int g = int(MathHelper::Clamp<float>(GammaCorrect(rgb.y) *255.0f + 0.5f, 0.0, 255.0));
int b = int(MathHelper::Clamp<float>(GammaCorrect(rgb.z) *255.0f + 0.5f, 0.0, 255.0));
image.setPixelColor(QPoint(i, height - 1 - j), QColor(r, g, b));
}
}
具体采样的代码如下,就是在像素内部随机选一个点,打出一条光线计算光强。注意还需要计算一下采样点的权重,在这里我选用了三角滤波进行加权:
Pixel PathTracingRenderer::sample_pixel(Camera* camera, int x, int y, int width, int height)
{
float sample_x = 0.0f;
float sample_y = 0.0f;
Spectrum r;
Pixel p;
for (int i = 0; i < sample_count; i++)
{
sample_x = x + generateRandomFloat();
sample_y = y + generateRandomFloat();
float w = TriangleFilterEval(sample_x - x - 0.5f, sample_y - y - 0.5f, 1.0f);
Ray ray = camera->getRay(sample_x / width, sample_y / height);
Spectrum radiance = Li(ray);
p.xyz = MathHelper::AddFloat3(p.xyz, RGBToXYZ((radiance * w).getFloat3()));
p.filterWeightSum += w;
}
return p;
}
接下来我们深入函数Li,看看究竟是怎么计算光强的。由于代码有点多,所以我将要点都予以注释:
Spectrum PathTracingRenderer::Li(const Ray& r)
{
bool specularBounce = false;
Spectrum L, beta(1.0f, 1.0f, 1.0f);
Ray ray(r);
// 逐步增加路径条数,计算每次路径的P值
for (int bounce = 0; bounce < max_bounce; bounce++)
{
IntersectInfo it;
//对场景中的物体做碰撞检测,可以利用BVH和KD-Tree加快速度
g_pGlobalSys->cast_ray_to_get_intersection(ray, it);
if (bounce == 0 || specularBounce)
{
if (!it.isSurfaceInteraction())
{
//如果没有碰撞到物体,计算所有光的环境光强
auto& lights = g_pGlobalSys->objectManager.getAllLights();
for each (Light* light in lights)
L += (beta* light->Le(ray));
}
else
{
//如果碰撞到的是AreaLight,则计算AreaLight的光强(Le函数内部会剔除掉非AreaLight)
L += beta * it.Le(MathHelper::NegativeFloat3(ray.direction));
}
}
if (!it.isSurfaceInteraction() || bounce >= max_bounce)
break;
//根据碰撞到物体的材质赋予其BSDF
it.ComputeScatteringFunctions();
//计算直接光照的强度,相当于物体本身的发光
L += beta * UniformSampleOneLight(it);
XMFLOAT3 wo(-ray.direction.x, -ray.direction.y, -ray.direction.z), wi;
float pdf;
BxDFType flags;
float f1 = generateRandomFloat(), f2 = generateRandomFloat();
//根据BRDF采样出射方向wi和pdf
Spectrum f = it.bsdf->Sample_f(wo, &wi, XMFLOAT2(f1, f2), &pdf, BSDF_ALL, &flags);
if (f.isBlack() || pdf == 0.0f)
break;
beta *= (f * abs(MathHelper::DotFloat3(wi, it.normal))/pdf);
specularBounce = (flags&BxDFType::BSDF_SPECULAR) != 0;
ray = it.spawnRay(wi);
// 俄罗斯轮盘计算权重和决定是否要将路径终止
if (bounce > 3)
{
float q = std::max<float>(0.05f, 1.0 - beta.y());
if (generateRandomFloat() < q)
break;
beta /= (1.0 - q);
}
}
return L;
}
值得一提的是,UniformSampleOneLight函数会随机挑选一个光源进行直接光照的计算,具体的计算过程用的是复合重要性采样的思想:
Spectrum PathTracingRenderer::UniformSampleOneLight(const IntersectInfo& it)
{
std::vector<Light*> lights = g_pGlobalSys->objectManager.getAllLights();
Light* sel_light = lights[rand() % (lights.size())];
XMFLOAT2 uScattering(generateRandomFloat(), generateRandomFloat());
XMFLOAT2 uLight(generateRandomFloat(), generateRandomFloat());
float light_count = g_pGlobalSys->objectManager.getLightsCountParameter();
return light_count * EstimateDirect(it, uScattering, sel_light, uLight);
}
Spectrum EstimateDirect(const IntersectInfo& it, XMFLOAT2 uScattering, Light* light, XMFLOAT2 uLight, bool specular)
{
Spectrum ld;
BxDFType bsdfFlags = specular ? BSDF_ALL : BxDFType(BSDF_ALL & ~BSDF_SPECULAR);
// sample light source with multiple importance sampling
XMFLOAT3 wi;
float light_pdf = 0.0f, scattering_pdf = 0.0f;
VisibilityTester vt;
Spectrum li = light->sample_li(it, uLight, &wi, &light_pdf, vt);
if (light_pdf > 0.0f && !li.isBlack())
{
Spectrum f;
if (it.isSurfaceInteraction())
{
f = it.bsdf->f(it.wo, wi, bsdfFlags) * MathHelper::DotFloat3(wi, it.normal);
scattering_pdf = it.bsdf->Pdf(it.wo, wi, bsdfFlags);
}
if (!f.isBlack())
{
if (!vt.unoccluded())
li = Spectrum();
if (!li.isBlack())
{
if (light->isDelta())
ld += f * li / light_pdf;
else
{
float weight = PowerHeuristic(1, light_pdf, 1, scattering_pdf);
ld += f * li * weight / light_pdf;
}
}
}
}
// sample BSDF with multiple importance sampling
if (!light->isDelta())
{
Spectrum f;
bool sampledSpecular = false;
BxDFType sampled_type;
if (it.isSurfaceInteraction())
{
f = it.bsdf->Sample_f(it.wo, &wi, uScattering, &scattering_pdf, bsdfFlags, &sampled_type);
f = f * MathHelper::DotFloat3(wi, it.normal);
sampledSpecular = sampled_type & BSDF_SPECULAR;
}
if (!f.isBlack() && scattering_pdf > 0.0f)
{
float weight = 1.0;
if (!sampledSpecular)
{
light_pdf = light->Pdf_Li(it, wi);
if (light_pdf == 0.0)
return ld;
weight = PowerHeuristic(1, scattering_pdf, 1, light_pdf);
}
IntersectInfo light_it;
Ray ray = it.spawnRay(wi);
g_pGlobalSys->cast_ray_to_get_intersection(ray, light_it);
Spectrum li;
if (light_it.isSurfaceInteraction())
{
if (light_it.obj->getType() == AREA_LIGHT)
li = light_it.Le(MathHelper::NegativeFloat3(wi));
}
else
li = light->Le(ray);
if (!li.isBlack())
ld = ld + f * li * weight / scattering_pdf;
}
}
return ld;
}
主要的代码结构就是这样,接下来展示一下我利用上述代码实现的渲染结果:
经和PBRT比较,同样的场景和参数,渲染结果差距不大。
原文:https://www.cnblogs.com/wickedpriest/p/12633981.html