GAMES101——作业5 光线与三角形相交(菲涅尔反射率)

任务

需要修改的函数是:
Renderer.cpp 中的 Render() :这里你需要为每个像素生成一条对应的光线,然后调用函数 castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相应像素中。
Triangle.hpp 中的 rayTriangleIntersect() : v0, v1, v2 是三角形的三个顶点,orig 是光线的起点, dir 是光线单位化的方向向量。 tnear, u, v 是你需要使用我们课上推导的 Moller-Trumbore 算法来更新的参数。

实现

Render

在这里我们要做的就是将像素的位置变换成像素在空间的坐标,然后根据像素在空间的坐标和相机的坐标,得到该像素对应的光线,从而实现光线追踪。

一个像素是通过下面的步骤得来的 ,那么假如知道一个像素的位置,我们可以倒着推出其在世界坐标的位置。

①将像素坐标转换到图像坐标。

像素坐标左上角为(0,0),范围是x∈[0,1],y∈[0,1],而图像坐标原点则为正中心,先转化成NDC坐标,坐标范围是x∈[-1,1],y∈[-1,1],再通过宽高比计算出图像的坐标,x∈[-width/2,width/2],y∈[-height/2,height/2],因此我们先计算出像素中心点的图像坐标。

设某一个像素点的坐标为(x0,y0),则

x = (2 * (x0+0.5)/width - 1 )*imageAspectRatio

y = (1 -2*(y0+0.5)/scene.height )

括号内是点在NDC坐标的位置,x乘以宽高比就得到了图像坐标。

②将图像坐标转化为相机坐标

得到了点在图像坐标的位置后,就可以将其转化为相机坐标了,这时候只需要知道该图像与相机的距离,就可以推算出其大小。因为图像离相机无论多远,都会规范化到[-1,1]的NDC坐标上,而第一步就是将其变为NDC坐标,再调整了一下宽高而已,并且经过上面的处理后这里的高总为[-1,1]。和相机的距离可以通过视角的大小的一半的正切值求出。

tan(forY/2) =( height/2 ) / 距离,在这里也就是,1/距离。因此距离就是 1/tan(forY/2),结合上面的推到,可以得到最终的相机坐标系的x,y位置。

float scale = std::tan(deg2rad(scene.fov * 0.5f));

...............

x = (2 * ((float)i+0.5)/scene.width - 1 )*imageAspectRatio*scale;

y = (1.0f -2*((float)j+0.5)/scene.height )*scale;

③将相机坐标转化为世界坐标

其实就是乘以视图矩阵的逆矩阵就好了,该作业框架里,直接将相机放在了世界坐标的原点,所以我们不需要进行此变换。

cpp 复制代码
void Renderer::Render(const Scene& scene)
{
    std::vector<Vector3f> framebuffer(scene.width * scene.height);

    float scale = std::tan(deg2rad(scene.fov * 0.5f));
    float imageAspectRatio = scene.width / (float)scene.height;

    Vector3f eye_pos(0);
    int m = 0;
    for (int j = 0; j < scene.height; ++j)
    {
        for (int i = 0; i < scene.width; ++i)
        {
            float x;
            float y;

            x = (2 * ((float)i+0.5)/scene.width - 1 )*imageAspectRatio*scale;
            y = (1.0f -2*((float)j+0.5)/scene.height )*scale;     

            Vector3f dir = normalize(Vector3f(x, y, -1)); 
            framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
        }
        UpdateProgress( j / (float)scene.height);
    }

    FILE* fp = fopen("binary.ppm", "wb");
    (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
    for (auto i = 0; i < scene.height * scene.width; ++i) {
        static unsigned char color[3];
        color[0] = (char)(255 * clamp(0, 1, framebuffer[i].x));
        color[1] = (char)(255 * clamp(0, 1, framebuffer[i].y));
        color[2] = (char)(255 * clamp(0, 1, framebuffer[i].z));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}
rayTriangleIntersect

这里直接根据下面的公式代入数据了,过程的推导可以查阅相关教程,计算出结果后,需要先判断t是否大于0,重心坐标的三个值是否都大于0,都大于0说明光线与三角形相交。

cpp 复制代码
bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
                          const Vector3f& dir, float& tnear, float& u, float& v)
{

    Vector3f E1 = v1-v0;
    Vector3f E2 = v2-v0;
    Vector3f S = orig - v0;
    Vector3f S1 = crossProduct(dir,E2);
    Vector3f S2 = crossProduct(S,E1);

    tnear = dotProduct(S2,E2)/dotProduct(S1,E1);
    u = dotProduct(S1,S)/dotProduct(S1,E1);
    v = dotProduct(S2,dir)/dotProduct(S1,E1);
    if(u>=0 && v >= 0 && (1-u-v)>=0 && tnear >= 0){
        return true;
    }

    return false;
}

结果

值得注意的点

在该作业中,对透明的球已经实现了菲涅尔反射的模型。观察渲染的图片里透明球的边缘,可以注意到比较亮,查询代码发现了菲涅尔系数的求解,因此这里提一下菲涅尔反射系数。

cpp 复制代码
float fresnel(const Vector3f &I, const Vector3f &N, const float &ior)
{
    float cosi = clamp(-1, 1, dotProduct(I, N));    //确保光线合法
    float etai = 1, etat = ior;   //etai是入射介质的折射率,etat是出射物质的折射率
    //cosi>0,说明光是从物体内射向空气的,因此交换两个折射率
    if (cosi > 0) {  std::swap(etai, etat); }
    // 利用斯涅尔公式计算出射角的正弦值
    float sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi));
    // 如果大于1,说明发生了全反射,因此反射系数为1。
    if (sint >= 1) {
        return 1;
    }
    else {
        float cost = sqrtf(std::max(0.f, 1 - sint * sint));
        cosi = fabsf(cosi);
        float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
        float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
        return (Rs * Rs + Rp * Rp) / 2;
    }
    // As a consequence of the conservation of energy, transmittance is given by:
    // kt = 1 - kr;
}

使用斯涅尔公式求解sint,其实这个高中就学过了。

菲涅尔反射系数的精确求解法

根据公式计算出s和p偏振光的反射系数,因为光源是非偏振光,因此将两个反射系数取平均就能得到最终的反射系数

菲涅尔系数的近似求解法

代码中的反射系数明显采取了精确的求法。在castRay的代码中,可以看到其用武之地

cpp 复制代码
                Vector3f reflectionColor = castRay(reflectionRayOrig, reflectionDirection, scene, depth + 1);
                Vector3f refractionColor = castRay(refractionRayOrig, refractionDirection, scene, depth + 1);
                float kr = fresnel(dir, N, payload->hit_obj->ior);
                hitColor = reflectionColor * kr + refractionColor * (1 - kr);

这里是对应的反射与折射材质(REFLECTION_AND_REFRACTION),使用菲涅尔反射系数,可以真实地分配反射与折射光的强度。

相关推荐
Ajiang282473530423 分钟前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
‘’林花谢了春红‘’5 小时前
C++ list (链表)容器
c++·链表·list
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.999 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王9 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_9 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀9 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun9 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥10 小时前
c++中mystring运算符重载
开发语言·c++·算法