GAMES101:作业5记录

总览

在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就可以执行着色并返回像素颜色。在这次作业中,我们需要实现两个部分:光线的生成和光线与三角的相交。本次代码框架的工作流程为:

  1. main 函数开始。我们定义场景的参数,添加物体(球体或三角形)到场景中,并设置其材质,然后将光源添加到场景中。

  2. 调用 Render(scene) 函数。在遍历所有像素的循环里,生成对应的光线并将返回的颜色保存在帧缓冲区(framebuffer)中。在渲染过程结束后,帧缓冲区中的信息将被保存为图像。

  3. 在生成像素对应的光线后,我们调用 CastRay 函数,该函数调用 trace 来查询光线与场景中最近的对象的交点。

  4. 然后,我们在此交点执行着色。我们设置了三种不同的着色情况,并且已经为你提供了代码。

你需要修改的函数是:
Renderer.cpp 中的 Render():这里你需要为每个像素生成一条对应的光线,然后调用函数 castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相应像素中。

Triangle.hpp 中的 rayTriangleIntersect(): v0, v1, v2 是三角形的三个顶点,orig 是光线的起点,dir 是光线单位化的方向向量。tnear, u, v 是你需要使用我们课上推导的 Moller-Trumbore 算法来更新的参数。

实现

Renderer.cpp->Render()的实现

建议先把这个文章看一下:

Ray-Tracing: Generating Camera Rays

在作业3里我们的坐标变换是正向的,从世界坐标到相机坐标到NDC坐标到渲染的像素坐标,而在光线追踪里我们要把光线从相机原点发射光线到屏幕上的每一个像素(知道的是像素坐标),我们需要做逆向的操作从渲染的像素坐标(注意左上角为原点)到NDC坐标到世界坐标,因为我们计算交点比较深度都是在世界坐标系里计算的。

在这里插入图片描述

  • 渲染像素坐标->NDC坐标([0,1]的正方形)
    在这里插入图片描述
  • NDC坐标->屏幕像素坐标([-1,1]的正方形【原点在左下角】)

在这里插入图片描述
因为原点在左上角,所以第二个式子需要乘一个负号:

在这里插入图片描述

我们的渲染图像的纵横比不一定是1,考虑到这一点我们需要对公式进行校正(这样使得每个分隔的方格都是正方形)

在这里插入图片描述

  • 屏幕像素坐标->相机坐标
    这里第二行和第三行右边应该是 P i x e l N D C x PixelNDC_x PixelNDCx P i x e l N D C y PixelNDC_y PixelNDCy
    在这里插入图片描述
    我们把相机放在原点,相机朝z轴负向看物体,然后渲染图像的平面(z=n)离原点的距离是1:

在这里插入图片描述
由几何关系

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以相机空间的位置为:

在这里插入图片描述
这里相机坐标系是和世界坐标系是重合的,如果要再进一步到世界坐标系我们还需要进行一个矩阵的映射

在这里插入图片描述
在本作业中,我们使用的公式整合一下即:

P i x e l C a m e r a x = ( 2 ∗ P i x e l x + 0.5 I m a g e W i d t h − 1 ) ∗ I m a g e A s p e c t R a t i o ∗ t a n ( α / 2 ) P I x e l C a m e r a y = ( 1 − 2 ∗ P i x e l y + 0.5 I m a g e H e i g h t ) ∗ t a n ( α / 2 ) PixelCamera_x = \left(2*\frac{Pixel_x + 0.5}{ImageWidth}-1\right)*ImageAspectRatio*tan(\alpha/2)\\ PIxelCamera_y = \left(1 - 2 * \frac{Pixel_y + 0.5}{ImageHeight}\right) * tan(\alpha/2) PixelCamerax=(2ImageWidthPixelx+0.51)ImageAspectRatiotan(α/2)PIxelCameray=(12ImageHeightPixely+0.5)tan(α/2)

这样我们就可以写出我们的代码了,一个是修改x和y,一个是对dir使用normalize归一化:

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;

    // Use this variable as the eye position to start your rays.
    Vector3f eye_pos(0);
    int m = 0;
    for (int j = 0; j < scene.height; ++j)
    {
        for (int i = 0; i < scene.width; ++i)
        {
            // generate primary ray direction
            float x = (2 * ((float)i + 0.5) / scene.width - 1) * scale * imageAspectRatio;
            float y = (1 - 2 * ((float)j + 0.5) / scene.height) * scale;
            // TODO: Find the x and y positions of the current pixel to get the direction
            // vector that passes through it.
            // Also, don't forget to multiply both of them with the variable *scale*, and
            // x (horizontal) variable with the *imageAspectRatio*            
            
            Vector3f dir = normalize(Vector3f(x, y, -1)); // Don't forget to normalize this direction!
            framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
        }
        UpdateProgress(j / (float)scene.height);
    }

    // save framebuffer to file
    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);    
}

Triangle.cpp->rayTriangleIntersect()的实现

详细的推导可以看:Möller-Trumbore algorithm

在这里插入图片描述
按照公式写代码即可,这里使用了Vector.hpp的dotProductcrossProduct函数,判断相交的条件是射线的t大于等于0且和三角形平面的三个重心插值坐标也都大于等于0:

bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
                          const Vector3f& dir, float& tnear, float& u, float& v)
{
    // TODO: Implement this function that tests whether the triangle
    // that's specified bt v0, v1 and v2 intersects with the ray (whose
    // origin is *orig* and direction is *dir*)
    // Also don't forget to update tnear, u and v.
    Vector3f E1 = v1 - v0;
    Vector3f E2 = v2 - v0;
    Vector3f S = orig - v0;
    Vector3f S1 = crossProduct(dir, E2);
    Vector3f S2 = crossProduct(S, E1);
    if (dotProduct(S1, E1) == 0)
        return false;
    tnear = dotProduct(S2, E2) / dotProduct(S1, E1);
    u = dotProduct(S1, S) / dotProduct(S1, E1);
    v = dotProduct(S2, dir) / dotProduct(S1, E1);
    if (tnear >= 0 && u >= 0 && v >= 0 && (1 - u - v) >= 0)
        return true;

    return false;
}

效果

在这里插入图片描述

其他代码批注

Renderer.cpp->castRay()

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);
    }//深度大于最大深度,这里的maxDepth是5,返回黑色

    Vector3f hitColor = scene.backgroundColor;//设置hitColor是背景色,这里是Vector3f(0.235294, 0.67451, 0.843137)
    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));//折射方向
                //为避免浮点误差反射和折射的作用点是hitPoint加减一个小的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;
                //递归找到反射的颜色
                Vector3f reflectionColor = castRay(reflectionRayOrig, reflectionDirection, scene, depth + 1);
                //递归找到折射的颜色
                Vector3f refractionColor = castRay(refractionRayOrig, refractionDirection, scene, depth + 1);
                //使用fresnel公式计算反射率
                float kr = fresnel(dir, N, payload->hit_obj->ior);
                //颜色是反射和折射的颜色的加权和
                hitColor = reflectionColor * kr + refractionColor * (1 - kr);
                break;
            }
            case REFLECTION://考虑反射,没有折射
            {
                float kr = fresnel(dir, N, payload->hit_obj->ior);
                Vector3f reflectionDirection = reflect(dir, N);
                Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                                             hitPoint + N * scene.epsilon :
                                             hitPoint - N * scene.epsilon;
                hitColor = castRay(reflectionRayOrig, reflectionDirection, scene, depth + 1) * kr;
                break;
            }
            default:
            {
                // [comment]
                // We use the Phong illumation model int the default case. The phong model
                // is composed of a diffuse and a specular reflection component.
                // [/comment]
                // 设置环境光和镜面反射光
                Vector3f lightAmt = 0, specularColor = 0;
                //相机发出的dir作用线在物体外部,阴影的作用点移动到物体交点的外部,反之移动到内部
                Vector3f shadowPointOrig = (dotProduct(dir, N) < 0) ?
                                           hitPoint + N * scene.epsilon :
                                           hitPoint - N * scene.epsilon;
                // [comment]
                // Loop over all lights in the scene and sum their contribution up
                // We also apply the lambert cosine law
                // [/comment]
                for (auto& light : scene.get_lights()) {
                	//从光源和物体的交点指向光源的矢量
                    Vector3f lightDir = light->position - hitPoint;
                    // square of the distance between hitPoint and the light:光源和物体的交点指向光源的矢量的长度
                    float lightDistance2 = dotProduct(lightDir, lightDir);
                    //归一化
                    lightDir = normalize(lightDir);
                    float LdotN = std::max(0.f, dotProduct(lightDir, N));
                    // is the point in shadow, and is the nearest occluding object closer to the object than the light itself?
                    //判断光源和物体是否相交(是否有遮挡)
                    auto shadow_res = trace(shadowPointOrig, lightDir, scene.get_objects());
                    //有遮挡且【阴影点和遮挡物交点的距离(变化了一个小epsilon)】小于【光源和物体交点的距离(没有变化一个xnepsilon)】
                    bool inShadow = shadow_res && (shadow_res->tNear * shadow_res->tNear < lightDistance2);
					// 如果没有阴影说明光打得到这个物体,加上散射光项(Phong模型),但是没有除距离的平方
                    lightAmt += inShadow ? 0 : light->intensity * LdotN;
                    //计算反射方向
                    Vector3f reflectionDirection = reflect(-lightDir, N);
					//计算镜面反射
                    specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, dir)),
                        payload->hit_obj->specularExponent) * light->intensity;
                }
				//最终的颜色好药乘系数,环境光要乘payload->hit_obj->evalDiffuseColor(st)为0.2
                hitColor = lightAmt * payload->hit_obj->evalDiffuseColor(st) * payload->hit_obj->Kd + specularColor * payload->hit_obj->Ks;
                break;
            }
        }
    }

    return hitColor;
}

Renderer.cpp->reflect(), Renderer.cpp->rrefract(), Renderer.cpp->fresnel()

这几个函数涉及光的折射和反射,推导见:

计算机图形学十二:Whitted-Style光线追踪原理详解及实现细节-4 反射与折射
在这里插入图片描述

相关代码为:

// 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)
{
    float cosi = clamp(-1, 1, dotProduct(I, N));
    float etai = 1, etat = ior;
    Vector3f n = N;
    if (cosi < 0) { cosi = -cosi; } else { std::swap(etai, etat); n= -N; }
    float eta = etai / etat;
    float k = 1 - eta * eta * (1 - cosi * cosi);
    return k < 0 ? 0 : eta * I + (eta * cosi - sqrtf(k)) * n;
}

fresnel公式计算反射率,

在这里插入图片描述

// [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)
{
    float cosi = clamp(-1, 1, dotProduct(I, N));
    float etai = 1, etat = ior;
    if (cosi > 0) {  std::swap(etai, etat); }
    // Compute sini using Snell's law
    float sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi));
    // Total internal reflection,全反射
    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;
}

Renderer.cpp->trace()

std::optional返回一个hit_payload类型,这里的初始化是空,如果找到最近的相交的物体则返回一个有效的hit_payload的值,关于std::optional详情见:C++17之std::optional全方位详解

// [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]
std::optional<hit_payload> trace(
        const Vector3f &orig, const Vector3f &dir,
        const std::vector<std::unique_ptr<Object> > &objects)
{
    float tNear = kInfinity;
    std::optional<hit_payload> payload;
    for (const auto & object : objects)
    {
        float tNearK = kInfinity;
        uint32_t indexK;
        Vector2f uvK;
        if (object->intersect(orig, dir, tNearK, indexK, uvK) && tNearK < tNear)//寻找最近的物体然后让返回值有效
        {
            payload.emplace();
            payload->hit_obj = object.get();
            payload->tNear = tNearK;
            payload->index = indexK;
            payload->uv = uvK;
            tNear = tNearK;
        }
    }

    return payload;
}

Sphere.hpp->intersect()

判断是否和所有球相交

bool intersect(const Vector3f& orig, const Vector3f& dir, float& tnear, uint32_t&, Vector2f&) const override
    {
        // analytic solution
        Vector3f L = orig - center;
        float a = dotProduct(dir, dir);
        float b = 2 * dotProduct(dir, L);
        float c = dotProduct(L, L) - radius2;
        float t0, t1;
        if (!solveQuadratic(a, b, c, t0, t1))//解一元二次方程
            return false;
        if (t0 < 0)
            t0 = t1;
        if (t0 < 0)
            return false;
        tnear = t0;

        return true;
    }

Triangle.hpp->intersect()

判断是否和所有三角形相交

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;
    }

SPhere.hpp->getSurfaceProperties()

    void getSurfaceProperties(const Vector3f& P, const Vector3f&, const uint32_t&, const Vector2f&,
                              Vector3f& N, Vector2f&) const override
    {
        N = normalize(P - center);//获得法线
    }

Triangle.hpp->getSurfaceProperties()

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));
        //st坐标的插值
        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;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/293369.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】stdbool.h——有关bool的总结

在编程和日常生活中&#xff0c;经常需要一种只能具有两个值之一的数据类型&#xff0c;如是否、开关、真假等&#xff0c;因此&#xff0c;C 有一种bool数据类型&#xff0c;称为booleans。布尔值表示 或true的值false。 C 中的 bool 是大多数语言中的基本数据类型&#xff0…

自由DIY预约小程序源码系统:适用于任何行业+自由DIY你的界面布局+全新升级的UI+多用户系统 带安装部署教程

随着移动互联网的普及&#xff0c;预约服务逐渐成为人们日常生活的一部分。从家政服务、医疗挂号到汽车保养&#xff0c;预约已经渗透到各个行业。然而&#xff0c;市面上的预约小程序大多功能单一&#xff0c;界面老旧&#xff0c;无法满足商家和用户的个性化需求。今天来给大…

03 decision tree(决策树)

一、decision tree&#xff08;决策树&#xff09; 1. classification problems&#xff08;纯度&#xff09; i . entropy &#xff08;熵&#xff09; ​ 作用&#xff1a;衡量一组数据的纯度是否很纯 &#xff0c;当五五开时他的熵都是最高的&#xff0c;当全是或者都不是…

fastadmin 安装QueryList

之前的项目复制过来&#xff0c;抓取数据的时候报这个错&#xff0c;于是我准备重新安装QueryList 我准备安装QueryList 4版本 安装命令&#xff1a; composer require jaeger/querylist:~V4 报错&#xff1a; 解决方法&#xff1a; 创建一个新用户和用户组&#xff0c;切换…

vivado 管理宏

管理宏 宏存储为XDC约束。根据定义&#xff0c;它们是Tcl命令。这允许要在XDC约束文件和Tcl脚本中使用并交互使用的宏。宏是使用write_xdc命令编写的。使用read_xdc读取宏命令-cell选项可用于将作用域限制为特定的单元格。-cell选项特别适用于将一个宏的相对放置应用到不同层次…

electron预加载脚本

webPreferences 指定预加载脚本,可以使用部分node脚本 webPreferences: {preload: path.join(__dirname, "preload.js"),},创建preload.js 中 测试文件读取功能 const fs require(fs) const text fs.readFileSync(package.json, utf-8)console.log(text)报错,为了…

Visual Studio 2013 “即将退休”

新年快乐&#xff01; 这也是向各位开发者提醒 Visual Studio 支持生命周期中即将到来的好时机。 对 Visual Studio 2013 的支持即将在今年(2024年)的4月9日结束。如果你正在使用旧版本的 Visual Studio&#xff0c;我们强烈建议您升级您的开发环境到最新的 Visual Studio 20…

给孩子选台灯什么样的好?分享高品质的学生护眼台灯!

在这个青少年如此高近视率的情况下&#xff0c;想要真正保护孩子的视力&#xff0c;除了需要监督孩子养成良好的用眼习惯以外&#xff0c;还要注意光线环境&#xff01;在夜晚不管是看书写字、还是使用电脑平板上网课&#xff0c;都需要一个健康的照明环境。因此在挑选孩子学习…

Electron介绍

前言 相信很多的前端小伙伴都想过一个问题&#xff0c;web技术是否可以用于开发桌面应用。答案当然是可以的&#xff0c;Electron框架就是其中的一种解决方案。 Electron介绍 Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 Electron 并不是一门新的…

.NetCore部署微服务(一)

目录 前言 什么是微服务 微服务的优势 微服务的原则 创建项目 在Docker中运行服务 客户端调用 简单的集群服务 前言 写这篇文章旨在用最简单的代码阐述一下微服务 什么是微服务 微服务描述了从单独可部署的服务构建分布式应用程序的体系结构流程&#xff0c;同时这些…

Redis 连接 命令

目录 1.Redis Echo 命令 - 打印字符串简介语法可用版本: > 1.0.0返回值: 返回字符串本身。 示例 2.Redis Select 命令 - 切换到指定的数据库简介语法可用版本: > 1.0.0返回值: 总是返回 OK 。 示例 3.Redis Ping 命令 - 查看服务是否运行简介语法可用版本: > 1.0.0返回…

【Proteus仿真】【Arduino单片机】超声波测距系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用动态数码管、按键、HCSR04超声波、蜂鸣器模块等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管显示超声波检测距离&#xff0c;当检…

Apache网页优化

本章主要介绍如何对Apache网页进行优化 Apache 网页压缩Apache 网页缓存Apache 隐藏版本信息Apache 网页防盗链 目录 1、网页压缩与缓存 1.1、网页压缩 &#xff08;1&#xff09;gzip介绍 &#xff08;2&#xff09;HTTP压缩的过程 &#xff08;3&#xff09;Apache的…

Scikit-Learn线性回归(五)

Scikit-Learn线性回归五&#xff1a;岭回归与Lasso回归 1、误差与模型复杂度2、正则化3、Scikit-Learn岭&#xff08;Ridge&#xff09;回归4、Scikit-Learn Lasso回归 1、误差与模型复杂度 在第二篇文章 Scikit-Learn线性回归(二) 中&#xff0c;我们已经给出了过拟合与模型泛…

均匀与准均匀 B样条算法

B 样条曲线的定义 p ( t ) ∑ i 0 n P i F i , k ( t ) p(t) \sum_{i0}{n} P_i F_{i, k}(t) p(t)i0∑​nPi​Fi,k​(t) 方程中 n 1 n1 n1 个控制点&#xff0c; P i P_i Pi​, i 0 , 1 , ⋯ n i0, 1, \cdots n i0,1,⋯n 要用到 n 1 n1 n1 个 k k k 次 B 样条基函数 …

手游开发项目经验简单总结

这是我最近一个完整的手游开发项目的总结信息&#xff0c;请大家指点 目录 引擎 语言 编辑器 项目开发模块规划分 主项目工程&#xff0c;UI资源项目工程&#xff0c;模型场景资源项目工程 热更框架 前后端协议 UI 图集 多语言适配 SLG场景和其他场景 战斗 美术模型资源 人物…

微信怎么删除聊天记录?进来Get常用的操作方法!

微信是国内使用最广泛的社交应用程序之一。在我们使用微信的过程中&#xff0c;删除聊天记录是一个常见的操作。人们会删除一些不需要的聊天记录&#xff0c;以此减少存储空间的占用。微信怎么删除聊天记录&#xff1f;本文将介绍删除聊天记录的常用方法&#xff0c;非常简单&a…

Git保姆级安装教程

Git保姆级安装教程 一、去哪下载二、安装2.1 具体安装步骤2.2 设置全局用户签名 一、去哪下载 1、官网&#xff08;有最新版本&#xff09;&#xff1a;https://git-for-windows.github.io/ 2、本人学习时安装的版本&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1uAo…

被替换的文件怎么找回?3个高效方法分享!

“我在对电脑进行清理时&#xff0c;删除了一些比较重要的文件&#xff0c;为了恢复它们&#xff0c;我重新复制了一些文件&#xff0c;这导致我原先的文件被替换了&#xff0c;这怎么办呢&#xff1f;被替换的文件还能恢复吗&#xff1f;” 在信息化时代&#xff0c;用户的电脑…

Spark四:Spark Streaming和Structured Streaming

简介 Spark Streaming整体流程和DStream介绍 Structured Streaming发展历史和Dataflow模型介绍 Spark Streaming 是一个基于 Spark Core 之上的实时计算框架&#xff0c;从很多数据源消费数据并对数据进行实时的处理&#xff0c;具有高吞吐量和容错能力强等特点。 Spark Stre…