GAMES101 作业5 详细注释版本

作业5主要需要对Renderer.cpp和Triangle.hpp进行修改,我在这里贴出这两者的代码

#include <fstream>
#include "Vector.hpp"
#include "Renderer.hpp"
#include "Scene.hpp"
#include <optional>

inline float deg2rad(const float &deg)
{ return deg * M_PI/180.0; }

// Compute reflection direction
Vector3f reflect(const Vector3f &I, const Vector3f &N)
{
    return I - 2 * dotProduct(I, N) * N;
}

// [comment]
// Compute refraction direction using Snell's law
//
// We need to handle with care the two possible situations:
//
//    - When the ray is inside the object
//
//    - When the ray is outside.
//
// If the ray is outside, you need to make cosi positive cosi = -N.I
//
// If the ray is inside, you need to invert the refractive indices and negate the normal N
// [/comment]
Vector3f refract(const Vector3f& I, const Vector3f& N, const float& ior)
{
    // 计算入射光线I和法线N之间的夹角余弦值
    //clamp是个区间限定函数,在这里其实就是把cosi的值限定在了[-1,1]之间
    float cosi = clamp(-1, 1, dotProduct(I, N));
    // 定义入射介质和折射介质的折射率
    float etai = 1, etat = ior;
    // 定义法线向量
    Vector3f n = N;
    //如果入射光从介质1(etai)->介质2(etat),则夹角>90°;
    //如果入射光从介质2->介质1,则夹角<90,N也要反过来,折射率之比也需要换一下
    // 如果夹角余弦值小于0,取其相反数
    if (cosi < 0) { cosi = -cosi; }
    // 否则交换入射介质和折射介质的折射率,并将法线取反
    else { std::swap(etai, etat); n = -N; }
    // 计算入射介质和折射介质的折射率比值
    float eta = etai / etat;
    // 计算k值
    float k = 1 - eta * eta * (1 - cosi * cosi);
    // 如果k小于0,返回0,否则返回折射向量
    return k < 0 ? 0 : eta * I + (eta * cosi - sqrtf(k)) * n;
}



// [comment]
// Compute Fresnel equation
//
// \param I is the incident view direction
//
// \param N is the normal at the intersection point
//
// \param ior is the material refractive index
// [/comment]
float fresnel(const Vector3f& I, const Vector3f& N, const float& ior)
{
    // 计算入射光线I和法线N之间的夹角余弦值
    float cosi = clamp(-1, 1, dotProduct(I, N));
    // 定义入射介质和折射介质的折射率
    float etai = 1, etat = ior;
    // 如果夹角余弦值大于0,交换入射介质和折射介质的折射率
    if (cosi > 0) { std::swap(etai, etat); }
    // 根据斯涅尔定律计算折射角正弦值
    //snell定律:sin比等于折射率反比
    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);
        // 计算Rs和Rp:Rs和Rp分别代表了垂直光和水平光的菲涅尔反射系数
        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;
}

// [comment]
// Returns true if the ray intersects an object, false otherwise.
//
// \param orig is the ray origin
// \param dir is the ray direction
// \param objects is the list of objects the scene contains
// \param[out] tNear contains the distance to the cloesest intersected object.
// \param[out] index stores the index of the intersect triangle if the interesected object is a mesh.
// \param[out] uv stores the u and v barycentric coordinates of the intersected point
// \param[out] *hitObject stores the pointer to the intersected object (used to retrieve material information, etc.)
// \param isShadowRay is it a shadow ray. We can return from the function sooner as soon as we have found a hit.
// [/comment]

// 定义一个名为 trace 的函数,它接受四个参数并返回一个 std::optional<hit_payload> 类型的值
std::optional<hit_payload> trace(
    const Vector3f& orig, // 第一个参数是一个常量 Vector3f 类型的引用,表示光线的起点
    const Vector3f& dir, // 第二个参数也是一个常量 Vector3f 类型的引用,表示光线的方向
    const std::vector<std::unique_ptr<Object> >& objects) // 第三个参数是一个常量 std::vector<std::unique_ptr<Object> > 类型的引用,表示场景中的物体列表
{
    float tNear = kInfinity; // 定义一个浮点型变量 tNear 并将其初始化为 kInfinity
    std::optional<hit_payload> payload; // 定义一个 std::optional<hit_payload> 类型的变量 payload
    for (const auto& object : objects) // 遍历场景中的每个物体
    {
        float tNearK = kInfinity; // 定义一个浮点型变量 tNearK 并将其初始化为 kInfinity
        uint32_t indexK; // 定义一个 uint32_t 类型的变量 indexK
        Vector2f uvK; // 定义一个 Vector2f 类型的变量 uvK
        if (object->intersect(orig, dir, tNearK, indexK, uvK) && tNearK < tNear) // 如果光线与当前物体相交且 tNearK 小于 tNear
        {
            payload.emplace(); // 调用 payload 的 emplace 方法创建一个新的 hit_payload 对象
            payload->hit_obj = object.get(); // 将 hit_obj 成员设置为当前物体的指针
            payload->tNear = tNearK; // 将 tNear 成员设置为 tNearK
            payload->index = indexK; // 将 index 成员设置为 indexK
            payload->uv = uvK; // 将 uv 成员设置为 uvK
            tNear = tNearK; // 更新 tNear 的值为 tNearK
        }
    }
    //payload(有效载荷)在代码世界里通俗一点讲代表着对有用的数据
    return payload; // 返回 payload 的值
}

// [comment]
 //Implementation of the Whitted-style light transport algorithm (E [S*] (D|G) L)

 //This function is the function that compute the color at the intersection point
 //of a ray defined by a position and a direction. Note that thus function is recursive (it calls itself).

 //If the material of the intersected object is either reflective or reflective and refractive,
 //then we compute the reflection/refraction direction and cast two new rays into the scene
 //by calling the castRay() function recursively. When the surface is transparent, we mix
 //the reflection and refraction color using the result of the fresnel equations (it computes
 //the amount of reflection and refraction depending on the surface normal, incident view direction
 //and surface refractive index).

 //If the surface is diffuse/glossy we use the Phong illumation model to compute the color
 //at the intersection point.
// [/comment]
Vector3f castRay(
        const Vector3f &orig, const Vector3f &dir, const Scene& scene,
        int depth)
{
    if (depth > scene.maxDepth) {
        return Vector3f(0.0,0.0,0.0);
    }

    Vector3f hitColor = scene.backgroundColor;
    if (auto payload = trace(orig, dir, scene.get_objects()); payload)
    {
        Vector3f hitPoint = orig + dir * payload->tNear;
        Vector3f N; // normal
        Vector2f st; // st coordinates
        payload->hit_obj->getSurfaceProperties(hitPoint, dir, payload->index, payload->uv, N, st);
        switch (payload->hit_obj->materialType) {
        case REFLECTION_AND_REFRACTION:
        {
            //反射方向
            Vector3f reflectionDirection = normalize(reflect(dir, N));
            //折射方向
            Vector3f refractionDirection = normalize(refract(dir, N, payload->hit_obj->ior));
            //由于计算问题会出现误差,如果点在面内就向N方向偏移;如果点在面外就向-N方向偏移
            //epsilon是定义的一个浮点数的bias,epsilon=0.00001
            Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                hitPoint - N * scene.epsilon :
                hitPoint + N * scene.epsilon;
            Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ?
                hitPoint - N * scene.epsilon :
                hitPoint + N * scene.epsilon;
            //两条光线射线又开始作为入射光线,递归使用castRay()追踪
            //这里的depth+1意思是发生了一次反射,有最大次数限制->maxDepth
            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);
            break;
        }
            //镜面物体
            case REFLECTION:
            {
                //菲涅耳定律->得到反射光线占比kr
                float kr = fresnel(dir, N, payload->hit_obj->ior);
                //计算反射射线方向
                Vector3f reflectionDirection = reflect(dir, N);
                //由于计算问题会出现误差,如果点在面内就向N方向偏移;如果点在面外就向-N方向偏移
                Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                    hitPoint + N * scene.epsilon ://epsilon是定义的一个浮点数的bias,epsilon=0.00001
                    hitPoint - N * scene.epsilon;
                //接着递归使用castRay()把计算的反射起点Orig和方向Dir带入,并*反射光线占比kr,继续跟踪折射和反射射线
                //这里的depth+1意思是发生了一次反射,有最大次数限制->maxDepth
                hitColor = castRay(reflectionRayOrig, reflectionDirection, scene, depth + 1) * kr;
                break;
            }
            default:
            {
                // 初始化环境光和镜面反射颜色
                Vector3f lightAmt = 0, specularColor = 0;
                // 偏移校正
                Vector3f shadowPointOrig = (dotProduct(dir, N) < 0) ?
                    hitPoint + N * scene.epsilon :
                    hitPoint - N * scene.epsilon;
                // 遍历场景中的所有光源
                for (auto& light : scene.get_lights()) {
                    // 计算光线方向
                    Vector3f lightDir = light->position - hitPoint;
                    // 计算光线距离的平方
                    float lightDistance2 = dotProduct(lightDir, lightDir);
                    // 归一化光线方向
                    lightDir = normalize(lightDir);
                    // 计算光线方向和法线之间的夹角余弦值
                    float LdotN = std::max(0.f, dotProduct(lightDir, N));
                    // 进行阴影测试
                    //一条shadow ray从hitpoint出发,以lightdir为方向,如果shadow_res = true->说明被遮挡了
                    auto shadow_res = trace(shadowPointOrig, lightDir, scene.get_objects());
                    // 如果返回的tnear<lightdistance -> shadow ray被路径上的某一个表面遮挡了,到达不了light,因此说明hitpoint在这条ray的阴影里
                    bool inShadow = shadow_res && (shadow_res->tNear * shadow_res->tNear < lightDistance2);
                    // 如果在阴影中,不累加光照颜色,否则累加光照颜色
                    lightAmt += inShadow ? 0 : light->intensity * LdotN;
                    //这里求反射ray方向,用到reflect(),这个函数里的lightdir定义的是从光源出发,因此这里lightDir要取-lightDir
                    //计算反射ray方向为后面求高光做准备
                    Vector3f reflectionDirection = reflect(-lightDir, N);
                    // 累加镜面反射颜色
                    specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, dir)),
                        payload->hit_obj->specularExponent) * light->intensity;
                }
                // 计算最终颜色
                hitColor = lightAmt * payload->hit_obj->evalDiffuseColor(st) * payload->hit_obj->Kd + specularColor * payload->hit_obj->Ks;
                break;
            }
        }
    }
    return hitColor;
}

// [comment]
// The main render function. This where we iterate over all pixels in the image, generate
// primary rays and cast these rays into the scene. The content of the framebuffer is
// saved to a file.
// [/comment]

void Renderer::Render(const Scene& scene)
{
    // 创建一个framebuffer,大小为场景的宽度乘以高度
    std::vector<Vector3f> framebuffer(scene.width * scene.height);
    // 计算缩放比例
    float scale = std::tan(deg2rad(scene.fov * 0.5f));//fov视场角除以2,转化为弧度制后取正切值
    // 计算图像宽高比
    float imageAspectRatio = scene.width / (float)scene.height;
    // 设置相机位置为(0,0,0)
    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和y的值
            x = 2 * scale * imageAspectRatio / scene.width * (i + 0.5) - scale * imageAspectRatio;
            y = -2 * scale / scene.height * (j + 0.5) + scale;
            //定义dir
            //这里直接让摄像机在世界坐标朝向(0,0,-1)构建screen space,就可以省去screen space ->world space的转化
            Vector3f dir = Vector3f(x, y, -1);
            dir = normalize(dir);// 计算方向向量并归一化
            // 调用castRay函数并将结果存储在framebuffer中
            framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
        }
        // 更新进度条
        UpdateProgress(j / (float)scene.height);
    }
    // 打开文件并写入ppm格式的framebuffer图像数据
    FILE* fp = fopen("D:/Desktop/rtresult.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);
}
#pragma once

#include "Object.hpp"

#include <cstring>

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, E2, S, S1, S2, re;
    // 计算三角形的两条边
    E1 = v1 - v0;
    E2 = v2 - v0;
    // 计算射线原点到三角形顶点v0的向量
    S = orig - v0;
    // 计算S1和S2
    S1 = crossProduct(dir, E2);
    S2 = crossProduct(S, E1);
    // 计算re
    re = Vector3f(dotProduct(S2, E2), dotProduct(S1, S), dotProduct(S2, dir));
    re = re / dotProduct(S1, E1);
    // 计算tnear,u和v
    tnear = re.x;
    u = re.y;
    v = re.z;
    // 判断是否相交
    //以上所有步骤都是为了算出tnear,u和v,即Moller-Trumbore 算法来更新的参数
    //tnear表示射线与三角形相交点到射线原点的距离,u和v表示射线与三角形相交点在三角形上的重心坐标
    if (tnear > 0 && v >= 0 && v <= 1 && u >= 0 && u <= 1)
        return true;
    return false;
}

class MeshTriangle : public Object
{
public:
    MeshTriangle(const Vector3f* verts, const uint32_t* vertsIndex, const uint32_t& numTris, const Vector2f* st)
    {
        uint32_t maxIndex = 0;
        for (uint32_t i = 0; i < numTris * 3; ++i)
            if (vertsIndex[i] > maxIndex)
                maxIndex = vertsIndex[i];
        maxIndex += 1;
        vertices = std::unique_ptr<Vector3f[]>(new Vector3f[maxIndex]);
        memcpy(vertices.get(), verts, sizeof(Vector3f) * maxIndex);
        vertexIndex = std::unique_ptr<uint32_t[]>(new uint32_t[numTris * 3]);
        memcpy(vertexIndex.get(), vertsIndex, sizeof(uint32_t) * numTris * 3);
        numTriangles = numTris;
        stCoordinates = std::unique_ptr<Vector2f[]>(new Vector2f[maxIndex]);
        memcpy(stCoordinates.get(), st, sizeof(Vector2f) * maxIndex);
    }

    bool intersect(const Vector3f& orig, const Vector3f& dir, float& tnear, uint32_t& index,
                   Vector2f& uv) const override
    {
        bool intersect = false;
        for (uint32_t k = 0; k < numTriangles; ++k)
        {
            const Vector3f& v0 = vertices[vertexIndex[k * 3]];
            const Vector3f& v1 = vertices[vertexIndex[k * 3 + 1]];
            const Vector3f& v2 = vertices[vertexIndex[k * 3 + 2]];
            float t, u, v;
            if (rayTriangleIntersect(v0, v1, v2, orig, dir, t, u, v) && t < tnear)
            {
                tnear = t;
                uv.x = u;
                uv.y = v;
                index = k;
                intersect |= true;
            }
        }

        return intersect;
    }

    void getSurfaceProperties(const Vector3f&, const Vector3f&, const uint32_t& index, const Vector2f& uv, Vector3f& N,
                              Vector2f& st) const override
    {
        const Vector3f& v0 = vertices[vertexIndex[index * 3]];
        const Vector3f& v1 = vertices[vertexIndex[index * 3 + 1]];
        const Vector3f& v2 = vertices[vertexIndex[index * 3 + 2]];
        Vector3f e0 = normalize(v1 - v0);
        Vector3f e1 = normalize(v2 - v1);
        N = normalize(crossProduct(e0, e1));
        const Vector2f& st0 = stCoordinates[vertexIndex[index * 3]];
        const Vector2f& st1 = stCoordinates[vertexIndex[index * 3 + 1]];
        const Vector2f& st2 = stCoordinates[vertexIndex[index * 3 + 2]];
        st = st0 * (1 - uv.x - uv.y) + st1 * uv.x + st2 * uv.y;
    }

    Vector3f evalDiffuseColor(const Vector2f& st) const override
    {
        float scale = 5;
        float pattern = (fmodf(st.x * scale, 1) > 0.5) ^ (fmodf(st.y * scale, 1) > 0.5);
        return lerp(Vector3f(0.815, 0.235, 0.031), Vector3f(0.937, 0.937, 0.231), pattern);
    }

    std::unique_ptr<Vector3f[]> vertices;
    uint32_t numTriangles;
    std::unique_ptr<uint32_t[]> vertexIndex;
    std::unique_ptr<Vector2f[]> stCoordinates;
};

凭借一些个人的理解对大多数代码加上了注释,有些地方肯定会有理解不到位或者不对的情况,欢迎大佬留言指正。

完成内容:完成了Whittd-style的光线追踪渲染

实验结果:

 完整代码git自取:Yuchenpoet/CG (github.com)   

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: games101作业5是一个有趣的编程作业,要求学生使用OpenGL编写一个基础的游戏框架。 首先,我们需要实现一个窗口和一个渲染器。窗口用来显示游戏画面,渲染器负责将图形渲染到窗口上。为了实现这两个功能,我们可以使用OpenGL的库函数来创建窗口和渲染器对象。 接下来,我们需要添加一些基本的游戏元素,比如角色、地图和物体。角色可以是一个可移动的对象,地图可以是一个二维或三维的场景,物体可以是一些可以与角色交互的元素,比如道具或敌人。这些游戏元素可以使用OpenGL的图形绘制函数来创建和渲染。 然后,我们需要处理用户的输入,比如键盘输入和鼠标输入。根据用户的输入,我们可以控制角色的移动或进行其他操作。为了实现这一功能,我们可以使用OpenGL的事件处理函数来监听用户的输入事件。 最后,我们可以添加一些游戏逻辑和交互效果,比如碰撞检测、游戏得分和游戏结束等。这些功能可以通过编写一些自定义的函数来实现,并且可以在每一帧渲染时更新游戏状态。 总之,games101作业5是一个锻炼OpenGL编程技巧的作业。通过完成这个作业,我们可以学习到如何使用OpenGL创建一个基础的游戏框架,并且可以了解到游戏开发的一些基本概念和技术。 ### 回答2: 游戏101作业5主要涉及游戏的AI设计和实现。AI(人工智能)在游戏起着重要的作用,它可以为游戏添加更多的挑战性和可玩性。在作业5,我们需要设计一个实时策略游戏,并实现其的AI。 首先,我们需要设计一个游戏世界,并将其划分为地图、单位和资源三个部分。地图是游戏的背景,单位是游戏的角色,资源是用来发展单位和地图的基础。然后,我们需要设计游戏的规则和目标,确保游戏有明确的胜利条件和失败条件。 接下来,我们需要设计游戏的AI策略。AI策略的目标是使AI角色能够根据当前情况做出正确的决策。这需要采用一定的算法和技术来实现。例如,可以使用路径规划算法来决定单位的行动路线,使用决策树或神经网络来评估当前局势和选择最佳策略。AI还需要考虑游戏的难度和平衡性,确保游戏能够提供足够的挑战同时又不至于过于困难。 最后,我们需要用编程语言来实现游戏和AI。可以使用Python或者其他适合游戏开发的语言来编写游戏的逻辑和AI算法。在实现过程,需要注意代码的结构和性能,确保游戏的流畅运行和AI的快速响应。 总结来说,游戏101作业5是一个关于游戏AI设计与实现的任务。通过设计游戏世界、制定规则和目标以及实现AI策略,我们可以创建一个具有挑战性和可玩性的实时策略游戏。通过编程语言的实现,我们可以使游戏AI能够根据当前情况做出明智的决策。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值