目录
补充Material.hpp
补充计算着色函数-eval()
补充计算公式-private:
补充材质剩下的属性-pdf&sample
main.cpp
材质属性&加入物体
优化部分
Sphere::evalDiffuseColor()
模型有黑点
结果:spp=4,用时25min
提高部分作业要求:
看之前可以结合我的另一篇博客一起看:GAMES101作业7提高-实现微表面模型你需要了解的知识
我们要做什么?主要操作是在Material.hpp里完成的。
补充Material.hpp 补充计算着色函数-eval()提高部分要实现的微表面模型,其实跟DIFFUSE一样,也是一种材质,需要通过一系列计算得到BRDF的值,即路径Scene.cpp -> castRay()函数代码中,直接光照和间接光照计算公式中f_r的值:
dir = L_i * f_r * cos_theta * cos_theta_l / dis2 / pdf_light;
indir = castRay(r, depth + 1) * f_r * cos_theta / pdf_hemi / RussianRoulette;
赋值分别是:
//dir
Vector3f f_r = inter.m->eval(wo, -ws, N);
//indir
Vector3f f_r = obj_to_scene.m->eval(wo, wi, N);
那我们的目光就锁定在eval()这个函数,这个函数是Material.hpp中class Material定义的,用以计算材质返回颜色值。
class Material{
public:
...
//计算光线的贡献
inline Vector3f eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N);
...
}
Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){
switch(m_type){
case DIFFUSE:
{
//计算DIFFUSE贡献 -> kd
float cosalpha = dotProduct(N, wo);//只看半球,另一半不看,所以要判断一下wo和N的夹角
if (cosalpha > 0.0f) {
Vector3f diffuse = Kd / M_PI;
return diffuse;
}
else
return Vector3f(0.0f);
break;
}
}
}
那就先在eval()里定义好MICROFACET这个材质,已经尽量很详细的注释了:
Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){
switch(m_type){
case DIFFUSE:
{
...
}
case MICROFACET:
{
float cosalpha = dotProduct(N, wo);//只看半球,另一半不看,所以要判断一下wo和N的夹角
if (cosalpha > 0.0f) {
//参数的计算
//注意:wi这里是入射光线的方向,因此计算需要变成-wi
float roughness = 0.8;
Vector3f H = (-wi + wo).normalized();
float NdotH = std::max(dotProduct(N, H), 0.0f);
float NdotV = std::max(dotProduct(N, wo), 0.0f);
float NdotL = std::max(dotProduct(N, -wi), 0.0f);
//计算法线密度函数 D
float D = DistributionGGX(NdotH, roughness);
//计算阴影遮挡函数 G
//Disney Way
float G = GeometrySmith_Disney(N, wo, -wi, roughness);
//UE4 way
//float G = GeometrySmith_UE4(N, wo, -wi, roughness);
//菲涅尔项 F
float ior = 1.5;
float F;
fresnel(wi, N, ior, F);
//计算镜面反射的BRDF
float m = 4 * std::max(NdotL * NdotL, 0.00001f);
float Specular = D * G * F / m;
//计算反射和折射光占比
//反射ks
float ks = F;
float kd = 1 - F;
//漫反射的BRDF
float rou = 1.0f;//定义漫反射材质反射率,大小在(0,1),直接给1
float Diffuse = rou / M_PI;
//值得注意的是,这里的Kd和Ks才是对应的颜色,而Specular已经乘过F了(已经考虑了反射光的占比),这里就不用再乘以ks了
return Kd * Diffuse * kd + Ks * Specular;
}
else
return Vector3f(0.0f);
break;
}
}
}
补充计算公式-private:
与上述代码使用到的fresnel()函数相同,我们还需要定义计算G、D值的函数,这些函数跟fresnel()一样,都是在Material类的private里定义的,在private里添加就行。
private:
...
float DistributionGGX(float NdotH, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float m = NdotH * NdotH * (a2 - 1) + 1;
return a2 / std::max(M_PI * m * m, 0.000001f);
}
//定义阴影遮挡函数G
//Disney way:
//对G1的实现
float SmithG_GGX(float NdotV, float roughness) {
float r = 0.5 + roughness / 2.0f;
float m = r * r + (1 - r * r) * NdotV * NdotV;
return 2.0f * NdotV / (NdotV + std::sqrt(m));
}
//光源方向和观察方向分别计算ggx1和ggx2,相乘得到G
float GeometrySmith_Disney(Vector3f N, Vector3f V, Vector3f L, float roughness) {
float NdotV = std::max(dotProduct(N, V), 0.0f);
float NdotL = std::max(dotProduct(N, L), 0.0f);
float ggx1 = SmithG_GGX(NdotL, roughness);
float ggx2 = SmithG_GGX(NdotV, roughness);
return ggx1 * ggx2;
}
//UE4 way
//G1
float GeometrySchlickGGX(float NdotV, float roughness) {
float r = roughness + 1;
float k = r * r / 8;
float m = NdotV / NdotV * (1.f - k) + k;
return NdotV / m;
}
//光源方向和观察方向分别计算ggx1和ggx2,相乘得到G
float GeometrySmith_UE4(Vector3f N, Vector3f V, Vector3f L, float roughness) {
float NdotV = std::max(dotProduct(N, V), 0.0f);
float NdotL = std::max(dotProduct(N, L), 0.0f);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
return ggx1 * ggx2;
}
补充材质剩下的属性-pdf&sample
这里直接贴代码就行,完全是copyDIFFUSE材质的。
Vector3f Material::sample(const Vector3f &wi, const Vector3f &N){
switch(m_type){
case DIFFUSE:
{
// 均匀地对半球采样
//半球z轴值z∈[0,1]
// r -> 以法线为旋转轴的半径,x²+y²+z²=1,r²=x²+y²
//phi∈[0,2Π],旋转角度
float x_1 = get_random_float(), x_2 = get_random_float();//随机[0,1]取值
float z = std::fabs(1.0f - 2.0f * x_1);//不是很理解为什么不直接取[0,1]随机数
float r = std::sqrt(1.0f - z * z), phi = 2 * M_PI * x_2;
Vector3f localRay(r*std::cos(phi), r*std::sin(phi), z);//半球面上随机光线的方向
//接着需要把半球上的局部光线坐标转换成世界坐标
return toWorld(localRay, N);
break;
}
case MICROFACET:
{
float x_1 = get_random_float(), x_2 = get_random_float();
float z = std::fabs(1.0f - 2.0f * x_1);
float r = std::sqrt(1.0f - z * z), phi = 2 * M_PI * x_2;
Vector3f localRay(r * std::cos(phi), r * std::sin(phi), z);
return toWorld(localRay, N);
break;
}
}
}
//计算概率密度函数pdf
float Material::pdf(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){
switch(m_type){
case DIFFUSE://材质
{
//均匀采样,则pdf为常数1/2Π
if (dotProduct(wo, N) > 0.0f)
return 0.5f / M_PI;
else
return 0.0f;
break;
}
case MICROFACET:
{
if (dotProduct(wo, N) > 0.0f)
return 0.5f / M_PI;
else
return 0.0f;
break;
}
}
}
到这里Material.hpp就结束了,别忘了在开头加上MICROFACET的材质类型。
enum MaterialType { DIFFUSE,MICROFACET};
main.cpp
材质属性&加入物体
这里定义了微表面材质的属性,并向场景里放入了小球,注意,这里的Kd和Ks分别对应颜色。
...
Material*Microfacet = new Material(MICROFACET,Vector3f(0.0f));
Microfacet->Kd = Vector3f(0.5, 0.5, 0.5);
Microfacet->Ks = Vector3f(0.5, 0.5, 0.5);
Sphere sphere(Vector3f(120, 100, 300), 100, Microfacet);
...
//开始划分三角形网格
MeshTriangle floor("../models/cornellbox/floor.obj", white);
//MeshTriangle shortbox("../models/cornellbox/shortbox.obj", white);
//MeshTriangle tallbox("../models/cornellbox/tallbox.obj", white);
MeshTriangle left("../models/cornellbox/left.obj", red);
MeshTriangle right("../models/cornellbox/right.obj", green);
MeshTriangle light_("../models/cornellbox/light.obj", light);
//把物体加入场景中:objects.push_back(object);
scene.Add(&floor);
scene.Add(&sphere);
//scene.Add(&shortbox);
//scene.Add(&tallbox);
scene.Add(&left);
scene.Add(&right);
scene.Add(&light_);
优化部分
到这里并没有完全结束!如果这个时候你直接运行会出现错误:
因此需要给这个函数赋予值,尽管我们用不上他。
Sphere::evalDiffuseColor()给他一个空值就行。
Vector3f evalDiffuseColor(const Vector2f &st)const {
//return m->getColor();
return {};
}
模型有黑点
这时能正常运行了,但是会出现这样的问题,球的表面会有很多黑点:(spp=4)
这个问题我也是上网搜索到了解决办法,这其实是由于光线和球判断交点的时候误差太大,导致有黑点。具体可以看看这篇文章:
Games101,作业7(微表面模型)_Elsa的迷弟的博客
解决办法是,Sphere::getIntersection()函数里给t0一个判断精度0.5,这个精度也是这篇文章的作者通过不同值的比较得到的:
Intersection getIntersection(Ray ray){
Intersection result;
result.happened = false;
Vector3f L = ray.origin - center;
float a = dotProduct(ray.direction, ray.direction);
float b = 2 * dotProduct(ray.direction, L);
float c = dotProduct(L, L) - radius2;
float t0, t1;
if (!solveQuadratic(a, b, c, t0, t1)) return result;
if (t0 < 0) t0 = t1;
if (t0 < 0) return result;
//给了精度0.5
if (t0 > 0.5) {
result.happened = true;
result.coords = Vector3f(ray.origin + ray.direction * t0);
result.normal = normalize(Vector3f(result.coords - center));
result.m = this->m;
result.obj = this;
result.distance = t0;
}
return result;
}
优化后的结果:(spp=4)