Games101-作业3(管线分析、深度插值、libpng warning、双线性插值等)

Games101-作业3(管线分析、深度插值、libpng warning、双线性插值等)

在前两次作业的基础上,添加光照模型和材质处理。
以下内容参考了论坛和博客。

渲染管线

image-20240321125845933

在Shading中,闫老师讲了图形管线的一系列操作,概括下来如下:

  1. Vertex Processing 点操作 :将三维空间上的点投影到二维
  2. Triangle Processing 三角形操作 :将三个点连成一个三角形/线,构造出一个平面/线,使得原三维空间物体变成由若干三角形组成的物体
  3. Raserization 光栅化:进行离散化的采样,用像素表示采样结果
  4. Fragment Processing 片元处理 :着色
  5. Framebuffer Operations片元缓冲操作:根据深度信息,确定遮挡和可视情况

下面我就按照管线的顺序,理一下整个流程,记录在注释里

1.主函数main()

int main(int argc, const char** argv)
{
    //记录组成三维图形的所有小三角形
    std::vector<Triangle*> TriangleList;

    float angle = 140.0;
    bool command_line = false;

    std::string filename = "output.png";
    objl::Loader Loader;
    std::string obj_path = "../models/spot/";

    // Load .obj File
    bool loadout = Loader.LoadFile("../models/spot/spot_triangulated_good.obj");
    for(auto mesh:Loader.LoadedMeshes)
    {
        //对于图形中的每个面(即一个小三角形)的三个点记录在一起
        for(int i=0;i<mesh.Vertices.size();i+=3)
        {
            //用Triangle类,记录一个小三角形的三个顶点的信息:
            //setVertex顶点位置,setNormal顶点的法线,setTexCoord顶点对应的纹理
            Triangle* t = new Triangle();
            for(int j=0;j<3;j++)
            {
                t->setVertex(j,Vector4f(mesh.Vertices[i+j].Position.X,mesh.Vertices[i+j].Position.Y,mesh.Vertices[i+j].Position.Z,1.0));
                t->setNormal(j,Vector3f(mesh.Vertices[i+j].Normal.X,mesh.Vertices[i+j].Normal.Y,mesh.Vertices[i+j].Normal.Z));
                t->setTexCoord(j,Vector2f(mesh.Vertices[i+j].TextureCoordinate.X, mesh.Vertices[i+j].TextureCoordinate.Y));
            }
            TriangleList.push_back(t);
        }
    }

    //初始化光栅化对象,定义屏幕长宽
    rst::rasterizer r(700, 700);


    //记录纹理到对象,注意rasterizer.hpp类有属性 optional<Texture> texture
    auto texture_path = "hmap.jpg";
    r.set_texture(Texture(obj_path + texture_path));

    //记录片元处理方式,类似于“赋值函数”,现默认方式是phong
    std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = phong_fragment_shader;


    //处理传入的参数,注意在此处根据调用方式不同,修改了rasterizer对象的片元处理方式!
    if (argc >= 2)
    {
        command_line = true;
        filename = std::string(argv[1]);

        if (argc == 3 && std::string(argv[2]) == "texture")
        {
            std::cout << "Rasterizing using the texture shader\n";
            active_shader = texture_fragment_shader;
            texture_path = "spot_texture.png";
            r.set_texture(Texture(obj_path + texture_path));
        }
        else if (argc == 3 && std::string(argv[2]) == "normal")
        {
            std::cout << "Rasterizing using the normal shader\n";
            active_shader = normal_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "phong")
        {
            std::cout << "Rasterizing using the phong shader\n";
            active_shader = phong_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "bump")
        {
            std::cout << "Rasterizing using the bump shader\n";
            active_shader = bump_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "displacement")
        {
            std::cout << "Rasterizing using the bump shader\n";
            active_shader = displacement_fragment_shader;
        }
    }

    //人眼所在位置
    Eigen::Vector3f eye_pos = {0,0,10};

    //设置顶点着色方式,获取顶点位置
    r.set_vertex_shader(vertex_shader);

    //设置片元着色方式,根据调用方式不同已赋值到active_shader
    r.set_fragment_shader(active_shader);

    //修改这个值,可以选择输出图片或动态旋转渲染的模型
    int key = 0;
    int frame_count = 0;

    if (command_line)
    {

        //清空两个缓冲区,在最后处理遮挡显示情况时发挥作用
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);

        //分别设置MVP矩阵,用于对点操作,实现将三维的点映射到平面
        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));


        // 应用变换矩阵 并进行光栅化、片元处理、帧缓冲
        r.draw(TriangleList);
        
        
        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);

        cv::imwrite(filename, image);

        return 0;
    }

    while(key != 27)
    {
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);

        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));

        //r.draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
        r.draw(TriangleList);
        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);

        cv::imshow("image", image);
        cv::imwrite(filename, image);
        key = cv::waitKey(10);

        if (key == 'a' )
        {
            angle -= 0.1;
        }
        else if (key == 'd')
        {
            angle += 0.1;
        }

    }
    return 0;
}

下面是整个main() 函数的大致流程:

①将顶点以三角形的方式每三个记录在一起。

②mvp变换(用于将三维的点变换到屏幕空间)。

③根据调用命令的不同设置光栅化器的着色方式,之后便进入了rasterizer的draw函数。

2.draw函数(顶点、三角形处理阶段)

void rst::rasterizer::draw(std::vector<Triangle*>& TriangleList) {

	float f1 = (50 - 0.1) / 2.0;// zfar和znear距离的一半
	float f2 = (50 + 0.1) / 2.0;// zfar和znear的中心z坐标

	Eigen::Matrix4f mvp = projection * view * model;// 计算MVP变换矩阵
	// 遍历每个小三角形
	for (const auto& t : TriangleList)
	{
		Triangle newtri = *t;

		// 计算viewspace_pos,其中viewspace_pos的坐标是经过MV变换,没有经过P投影变换
		// 所以默认在相机坐标系而不是世界坐标系

		// 记录三角形顶点MV变换后坐标
		std::array<Eigen::Vector4f, 3> mm{
				(view * model * t->v[0]),
				(view * model * t->v[1]),
				(view * model * t->v[2])
		};

		std::array<Eigen::Vector3f, 3> viewspace_pos;

		// 存入viewspace_pos
		std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
			return v.template head<3>();
			});

		// 得到经过mvp后的坐标
		Eigen::Vector4f v[] = {
				mvp * t->v[0],
				mvp * t->v[1],
				mvp * t->v[2]
		};

		// 换算齐次坐标
		for (auto& vec : v) {
			vec.x() /= vec.w();
			vec.y() /= vec.w();
			vec.z() /= vec.w();
		}

		// 计算在MV转换后各顶点的法向量
		// 利用原来点法向量推出MV变换后法向量
		// 因为光线作用是在view_space下进行的
		Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();
		Eigen::Vector4f n[] = {
				inv_trans * to_vec4(t->normal[0], 0.0f),
				inv_trans * to_vec4(t->normal[1], 0.0f),
				inv_trans * to_vec4(t->normal[2], 0.0f)
		};

		// 视口变换 得到顶点在屏幕上的坐标 即screen space
		for (auto& vert : v)
		{
			vert.x() = 0.5 * width * (vert.x() + 1.0);
			vert.y() = 0.5 * height * (vert.y() + 1.0);
			// 为了Zbuffer保留Z值
			// (透视)投影变换最后一步是从正交投影变换到正则立方体
			// 而这一步就是把正则立方体的z值还原到正交投影时的z值,即原始z值
			vert.z() = vert.z() * f1 + f2;
		}

		// 记录经过MVP视口变换后的顶点坐标
		// 完成顶点变换,变换到屏幕空间
		for (int i = 0; i < 3; ++i)
		{
			//screen space coordinates
			newtri.setVertex(i, v[i]);
		}

		// 记录顶点的法向量
		for (int i = 0; i < 3; ++i)
		{
			//view space normal
			newtri.setNormal(i, n[i].head<3>());
		}

		// 设置颜色
		newtri.setColor(0, 148, 121.0, 92.0);
		newtri.setColor(1, 148, 121.0, 92.0);
		newtri.setColor(2, 148, 121.0, 92.0);

		// 对这个小三角形进行光栅化
		// 传入viewspace_pos的坐标,光线的作用是在viewspace下的
		rasterize_triangle(newtri, viewspace_pos);
	}
}

概括一下:

  • 顶点进行MV操作,得到viewspace中的坐标viewspace_pos,这个坐标用于后续的光线作用。光线作用发生在视图空间。

  • 顶点进行MVP+视口操作,把三维空间顶点坐标换算到二维空间即screen space上。

    • 在其次变换中,w并未发生变换,它记录了原本的z值信息,用于后续的插值。
    • 视口变换中,将(x, y)变换到二维平面,而z值变为原始z值,用于后续Z-buffer算法。透视投影变换=变换到正交投影+变换到正则立方体,即把z值逆变换回正交投影时的z值。
  • 记录viewspace下的顶点法向量。因为光线作用在viewspace下。

光线作用发生在view space:

image-20240321132228156

image-20240321132251388

其中的难理解的变换解释如下
1.视口变换:
// 视口变换 得到顶点在屏幕上的坐标 即screen space
for (auto& vert : v)
{
	vert.x() = 0.5 * width * (vert.x() + 1.0);
	vert.y() = 0.5 * height * (vert.y() + 1.0);
	// 为了Zbuffer保留Z值
	// (透视)投影变换最后一步是从正交投影变换到正则立方体
	// 而这一步就是把正则立方体的z值还原到正交投影时的z值,即原始z值
	vert.z() = vert.z() * f1 + f2;
}

image-20240321132558315

image-20240321132705860

2.viewspace下的顶点法向量:
// 计算在MV转换后各顶点的法向量
// 利用原来点法向量推出MV变换后法向量
// 因为光线作用是在view_space下进行的
Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();
Eigen::Vector4f n[] = {
		inv_trans * to_vec4(t->normal[0], 0.0f),
		inv_trans * to_vec4(t->normal[1], 0.0f),
		inv_trans * to_vec4(t->normal[2], 0.0f)
};

image-20240321133033007

3.rasterizer_triangle函数(光栅化、片元处理、帧缓冲阶段)

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
	auto v = t.toVector4();
	// Bouding Box
	int min_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x())), min_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y())), max_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x())), max_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));

	// 采样
	for (int x = min_x; x <= max_x; x++)
		for (int y = min_y; y <= max_y; y++)
		{
			if (insideTriangle(x + 0.5, y + 0.5, t.v))
			{
				// Z-Buffer算法
				// 获取当前像素的深度
				// 线性插值,透视矫正
				//    * 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 tmp = computeBarycentric2D(x + 0.5, y + 0.5, t.v);
				float alpha, beta, gamma;
				std::tie(alpha, beta, gamma) = tmp;
				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 (zp < depth_buf[get_index(x, y)])// 当前深度更靠前
				{
					depth_buf[get_index(x, y)] = zp;
					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);

					// 把信息存到shader参数中
					fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
					payload.view_pos = interpolated_shadingcoords;
					auto pixel_color = fragment_shader(payload);

					set_pixel(Vector2i(x, y), pixel_color);
				}
			}
		}
}
1.光栅化阶段:
  • bouding box
  • 采样,判断每个像素是否在小三角形内,如果在,进行深度测试——深度插值得到这个像素的z值与缓存进行比较。
  • 通过深度测试后对三角形各个属性进行插值计算。
2.片元处理阶段:
  • 把三角形插值信息传入shader参数中并调用片元处理函数
3.帧缓冲阶段:
  • 把shader处理结果传到frame_buffer中(set_pixel)

此函数里调用的较为重要的几个函数

求重心坐标函数computeBarycentric2D:
static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector4f* v) {
	float c1 = (x * (v[1].y() - v[2].y()) + (v[2].x() - v[1].x()) * y + v[1].x() * v[2].y() - v[2].x() * v[1].y()) / (v[0].x() * (v[1].y() - v[2].y()) + (v[2].x() - v[1].x()) * v[0].y() + v[1].x() * v[2].y() - v[2].x() * v[1].y());
	float c2 = (x * (v[2].y() - v[0].y()) + (v[0].x() - v[2].x()) * y + v[2].x() * v[0].y() - v[0].x() * v[2].y()) / (v[1].x() * (v[2].y() - v[0].y()) + (v[0].x() - v[2].x()) * v[1].y() + v[2].x() * v[0].y() - v[0].x() * v[2].y());
	float c3 = (x * (v[0].y() - v[1].y()) + (v[1].x() - v[0].x()) * y + v[0].x() * v[1].y() - v[1].x() * v[0].y()) / (v[2].x() * (v[0].y() - v[1].y()) + (v[1].x() - v[0].x()) * v[2].y() + v[0].x() * v[1].y() - v[1].x() * v[0].y());
	return { c1,c2,c3 };
}

image-20240321133711822

倾斜透视矫正:
                auto tmp = computeBarycentric2D(x + 0.5, y+0.5, t.v);
                float alpha, beta, gamma;
                std::tie(alpha, beta, gamma) = tmp;

                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;

我们的重心坐标往往都是在屏幕空间下所得到的,如果直接使用屏幕空间下的重心坐标进行插值会造成一定的误差,与在view space下是不一样的。
具体解决方案:
https://zhuanlan.zhihu.com/p/144331875

下面给出上述链接未给出的过程。(即z和zp的关系)

image-20240321134359983

image-20240321134413946

image-20240321134545428

image-20240321134601727

属性插值:

image-20240321135103479

着色模型

1.normal模型

// 根据法线进行不同着色
Eigen::Vector3f normal_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = (payload.normal.head<3>().normalized() + Eigen::Vector3f(1.0f, 1.0f, 1.0f)) / 2.f;
    Eigen::Vector3f result;
    result << return_color.x() * 255, return_color.y() * 255, return_color.z() * 255;
    return result;
}

这个函数并不是光照模型,只是根据不同法线返回不同颜色值。
第一行的代码,首先取出当前待着色像素点的法向量的X,Y,Z坐标并归一化,故此时X,Y,Z都在[-1,1]之间,加上(1.0f, 1.0f, 1.0f)后,变为[0,2],再除以2,即得[0,1],再分别乘以255即可得到各个颜色值了。

normal模型渲染结果:

image-20240321135903442

2.phong模型

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;

    // ping point的信息
    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;// view space
    Eigen::Vector3f normal = payload.normal;

    Eigen::Vector3f result_color = {0, 0, 0};// 光照结果

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

直接根据公式写就行,这里我认为环境光应该放在循环外,不过这样渲出来的结果相比说说明偏暗,但后面的displacement又要放在外面否则偏亮。

phong模型渲染结果:

image-20240321135958497

3.texture模型

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {
        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};

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

只需要理解纹理影响颜色,颜色影响漫反射系数。所以在一开始读取材质纹理的颜色信息,再传给漫反射系数,之后再套用phong模型即可。

这里框架有一个问题,就是在getColor中,没有限制UV坐标的范围,加上即可。

Eigen::Vector3f getColor(float u, float v)
{
	// 限制(u, v)坐标范围
	u = std::fmin(1, std::fmax(u, 0));
	v = std::fmin(1, std::fmax(v, 0));

	auto u_img = u * width;
	auto v_img = (1 - v) * height;
	auto color = image_data.at<cv::Vec3b>(v_img, u_img);// 四舍五入
	return Eigen::Vector3f(color[0], color[1], color[2]);
}

这样做后,编译会显示一个报错,不过并不影响结果。

texture模型渲染结果:

image-20240321140123712

4.bump和displacement模型

这一部分助教在论坛说会在光线追踪部分详细介绍,现在按照注释实现即可。
所以原理我目前也不是完全明白…


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
    // Let n = normal = (x, y, z)
    // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
    // Vector b = n cross product t
    // Matrix TBN = [t b n]

    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();

    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    // Vector ln = (-dU, -dV, 1)
    // Normal n = normalize(TBN * ln)

    float u = payload.tex_coords.x(), v = payload.tex_coords.y();
    float w = payload.texture->width, h = payload.texture->height;

    float dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm());
    float dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm());

    Eigen::Vector3f ln{ -dU,-dV,1.0f };

    normal = TBN * ln;
    // 归一化
    Eigen::Vector3f result_color = normal.normalized();
    return result_color * 255.f;
}

bump模型渲染结果:

image-20240321140420112


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(), v = payload.tex_coords.y();
    float w = payload.texture->width, 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(), v = (eye_pos - point).normalized();// 光照方向和观察方向
        Eigen::Vector3f h = (l + v).normalized();// 半程向量
        Eigen::Vector3f I = light.intensity;// 光强
        float r2 = (light.position - point).dot(light.position - point);
        Eigen::Vector3f Ld = kd.cwiseProduct(I / r2) * std::max(0.0f, normal.dot(l));//cwiseProduct()函数允许Matrix直接进行点对点乘法,而不用转换至Array
        Eigen::Vector3f Ls = ks.cwiseProduct(I / r2) * std::pow(std::max(0.0f, normal.dot(h)), p);
        result_color += Ld + Ls;
    }

    return result_color * 255.f;
}

这里也存在我上面说的问题,即环境光的位置。
如果把环境光放在循环里面,渲染出来偏浅。
放在外面和说明基本一致,但还是有些偏暗。

displacement模型渲染结果:

image-20240321140500193

双线性插值

Eigen::Vector3f getColorBilinear(float u, float v)
{
	// 限制(u, v)坐标范围
	u = std::fmin(1, std::fmax(u, 0));
	v = std::fmin(1, std::fmax(v, 0));

	auto u_img = u * width, v_img = (1 - v) * width;
	float u0 = std::fmax(1, floor(u_img - 0.5)), u1 = floor(u_img + 0.5);
	float v0 = std::fmax(1, floor(v_img - 0.5)), v1 = floor(v_img + 0.5);
	float s = (u_img - u0) / (u1 - u0), t = (v_img - v0) / (v1 - v0);
	auto c00 = image_data.at<cv::Vec3b>(v0, u0);
	auto c01 = image_data.at<cv::Vec3b>(v0, u1);
	auto c10 = image_data.at<cv::Vec3b>(v1, u0);
	auto c11 = image_data.at<cv::Vec3b>(v1, u1);
	auto c0 = c00 + s * (c01 - c00);
	auto c1 = c10 + s * (c11 - c10);
	auto color = c0 + t * (c1 - c0);
   // std::cout << color << std::endl;
	return Eigen::Vector3f(color[0], color[1], color[2]);
}

image-20240321140555834

这里放一个双线性插值后的效果对比,不过我不知道怎样缩小纹理图大小,我缩小后使用双线性插值牛牛鼻子旁边会出现鼻涕一样的颜色… 所以就用的原贴图来进行对比。
img
鼻子边缘还是能看出效果的,不过效果不太明显就是了。

  • 71
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值