Renderer Pipeline:
视图变换(投影变换,视口变换) - 知乎 (zhihu.com)
Framework Interpretation:
Code execution logic diagram:
To make it easier for you to understand, I will explain in Chinese:
main.cpp:Load model, configure parameters, select shader, draw call.
Core: Instantiate the rasterized class, pass arguments, and draw call .
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;
// OBJ文件路径前缀
std::string obj_path = "../models/spot/";
// 加载OBJ文件
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* 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);
auto texture_path = "hmap.jpg";
r.set_texture(Texture(obj_path + texture_path));
std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = phong_fragment_shader;
// 处理命令行参数,以决定使用哪种着色器
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);
r.set_fragment_shader(active_shader);
int key = 0;
int frame_count = 0;
// 如果是命令行模式,直接渲染并保存图像
if (command_line)
{
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(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);
//最后使用OpenCV对缓冲区进行绘制
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;
}
rasterizer.cpp:base on arguments rasterize,lerp and drawcall.
//
// Created by goksu on 4/6/19.
//
#include <algorithm>
#include "rasterizer.hpp"
#include <opencv2/opencv.hpp>
#include <math.h>
/**
* 加载位置数据到Rasterizer中。
*
* @param positions 包含位置数据的向量,每个位置由一个三维向量(Eigen::Vector3f)表示。
* @return 返回一个pos_buf_id结构体,包含加载位置数据后生成的唯一标识符。
*/
rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f> &positions)
{
auto id = get_next_id();
pos_buf.emplace(id, positions);
return {id};
}
rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i> &indices)
{
auto id = get_next_id();
ind_buf.emplace(id, indices);
return {id};
}
rst::col_buf_id rst::rasterizer::load_colors(const std::vector<Eigen::Vector3f> &cols)
{
auto id = get_next_id();
col_buf.emplace(id, cols);
return {id};
}
rst::col_buf_id rst::rasterizer::load_normals(const std::vector<Eigen::Vector3f>& normals)
{
auto id = get_next_id();
nor_buf.emplace(id, normals);
normal_id = id;
return {id};
}
/**
* 使用Bresenham算法绘制线段
* @param begin 线段的起点,使用Eigen::Vector3f表示(x, y, z)坐标
* @param end 线段的终点,使用Eigen::Vector3f表示(x, y, z)坐标
* 该函数不返回任何值。
*/
void rst::rasterizer::draw_line(Eigen::Vector3f begin, Eigen::Vector3f end)
{
// 提取起点和终点的x, y坐标
auto x1 = begin.x();
auto y1 = begin.y();
auto x2 = end.x();
auto y2 = end.y();
// 默认线颜色为白色
Eigen::Vector3f line_color = {255, 255, 255};
// 初始化Bresenham算法所需变量
int x,y,dx,dy,dx1,dy1,px,py,xe,ye,i;
// 计算x和y方向的增量及绝对值
dx=x2-x1;
dy=y2-y1;
dx1=fabs(dx);
dy1=fabs(dy);
// 预计算Bresenham算法中的决策参数
px=2*dy1-dx1;
py=2*dx1-dy1;
// 根据dx和dy的大小决定是x方向还是y方向进行迭代
if(dy1<=dx1)
{// 确定起点和终点,以及迭代方向
if(dx>=0)
{
x=x1;
y=y1;
xe=x2;
}
else
{
x=x2;
y=y2;
xe=x1;
}
// 在起点处设置像素颜色
Eigen::Vector2i point = Eigen::Vector2i(x, y);
set_pixel(point,line_color);
// 使用Bresenham算法绘制线段
for(i=0;x<xe;i++)
{
x=x+1;
if(px<0)
{
px=px+2*dy1;
}
else
{// 根据决策参数更新y坐标
if((dx<0 && dy<0) || (dx>0 && dy>0))
{
y=y+1;
}
else
{
y=y-1;
}
px=px+2*(dy1-dx1);
}
// 在当前像素位置设置颜色
Eigen::Vector2i point = Eigen::Vector2i(x, y);
set_pixel(point,line_color);
}
}
else
{// 确定起点和终点,以及迭代方向
if(dy>=0)
{
x=x1;
y=y1;
ye=y2;
}
else
{
x=x2;
y=y2;
ye=y1;
}
// 在起点处设置像素颜色
Eigen::Vector2i point = Eigen::Vector2i(x, y);
set_pixel(point,line_color);
for(i=0;y<ye;i++)
{
y=y+1;
if(py<=0)
{
py=py+2*dx1;
}
else
{
if((dx<0 && dy<0) || (dx>0 && dy>0))
{
x=x+1;
}
else
{
x=x-1;
}
py=py+2*(dx1-dy1);
}
// 在当前像素位置设置颜色
Eigen::Vector2i point = Eigen::Vector2i(x, y);
set_pixel(point,line_color);
}
}
}
auto to_vec4(const Eigen::Vector3f& v3, float w = 1.0f)
{
return Vector4f(v3.x(), v3.y(), v3.z(), w);
}
static bool insideTriangle(int x, int y, const Vector4f* _v){
Vector3f v[3];
for(int i=0;i<3;i++)
v[i] = {_v[i].x(),_v[i].y(), 1.0};
Vector3f f0,f1,f2;
f0 = v[1].cross(v[0]);
f1 = v[2].cross(v[1]);
f2 = v[0].cross(v[2]);
Vector3f p(x,y,1.);
if((p.dot(f0)*f0.dot(v[2])>0) && (p.dot(f1)*f1.dot(v[0])>0) && (p.dot(f2)*f2.dot(v[1])>0))
return true;
return false;
}
/**
* 计算二维空间中点 (x, y) 对于给定的三个顶点坐标(v[0], v[1], v[2])形成的三角形的 barycentric 坐标。
*
* @param x 点的 x 坐标。
* @param y 点的 y 坐标。
* @param v 包含三个顶点坐标的向量数组,每个顶点是一个 Vector4f 对象(尽管这里只用到 x 和 y 分量)。
* @return 一个包含三个 float 值的 tuple,分别代表点 (x, y) 在三角形三个顶点上的权重(barycentric 坐标)。
*/
static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector4f* v){
// 计算点 (x, y) 对于三角形 v[0], v[1], v[2] 的 barycentric 坐标 c1, c2, c3(就那个贼长的公式)
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};
}
/**
* @brief 对给定的三角形列表进行绘制。
*
1.计算视口的中心点坐标。
2.计算模型视图投影矩阵。
3.遍历三角形列表,对每个三角形进行以下操作:
将三角形的顶点从模型空间变换到视图空间。
从齐次坐标转换为非齐次坐标。
将三角形的顶点从视图空间变换到齐次裁剪空间。
进行齐次除法。
计算三角形法线在视图空间的表示。
进行视口变换。
更新变换后的顶点和法线到新的三角形对象。
设置三角形颜色。
调用rasterize_triangle函数进行三角形光栅化。
*
* @param TriangleList 一个包含待绘制三角形指针的向量。
*/
void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {
// 计算视口的中心点坐标
float f1 = (50 - 0.1) / 2.0;
float f2 = (50 + 0.1) / 2.0;
// 计算模型视图投影矩阵
Eigen::Matrix4f mvp = projection * view * model;
for (const auto& t:TriangleList)
{
Triangle newtri = *t;
// 将三角形的顶点从模型空间变换到视图空间
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;
// 将mm容器中的每个元素(假定为具有多个元素的向量)的前三个元素提取出来,
//并将这些提取出的子向量存储到viewspace_pos容器中
std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
return v.template head<3>();
});
// 将三角形的顶点从视图空间变换到齐次裁剪空间
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();
}
// 计算三角形法线在视图空间的表示
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)
};
// 进行视口变换
for (auto & vert : v)
{
vert.x() = 0.5*width*(vert.x()+1.0);
vert.y() = 0.5*height*(vert.y()+1.0);
vert.z() = vert.z() * f1 + f2;
}
// 更新变换后的顶点和法线到新的三角形对象
for (int i = 0; i < 3; ++i)
{
// 更新屏幕空间顶点坐标
newtri.setVertex(i, v[i]);
}
for (int i = 0; i < 3; ++i)
{
// 更新视图空间法线向量
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);
// 传入视图空间顶点位置进行三角形光栅化
rasterize_triangle(newtri, viewspace_pos);
}
}
/**
* 通过线性插值计算三维向量的加权平均值。
* @param alpha、beta、gamma 三个插值系数,用于计算加权平均值。
* @param vert1、vert2、vert3 三个三维向量,作为插值的基向量。
* @param weight 加权因子,用于调整插值结果的权重。
* @return 返回根据插值系数和基向量计算得到的加权平均值向量。
*/
static Eigen::Vector3f interpolate(float alpha, float beta, float gamma, const Eigen::Vector3f& vert1, const Eigen::Vector3f& vert2, const Eigen::Vector3f& vert3, float weight)
{
return (alpha * vert1 + beta * vert2 + gamma * vert3) / weight;
}
/**
* 通过线性插值计算在三个顶点构成的三角形内的一点坐标。
*
* @param alpha、beta、gamma 用于确定点在三角形内的位置的权重参数。这三个参数的和应为1。
* @param vert1、vert2、vert3 三角形的三个顶点。
* @param weight 对插值结果进行缩放的权重值。
* @return 返回根据给定权重参数和顶点计算得到的插值点的坐标,作为一个Eigen::Vector2f对象。
*/
static Eigen::Vector2f interpolate(float alpha, float beta, float gamma, const Eigen::Vector2f& vert1, const Eigen::Vector2f& vert2, const Eigen::Vector2f& vert3, float weight)
{
auto u = (alpha * vert1[0] + beta * vert2[0] + gamma * vert3[0]);
auto v = (alpha * vert1[1] + beta * vert2[1] + gamma * vert3[1]);
u /= weight;
v /= weight;
return Eigen::Vector2f(u, v);
}
/**
* 屏幕空间光栅化
*
* 该函数用于将一个三角形光栅化到屏幕空间上,具体包括顶点深度值的插值、属性(如颜色、法向量、纹理坐标等)的插值,
* 以及调用片段着色器来计算最终像素颜色。
*
* @param t 待光栅化的三角形对象,包含顶点信息等。
* @param view_pos 包含三个顶点在视图空间中的位置的数组。
*/
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
// TODO: From your HW3, get the triangle rasterization code.
auto v = t.toVector4();
int left,right,top,bottom;
// TODO : Find out the bounding box of current triangle.
left =std::min(std::min(v[0].x(),v[1].x()),v[2].x());
right =std::max(std::max(v[0].x(),v[1].x()),v[2].x());
top=std::max(std::max(v[0].y(),v[1].y()),v[2].y());
bottom=std::min(std::min(v[0].y(),v[1].y()),v[2].y());
for(int y=bottom;y<=top;y++){
for(int x=left;x<=right;x++){
// iterate through the pixel and find if the current pixel is inside the triangle
if(insideTriangle(x+0.5,y+0.5,t.v)){
// If so, use the following code to get the interpolated z value.
auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal;
if(depth_buf[get_index(x,y)]>z_interpolated){
depth_buf[get_index(x,y)]=z_interpolated;
// TODO : set the current pixel (use the set_pixel function) to the color
// of the triangle (use getColor function) if it should be painted.
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);
// Use: Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
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({x,y},pixel_color);
}
}
}
}
}
void rst::rasterizer::set_model(const Eigen::Matrix4f& m)
{
model = m;
}
void rst::rasterizer::set_view(const Eigen::Matrix4f& v)
{
view = v;
}
void rst::rasterizer::set_projection(const Eigen::Matrix4f& p)
{
projection = p;
}
void rst::rasterizer::clear(rst::Buffers buff)
{
if ((buff & rst::Buffers::Color) == rst::Buffers::Color)
{
std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0});
}
if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
{
std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
}
}
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
frame_buf.resize(w * h);
depth_buf.resize(w * h);
texture = std::nullopt;
}
int rst::rasterizer::get_index(int x, int y)
{
return (height-y)*width + x;
}
void rst::rasterizer::set_pixel(const Vector2i &point, const Eigen::Vector3f &color)
{
//old index: auto ind = point.y() + point.x() * width;
int ind = (height-point.y())*width + point.x();
frame_buf[ind] = color;
}
void rst::rasterizer::set_vertex_shader(std::function<Eigen::Vector3f(vertex_shader_payload)> vert_shader)
{
vertex_shader = vert_shader;
}
void rst::rasterizer::set_fragment_shader(std::function<Eigen::Vector3f(fragment_shader_payload)> frag_shader)
{
fragment_shader = frag_shader;
}
Texture.hpp:Store the image and get the color according to uv.
//
// Created by LEI XU on 4/27/19.
//
#ifndef RASTERIZER_TEXTURE_H
#define RASTERIZER_TEXTURE_H
#include "global.hpp"
#include <eigen3/Eigen/Eigen>
#include <opencv2/opencv.hpp>
//说人话就是该类存储纹理贴图,并根据uv获取颜色
class Texture{
private:
//用来存储图像数据(矩阵)
cv::Mat image_data;
public:
Texture(const std::string& name)
{
image_data = cv::imread(name);//从文件读取图像到 cv::Mat
//执行的操作是将输入图像 image_data 从 RGB 颜色空间转换到 BGR 颜色空间。
//OpenCV 内部默认的存储顺序是 BGR 而不是标准的 RGB。
//这意味着如果你有一个原本是 RGB 格式的图像数据,
//当它被读入 cv::Mat 结构后,如果要与 OpenCV 的大多数图像处理函数兼容,
//可能需要将其转换为 BGR 格式。
cv::cvtColor(image_data, image_data, cv::COLOR_RGB2BGR);
//int cv::Mat::rows:获取矩阵的行数
//int cv::Mat::cols:获取矩阵的列数
width = image_data.cols;
height = image_data.rows;
}
//图像宽高
int width, height;
//根据u,v坐标获取图像颜色(u,v是0-1之间,所以要映射一下)
Eigen::Vector3f getColor(float u, float v)
{
//映射
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]);
}
};
#endif //RASTERIZER_TEXTURE_H
Theory:
Lerp according to barycenter:
diffuse:
Specular:bilne phong model
bump mapping:
Take two adjacent points and find the tangent, then rotate 90 degrees counterclockwise:
Displacement mapping :
There's an extra process of shifting the vertices
Addition:
We need to determine the coordinates of the four pixels adjacent to the point.
Point 1 represents the original uv coordinates. We first floor the coordinates to obtain Point 2. Then, based on Point 2, we can calculate Points 3, 4, 5, and 6:
Code:
rasterize_triangle:Find the barycentric coordinates of a pixel and interpolate the colors based on these coordinates.
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
// TODO: From your HW3, get the triangle rasterization code.
auto v = t.toVector4();
int left,right,top,bottom;
// TODO : Find out the bounding box of current triangle.
left =std::min(std::min(v[0].x(),v[1].x()),v[2].x());
right =std::max(std::max(v[0].x(),v[1].x()),v[2].x());
top=std::max(std::max(v[0].y(),v[1].y()),v[2].y());
bottom=std::min(std::min(v[0].y(),v[1].y()),v[2].y());
for(int y=bottom;y<=top;y++){
for(int x=left;x<=right;x++){
// iterate through the pixel and find if the current pixel is inside the triangle
if(insideTriangle(x+0.5,y+0.5,t.v)){
// If so, use the following code to get the interpolated z value.
auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal;
if(depth_buf[get_index(x,y)]>z_interpolated){
depth_buf[get_index(x,y)]=z_interpolated;
// TODO : set the current pixel (use the set_pixel function) to the color
// of the triangle (use getColor function) if it should be painted.
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);
// Use: Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
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({x,y},pixel_color);
}
}
}
}
}
attention:There is an interpolate function above, directly call interpolate, try not to write their own (because of the return value problem, their own write bad will give an error)
phong_fragment_shader:
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)
{
// 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 l,h,v;
v=eye_pos-point;
l=(light.position -point).normalized();
float x0,y0,z0;
x0=light.position.x() -point.x();
y0=light.position.y() -point.y();
z0=light.position.z() -point.z();
float r =sqrt(x0*x0+y0*y0+z0*z0);
h=(l+v)/(l+v).norm();
Eigen::Vector3f la = ka.cwiseProduct(amb_light_intensity);
Eigen::Vector3f ld= kd.cwiseProduct(light.intensity/(r*r))*std::max(normal.normalized().dot(l),0.0f);
Eigen::Vector3f ls = ks.cwiseProduct(light.intensity/(r*r))*pow(std::max(normal.dot(h),0.0f),64);
result_color+=la+ld+ls;
}
return result_color * 255.f;
}
attention:cwiseProduct, element-by-element multiplication of a vector, with * cannot be multiplied bit by bit, otherwise it will be an error.
texture_fragment_shader:Compared with the phong model, the additional one maps the base color from the texture.
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.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)
{
// 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 l,h,v;
v=eye_pos-point;
l=(light.position -point).normalized();
float x0,y0,z0;
x0=light.position.x() -point.x();
y0=light.position.y() -point.y();
z0=light.position.z() -point.z();
float r =sqrt(x0*x0+y0*y0+z0*z0);
h=(l+v)/(l+v).norm();
Eigen::Vector3f la = ka.cwiseProduct(amb_light_intensity);
Eigen::Vector3f ld= kd.cwiseProduct(light.intensity/(r*r))*std::max(normal.normalized().dot(l),0.0f);
Eigen::Vector3f ls = ks.cwiseProduct(light.intensity/(r*r))*pow(std::max(normal.dot(h),0.0f),64);
result_color+=la+ld+ls;
}
return result_color * 255.f;
}
attention:payload.texture->getColor,Notice the arrow and use the dot (.) You get an error.
bump_fragment_shader:Realization formula.
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;
// 根据法线向量计算TBN矩阵(切线空间到世界空间的变换矩阵)纹理坐标空间到世界空间的转换
Eigen::Vector3f t ;
t<<normal.x()*normal.y()/sqrt(normal.x()*normal.x()+normal.z()*normal.z()),
sqrt(normal.x()*normal.x()+normal.z()*normal.z()),
normal.z()*normal.y()/sqrt(normal.x()*normal.x()+normal.z()*normal.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();
int w=payload.texture->width;
int 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 ;
ln<<-dU, -dV, 1;
normal = (TBN * ln).normalized();
Eigen::Vector3f result_color = {0, 0, 0};
result_color = normal;
return result_color * 255.f;
}
attention:Note that the division becomes 1.0f, which would cause an error if you used the int 1 effect directly.
displacement_fragment_shader:
Additional code:
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;
// TODO: Implement displacement 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]
// 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)
// Position p = p + kn * n * h(u,v)
// Normal n = normalize(TBN * ln)
Eigen::Vector3f t ;
t<<normal.x()*normal.y()/sqrt(normal.x()*normal.x()+normal.z()*normal.z()),
sqrt(normal.x()*normal.x()+normal.z()*normal.z()),
normal.z()*normal.y()/sqrt(normal.x()*normal.x()+normal.z()*normal.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();
int w=payload.texture->width;
int 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 ;
ln<<-dU, -dV, 1;
normal = (TBN * ln).normalized();
point += kn * normal * payload.texture->getColor(u,v).norm();
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 l,h,v;
v=eye_pos-point;
l=(light.position -point).normalized();
float x0,y0,z0;
x0=light.position.x() -point.x();
y0=light.position.y() -point.y();
z0=light.position.z() -point.z();
float r =sqrt(x0*x0+y0*y0+z0*z0);
h=(l+v)/(l+v).norm();
Eigen::Vector3f la = ka.cwiseProduct(amb_light_intensity);
Eigen::Vector3f ld= kd.cwiseProduct(light.intensity/(r*r))*std::max(normal.normalized().dot(l),0.0f);
Eigen::Vector3f ls = ks.cwiseProduct(light.intensity/(r*r))*pow(std::max(normal.dot(h),0.0f),64);
result_color+=la+ld+ls;
}
return result_color * 255.f;
}
Attention:Remember to get the color.norm () (I don't know why yet)
Addtion:
Eigen::Vector3f getColorBilinear(float u,float v)
{
//防止溢出
if(u<0)u=0;
if(u>1)u=1;
if(v<0)v=0;
if(v>1)v=1;
auto u_img = u * width;
auto v_img = (1 - v) * height;
//leftX,RightX...
float LX,RX,TY,BY;
LX=std::max(1.0f,floor(u_img-0.5f));
RX=floor(u_img+0.5f);
TY=floor(v_img+0.5f);
BY=std::max(1.0f,floor(v_img-0.5f));
float tx,ty;
tx=(u_img-LX)/(RX-LX);
tx=(v_img-BY)/(TY-BY);
//注意这里是(v,u)写反了会出问题
auto c00=image_data.at<cv::Vec3b>(BY,LX);
auto c01=image_data.at<cv::Vec3b>(TY,LX);
auto c10=image_data.at<cv::Vec3b>(BY,RX);
auto c11=image_data.at<cv::Vec3b>(TY,RX);
Eigen::Vector3f p0,p1,p2;
auto color0 =c00+tx*(c01-c00);
auto color1 =c10+tx*(c11-c10);
auto color2 =color0+ty*(color1-color0);
return Eigen::Vector3f(color2[0], color2[1], color2[2]);
}
Attention:
When obtaining the color based on coordinates, it's important to note that the coordinates are (v, u). Writing them in reverse order may lead to the following situation:
Games101:作业3(管线分析、深度插值、libpng warning、双线性插值等)_games101作业3-CSDN博客
Homework:
Addition:Sleek little calf!
cool!