GAMES101:作业3

2023-11-18

GAMES101:作业3

附其他所有作业超链接如下:
Games101 作业0:作业0
Games101 作业1:作业1
Games101 作业2:作业2
Games101 作业3:作业3
Games101 作业4:作业4
Games101 作业5:作业5
Games101 作业6:作业6
Games101 作业7:作业7

完整代码获取途径:
https://github.com/liupeining/Games_101_homework

照旧把这段代码粘贴过来:

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    float n = zNear;
    float f = zFar;
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
    float t = -tan((eye_fov/360)*MY_PI)*(abs(n)); //top
    float r = t/aspect_ratio;

    Eigen::Matrix4f Mp;//透视矩阵
    Mp << 
        n, 0, 0,   0,
        0, n, 0,   0,
        0, 0, n+f, -n*f,
        0, 0, 1,   0;
    Eigen::Matrix4f Mo_tran;//平移矩阵
    Mo_tran <<
        1, 0, 0, 0,
        0, 1, 0, 0,  //b=-t;
        0, 0, 1, -(n+f)/2 ,
        0, 0, 0, 1;
    Eigen::Matrix4f Mo_scale;//缩放矩阵
    Mo_scale << 
        1/r,     0,       0,       0,
        0,       1/t,     0,       0,
        0,       0,       2/(n-f), 0,
        0,       0,       0,       1;
    projection = (Mo_scale*Mo_tran)* Mp;//投影矩阵
    //这里一定要注意顺序,先透视再正交;正交里面先平移再缩放;否则做出来会是一条直线!
    return projection;
}

修改函数 rasterize_triangle(const Triangle& t) in rasterizer.cpp: 在此 处实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值。
下面三角形的rasterizer和上次的思路完全一致,这里仍然沿用了MSAA;
大部分都和上次的代码相同,更改以及需要注意的地方均在代码中做了注释。
其实区别就在于添加了用重心坐标各种插值最终返回一个color

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) 
{
    auto v = t.toVector4(); //v[0],v[1],v[2]分别为三角形的三个顶点,是四维向量
    //比较三个顶点的横纵坐标,确定包围盒的边界并取整
    double min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
    double max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
    double min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
    double max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
    min_x = static_cast<int>(std::floor(min_x));
    min_y = static_cast<int>(std::floor(min_y));
    max_x = static_cast<int>(std::ceil(max_x));
    max_y = static_cast<int>(std::ceil(max_y));
    //此处实现的是MSAA
    std::vector<Eigen::Vector2f> pos
    {                               //对一个像素分割四份 当然你还可以分成4x4 8x8等等甚至你还可以为了某种特殊情况设计成不规则的图形来分割单元
        {0.25,0.25},                //左下
        {0.75,0.25},                //右下
        {0.25,0.75},                //左上
        {0.75,0.75}                 //右上
    };
    for (int i = min_x; i <= max_x; ++i)
    {
        for (int j = min_y; j <= max_y; ++j)
        {
            int count = 0; //开始遍历四个小格子,获得平均值
            for (int MSAA_4 = 0; MSAA_4 < 4; ++MSAA_4)
            {
                if (insideTriangle(static_cast<float>(i+pos[MSAA_4][0]), static_cast<float>(j+pos[MSAA_4][1]),t.v))
                    ++count;
            }
            if(count) //至少有一个小格子在三角形内
            {
                //此处是框架中代码,获得z,见原程序注释:
                //    * v[i].w() is the vertex view space depth value z.
                //    * Z is interpolated view space depth for the current pixel
                //    * zp is depth between zNear and zFar, used for z-buffer
                auto[alpha, beta, gamma] = computeBarycentric2D(i + 0.5, j + 0.5, t.v);
                float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
                float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
                zp *= Z;
                //end
                if (depth_buf[get_index(i, j)] > zp)
                {
                    depth_buf[get_index(i, j)] = zp;//更新深度
                    //这里注意,虽然说明上说"反转了z,保证都是正数,并且越大表示离视点越远",
                    //但经过我的查看,实际上并没有反转,因此还是按照-z近大远小来做,当然也可以在上面补一个负号不过没必要

                    //利用重心坐标插值各种值
					auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);
					auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1).normalized();
					auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
					auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);
                    //shadingcoords是由view_pos插值得到,也就是物体表面的点在相机坐标系的位置。他们会在shader中被用到,来计算光照等信息。

                    //此处是框架中代码,获得z,见原程序注释:
					fragment_shader_payload payload(interpolated_color, interpolated_normal, interpolated_texcoords, texture ? &*texture : nullptr);
					payload.view_pos = interpolated_shadingcoords;
					auto pixel_color = fragment_shader(payload);
                    //end

					// 设置颜色
					set_pixel(Eigen::Vector2i(i, j), pixel_color * (count / 4.0));
                }
            }
        }
    }
}

此时你可以运行./Rasterizer output.png normal 来观察法向量实现结果。
结果:
在这里插入图片描述
下面就是修改函数 phong_fragment_shader() in main.cpp: 实现 Blinn-Phong 模型计算 Fragment Color.
这里要添加的地方就是一个for循环,每个点用一下Ld,Ls,La那仨公式就行。
无脑对着公式敲,非常简单。
循环内代码:

    for (auto& light : lights)
    {
        Eigen::Vector3f l = (light.position - point).normalized();      // 光
		Eigen::Vector3f v = (eye_pos - point).normalized();		        // 眼
        Eigen::Vector3f h = (l + v).normalized();                       // 半程向量
	    double r_2 = (light.position - point).dot(light.position - point);
        Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l));    //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
        Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
		result_color += (Ld + Ls);   
    }
    Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
    result_color += La; 

另附一下整个函数的代码:

Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector<light> lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;

    Eigen::Vector3f result_color = {0, 0, 0};
    for (auto& light : lights)
    {
        Eigen::Vector3f l = (light.position - point).normalized();      // 光
		Eigen::Vector3f v = (eye_pos - point).normalized();		        // 眼
        Eigen::Vector3f h = (l + v).normalized();                       // 半程向量
	    double r_2 = (light.position - point).dot(light.position - point);
        Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l));    //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
        Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
		result_color += (Ld + Ls);   
    }
    Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
    result_color += La; 
    //这里注意一下.cwiseProduct和.dot的用法。
    return result_color * 255.f;
}

结果:
在这里插入图片描述
与作业说明中的例图完全一致。

接下来就是修改函数 texture_fragment_shader() in main.cpp: 在实现 Blinn-Phong的基础上,将纹理颜色视为公式中的 kd,实现 Texture Shading Fragment Shader.

这里要写一个if和一个for。if里写一句话:

    if (payload.texture)
    {   //payload是之前返回的一个结构体,texture是payload的成员,是个类,getcolor是公有函数,接收两个float,u,v坐标
        return_color = payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()); 
    }

for里写的东西和之前完全一样:

        Eigen::Vector3f l = (light.position - point).normalized();      // 光
		Eigen::Vector3f v = (eye_pos - point).normalized();		        // 眼
        Eigen::Vector3f h = (l + v).normalized();                       // 半程向量
	    double r_2 = (light.position - point).dot(light.position - point);
        Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l));    //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
        Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
        Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
		result_color += (La + Ld + Ls);     

附个完整的函数:

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {   //payload是之前返回的一个结构体,texture是payload的成员,是个类,getcolor是公有函数,接收两个float,u,v坐标
        return_color = payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()); 
    }
    Eigen::Vector3f texture_color;
    texture_color << return_color.x(), return_color.y(), return_color.z();

    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = texture_color / 255.f;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector<light> lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = texture_color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;

    Eigen::Vector3f result_color = {0, 0, 0};

    for (auto& light : lights)
    {
        Eigen::Vector3f l = (light.position - point).normalized();      // 光
		Eigen::Vector3f v = (eye_pos - point).normalized();		        // 眼
        Eigen::Vector3f h = (l + v).normalized();                       // 半程向量
	    double r_2 = (light.position - point).dot(light.position - point);
        Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l));    //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
        Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
		result_color += (Ld + Ls);   
    }
    Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
    result_color += La; 
    return result_color * 255.f;
}

结果:
在这里插入图片描述
与作业说明中的例图完全一致。

接下来,修改函数 bump_fragment_shader() in main.cpp: 在实现 Blinn-Phong 的基础上,仔细阅读该函数中的注释,实现 Bump mapping.
这里就是凹凸贴图的实现,主要思路就是实现贴图对法线的扰动(计算导数)
这里框架里给了非常详细的注释,基本上约等于帮你把这部分写了,照着注释抄一下就好
注意的地方仍然注释了。

TODO后需要自行添加的代码:

    // TODO: Implement bump mapping here 按照todo写:
    // 这个地方是为了后面用局部坐标系,让n始终朝向001,课上讲过
	float x = normal.x();
	float y = normal.y();
	float z = normal.z();
	Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
	Eigen::Vector3f b = normal.cross(t);
	Eigen::Matrix3f TBN;
	TBN <<  t.x(), b.x(), normal.x(),
		    t.y(), b.y(), normal.y(),
		    t.z(), b.z(), normal.z();
	float u = payload.tex_coords.x();
	float v = payload.tex_coords.y();
	float w = payload.texture->width;
	float h = payload.texture->height;
	float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
	float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
	Eigen::Vector3f ln{-dU, -dV, 1};
	Eigen::Vector3f result_color = (TBN * ln).normalized();
    return result_color * 255.f;

完整函数代码:

Eigen::Vector3f bump_fragment_shader(const fragment_shader_payload& payload)
{
    
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector<light> lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = payload.color; 
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;


    float kh = 0.2, kn = 0.1;

    // TODO: Implement bump mapping here 按照todo写:
    // 这个地方是为了后面用局部坐标系,让n始终朝向001,课上讲过
	float x = normal.x();
	float y = normal.y();
	float z = normal.z();
	Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
	Eigen::Vector3f b = normal.cross(t);
	Eigen::Matrix3f TBN;
	TBN <<  t.x(), b.x(), normal.x(),
		    t.y(), b.y(), normal.y(),
		    t.z(), b.z(), normal.z();
	float u = payload.tex_coords.x();
	float v = payload.tex_coords.y();
	float w = payload.texture->width;
	float h = payload.texture->height;
	float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
	float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
	Eigen::Vector3f ln{-dU, -dV, 1};
	Eigen::Vector3f result_color = (TBN * ln).normalized();
    return result_color * 255.f;
}

结果:
在这里插入图片描述
下面就是最后一项任务:
修改函数 displacement_fragment_shader() in main.cpp: 在实现 Bump mapping 的基础上,实现 displacement mapping.
这里实现了位移贴图,并加上了光照。和之前的区别就在于一句话:

point += (kn * normal * payload.texture->getColor(u , v).norm());

真正改变了点的高度。

完整代码如下:

Eigen::Vector3f displacement_fragment_shader(const fragment_shader_payload& payload)
{
    
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector<light> lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = payload.color; 
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;

    float kh = 0.2, kn = 0.1;
    
    float x = normal.x();
	float y = normal.y();
	float z = normal.z();
	Eigen::Vector3f t{ x * y / std::sqrt(x * x + z * z), std::sqrt(x * x + z * z), z*y / std::sqrt(x * x + z * z) };
	Eigen::Vector3f b = normal.cross(t);
	Eigen::Matrix3f TBN;
	TBN <<  t.x(), b.x(), normal.x(),
		    t.y(), b.y(), normal.y(),
		    t.z(), b.z(), normal.z();
	float u = payload.tex_coords.x();
	float v = payload.tex_coords.y();
	float w = payload.texture->width;
	float h = payload.texture->height;
	float dU = kh * kn * (payload.texture->getColor(u + 1 / w , v).norm() - payload.texture->getColor(u, v).norm());
	float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
	Eigen::Vector3f ln{-dU, -dV, 1};
    //与凹凸贴图的区别就在于这句话
    point += (kn * normal * payload.texture->getColor(u , v).norm());
	normal = (TBN * ln).normalized();
    Eigen::Vector3f result_color = {0, 0, 0};

    for (auto& light : lights)
    {
        Eigen::Vector3f l = (light.position - point).normalized();      // 光
		Eigen::Vector3f v = (eye_pos - point).normalized();		        // 眼
        Eigen::Vector3f h = (l + v).normalized();                       // 半程向量
	    double r_2 = (light.position - point).dot(light.position - point);
        Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r_2) * std::max(0.0f, normal.dot(l));    //cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array。
        Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r_2) * std::pow(std::max(0.0f, normal.dot(h)), p);
		result_color += (Ld + Ls);   
    }
    Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
    result_color += La; 

    return result_color * 255.f;
}

结果:
注:看到很多其他博主做出来的图实际上要比说明里的图颜色浅,问题就在于把环境光照写在了循环里。
下面做一个对比:
环境光照写在外面:和说明完全一致
在这里插入图片描述
环境光照写在循环里:
在这里插入图片描述
腿部等有非常明显的区别。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GAMES101:作业3 的相关文章

随机推荐

  • R语言与机器学习中的回归方法学习笔记

    来源 http blog sina com cn s blog 62b37bfe0101hom5 html key word lars rpart randomForest cp svm data diabetes prune boosti
  • BES系列蓝牙开发总结

    博文索引 框架 BES2300X BES2500X 框架解析 一 UI及外围功能模块 BES2300X BES2500X UI 按键 提示音 指示灯 BES2300X BES2500X UI 电池管理模块 蓝牙操作及协议连接 数据流及数据编
  • 使用jq实现手机上的touchmove效果

    Touch事件简介 pc上的web页面鼠 标会产生onmousedown onmouseup onmouseout onmouseover onmousemove的事件 但是在移动终端如 iphone ipod Touch ipad上的we
  • python pycharts模块词云

    from pyecharts import WordCloud import pandas as pd 读取文件 data pd read csv word csv names word count sep data data groupb
  • Java连接Mysql数据库详细代码

    数据库连接类 package util import java sql TODO author date 2020 12 10 9 07 public class DBUtil private final static String DRI
  • n个学生围成圈,报到3的学生被淘汰,最后剩下的学生编号是几号?

    include
  • Python的数据类型——字典(dict)

    目录 1 Python的数据类型 2 什么是字典 3 字典存储的数据 4 字典的语法 5 新建字典 1 用 新建一个空字典 2 新建一个存储城
  • list转json字符串

    使用Gson把list转成json字符串 com google gson Gson GetMapping valueTest public String valueTest List
  • 2021-08-03PHP面试笔试题记录

    1 一张表中有id pid name三个字段 用来表示无限级联动 pid表示父级id 如无父级 则pid为0 现已将表中数据全部查出 请封装函数 实现将该数据转换成树状结构 原始数据 menu datas id gt 1 pid gt 0
  • RGB三原色的简单理解

    RGB是什么 RGB是一种颜色表示系统 它由国际照明委员会 EIC 定义 选择红色 波长 700 00nm 绿色 波长 546 1nm 蓝色 波长 438 8nm 三种单色光作为表色系统的三基色 其中R red G Green B Blue
  • SpringSecurity学习笔记(七)密码加密

    参考视频 编程不良人 为什么要进行密码加密 如果密码直接存储到数据库不进行加密 一旦被黑客攻破就会导致用户的密码泄露 而且一般用户的密码是多个网站或者app用的同一个 这就导致了很大的安全隐患 所以一般数据库都不会直接存储用户的明文密码 都
  • java类型转换小细节之BigDecimal转String

    public static void main String args 浮点数的打印 System out println new BigDecimal 10000000000 toString 普通的数字字符串 System out pr
  • linux配置虚拟IP地址方法

    linux配置虚拟IP地址方法 在日常linux管理工作中 需要为应用配置单独的IP地址 以达到主机与应用的分离 在应用切换与迁移过程中可以做到动态切换 特别是在使用HA的时候 这种方案可以保证主机与应用的隔离 对日常的运维有很大的益处 但
  • DDL与DML的区别

    DML Data Manipulation Language 数据操纵语言 适用范围 对数据库中的数据进行一些简单操作 如insert delete update select等 DDL Data Definition Language 数
  • 如何排查 Electron V8 引发的内存 OOM 问题

    经过长达大半年时间的崩溃治理后 基于 Electron 框架开发的新版 PC 淘宝直播推流客户端的稳定性终于赶超基于QT 框架开发的旧版本了 剩下的崩溃问题中有 40 是跟内存 OOM 有关 其中 V8FatalErrorCallback
  • 堆栈常量池

    堆栈常量池详解 例子 转自 http www iteye com topic 634530 一 概述 寄存器 最快的存储区 由编译器根据需求进行分配 我们在程序中无法控制 栈 stack 存放基本类型的变量数据和对象的引用 但对象本身不存放
  • 在myeclipse拷贝项目时候经常遇到的问题

    我们有事为了省事 在myeclipse中拷贝一个web项目 然后复制下来 改改里面的内容就直接放到tomcat中运行 会发现找不到路径 然而地址栏什么的都正确 这是为什么那 仔细找找你会发现原来是 这个项目的入口没有改 你用的还是上一个项目
  • m3u8视频下载器

    文章目录 前言 一 获取网站的m3u8文件url 二 使用步骤 1 修改配置文件 2 运行py或者exe 总结 前言 有的时候看个视频太卡了 就想把视频搞下来 一些网站吧 它不让下载 而且还是ts流视频 于是就做了个m3u8视频下载器第一版
  • SQL如何分析用户复购?(复购率、表连接)

    题目 表名为 购买记录表 里记录某在线教育平台的用户购买记录 包含字段 用户id 购买时间 课程类型 消费金额 问题 分析出每日首次购买用户的次月 第三月 第四月复购情况如何 解题思路 1 群组分析方法 这类复购问题的取数方式是群组分析方法
  • GAMES101:作业3

    GAMES101 作业3 附其他所有作业超链接如下 Games101 作业0 作业0 Games101 作业1 作业1 Games101 作业2 作业2 Games101 作业3 作业3 Games101 作业4 作业4 Games101