当前位置:网站首页>GAMES101作业7提高-微表面材质的实现过程

GAMES101作业7提高-微表面材质的实现过程

2022-06-27 03:12:00 flashinggg

目录

补充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)

结果:spp=256,用时25min

 

原网站

版权声明
本文为[flashinggg]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_41835314/article/details/125448454