闫令琪:Games101 现代计算机图形学-作业Assignment03解析

本文目录

    • 1. 作业框架梳理
        • a. 对3D模型的处理:
        • b. 对三角形的坐标变换处理
        • c.shader设置
        • d.光栅化
    • 2.布林冯shader的注意事项
        • a.向量的具体含义
        • b.不同分量中颜色
        • c.代码框架中的观测点坐标疑错误
        • d.具体代码
    • 3.凹凸贴图
        • a.凹凸贴图为什么基本都是蓝色
        • b.贴图中的数据怎么映射到3D空间
        • c.具体代码

本次作业的核心任务是:

  1. 完成布林冯模型的shader
  2. 完成凹凸贴图的shader

对应的课程是第九讲、第八讲、第七讲。

1. 作业框架梳理

因为之前两次作业都是三角形,本次是有3D模型,作业框架在之前的基础上稍微增加了一些内容,其处理流程梳理如下:

a. 对3D模型的处理:

  • 专门的读取obj文件的类,读取3D文件
  • obj文件内部其实存储的是一个个三角形面,一个三角形的三个顶点作为一个元素,存到TriangleList数组里。
  • 接下来的所有的处理都是对这个数组元素的遍历处理,也就是一个三角形一个三角形的处理

b. 对三角形的坐标变换处理

  • 仅仅对三个顶点进行旋转、平移、缩放和摄像机坐标系平移,不进行透视变换,3D模型本身的形状不变,仅仅是变换位置,为了后续再三角形内插值使用。并且后续shader中的光的方向向量、视线的方向向量等都是需要基于真实坐标计算。
  • 对顶点的坐标进行完整的坐标变换。该坐标目前仅在计算bounding box还有z-buffer时有用;
  • 然后设置法线,其中法线不经过透视变换
  • 对三个顶点设置颜色。这个颜色后续作用不大,会被贴图的颜色取代

c.shader设置

框架里列出了四种shader去完成,其中最重要的是布林冯shader还有凹凸贴图的shader。

  • shader里需要接收的数据是需要渲染的像素点,它的法向量、颜色、真实3D坐标等,这些数据被封装到一个结构体fragment_shader_payload作为函数输入。
  • 根据需要渲染的点的结构体里的数据,还有shader里给的灯光位置、强度,摄像机(即view坐标)可以求出布林冯模型所需要的各种向量,然后求出漫反射、高光、环境光三个分量,求和后返回颜色

d.光栅化

  • 根据透视变换后的三角形三个顶点求出bounding box,简化计算
  • 对bounding box的每个像素进行遍历,先进行插值的系数计算,求出z值,进行z-buffer计算
  • 对于需要绘制的像素点,利用上一步计算的插值系数,分别插值出颜色、法向量、UV坐标值、没有经过透视变换的真实3D坐标,初始化结构体fragment_shader_payload
  • fragment_shader_payload的数据传进shader里,即可求出该点的颜色。
  • 然后调用设置颜色的函数,渲染完成。

上面四个步骤中,前两个步骤不需要自己去做,第四个步骤前面的作业已经完成,因此本次作业的核心是完成shader的编写。

思考本流程,因为是对每一个三角形面都进行光栅化,因此貌似并不能并行?这只能cpu串行,那么怎么用gpu并行呢?希望以后来填这个坑。

2.布林冯shader的注意事项

具体的代码,该博客已经记录的比较完整GAMES101-现代计算机图形学学习笔记(作业03)。
此处记录一些自己犯过的易错点。

a.向量的具体含义

①图中所示的漫反射的光线向量、视线向量,一定注意,他们是跟光线、视线相反的方向!!都是物体表面指向光源和观测点。
②其次!这都是单位向量!!!后面求高光时要算半程向量,那个时候两个向量相加必须是单位向量。
③这个距离衰减,不能用向量l,因为它不能初始化。
闫令琪:Games101 现代计算机图形学-作业Assignment03解析_第1张图片

b.不同分量中颜色

高光和环境光都是全局不变的颜色,物体的颜色影响最大的是漫反射。如果有贴图,就是把贴图的颜色归一化(/255)后赋值为漫反射的系数Kd。

c.代码框架中的观测点坐标疑错误

光栅化代码中所使用的view_pos坐标其实在draw函数里已经经过view矩阵变换,也就是已经把相机移动原点了。所以按理说shader中的观察点应该是原点,但是代码中还是给的相机的初始位置。

d.具体代码

光栅化相关代码变成:

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) 
{
    auto v = t.toVector4();

	int x_min = (int)std::floor(std::min(v[0][0],std::min(v[1][0],v[2][0])));
	int y_min = (int)std::floor(std::min(v[0][1],std::min(v[1][1],v[2][1])));
	int x_max = (int)std::ceil(std::max(v[0][0],std::max(v[1][0],v[2][0])));
	int y_max = (int)std::ceil(std::max(v[0][1],std::max(v[1][1],v[2][1])));
	
	for (int x = x_min; x < x_max; ++x)
	{
		for (int y = y_min; y < y_max; ++y) 
		{
			if (insideTriangle( (float)x+0.5, (float)y+0.5, t.v))
			{
				auto result = computeBarycentric2D(x, y, t.v);
				float alpha;
				float beta;
				float gamma;
				std::tie(alpha,beta,gamma) = result;
                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;

				if(depth_buf[get_index(x,y)] > zp)
				{
					depth_buf[get_index(x,y)] = zp;

                    auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0],t.color[1],t.color[2],1);
                    //notice to do normalized
                    auto interpolated_normal = interpolate(alpha,beta,gamma,t.normal[0],t.normal[1],t.normal[2],1);
                    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);

                    fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
                    payload.view_pos = interpolated_shadingcoords;
                    //Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
                    auto pixel_color = fragment_shader(payload);

                    set_pixel(Eigen::Vector2i(x,y),pixel_color);
				}
			}
		}
	}
}

本段代码是纹理-布林冯模型,单纯的布林冯模型就只是把
texture_color变成一个固定颜色值而已。

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {
        // TODO: Get the texture value at the texture coordinates of the current fragment
        return_color = payload.texture->getColor(payload.tex_coords[0],payload.tex_coords[1]);
    }
    Eigen::Vector3f texture_color;
    texture_color << return_color.x(), return_color.y(), return_color.z();

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

    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)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        //光的方向
        Eigen::Vector3f light_direc = (light.position - point).normalized();
        //视线方向
        Eigen::Vector3f eye_direc = (eye_pos - point).normalized();
        //半程向量:视线方向和光线方向平均
        Eigen::Vector3f half_vector = (light_direc + eye_direc).normalized();
        //距离衰减因子
        float r2 = (light.position - point).squaredNorm();

        //环境光
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        //漫反射
        Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity / r2);
        diffuse *= std::max(0.0f, normal.dot(light_direc));
        //高光
        Eigen::Vector3f specular = ks.cwiseProduct(light.intensity / r2);
        specular *= std::pow(std::max(0.0f, normal.dot(half_vector)), p);


        result_color += (ambient + diffuse + specular);

    }

    return result_color * 255.f;
}

3.凹凸贴图

这部分其实课程中并没有详细讲述。bump_fragment_shader是把最后求得的法向量当成颜色,displacement_fragment_shader是在上面求出新的法向量的基础上应用了布林冯模型。
选择这两个shader时,main函数中都会默认读取凹凸贴图。

a.凹凸贴图为什么基本都是蓝色

凹凸向量是三个值,正好对应颜色RGB,所以可以放到一张图里。因为图里面只有UV坐标,跟3D模型怎么对应呢?
因为UV对应是物体的表面,也就是下图中所示的任意点都有一个表面坐标系称为TBN【tangant轴(T):切线轴;bitangent轴(B):副切线轴;及法线轴(N)】。
所以凹凸贴图里任意一(u,v)点的向量,都可以在物体表面放到该点的TBN坐标系中,凹凸贴图主要存储法向量,即N轴数据,所以在颜色空间中对应RGB中的B,也就是蓝色。

闫令琪:Games101 现代计算机图形学-作业Assignment03解析_第2张图片
从这个图可以更好地表示,T轴和B轴,和贴图中的UV坐标轴方向是一致的。
闫令琪:Games101 现代计算机图形学-作业Assignment03解析_第3张图片

b.贴图中的数据怎么映射到3D空间

shader数学基础之法线贴图切线空间这篇文章算是讲的还不错的。
Tutorial 13 : Normal Mapping这篇Opengl讲的也不错,但是……
感觉都跟代码框架里的对不上……
课程的BBS系统讨论区中,提供了一种快速计算TBN的方法。至少此方法计算的TBN三者是相互垂直的。作业框架里的三者都不是互相垂直的。
希望以后能填这个TBN的坑

c.具体代码

作业中要求的bump_fragment_shader代码,只不过是这个代码中不要布林冯部分的渲染,直接把normal向量当成颜色而已。

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

    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.2f, kn = 0.1f;
    
    // TODO: Implement displacement mapping here
    float x = normal.x();
    float y = normal.y();
    float z = normal.z();
    Eigen::Vector3f t(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / 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[0];
    float v = payload.tex_coords[1];
    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.0f);
    point += (kn * normal * payload.texture->getColor(u, v).norm());
    normal = (TBN * ln).normalized();

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

    for (auto& light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        //光的方向
        Eigen::Vector3f light_direc = (light.position - point).normalized();
        //视线方向
        Eigen::Vector3f eye_direc = (eye_pos - point).normalized();
        //半程向量:视线方向和光线方向平均
        Eigen::Vector3f half_vector = (light_direc + eye_direc).normalized();
        //距离衰减因子
        float r2 = (light.position - point).squaredNorm();

        //环境光
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        //漫反射
        Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity / r2);
        diffuse *= std::max(0.0f, normal.dot(light_direc));
        //高光
        Eigen::Vector3f specular = ks.cwiseProduct(light.intensity / r2);
        specular *= std::pow(std::max(0.0f, normal.dot(half_vector)), p);

        result_color += (ambient + diffuse + specular);
    }

    return result_color * 255.f;
}

你可能感兴趣的:(图形学)