赞
踩
这里我第一篇写的文章就是作业3,因为作业3包含了整个1-3作业的代码,除了作业代码本身之外,我也会写一些额外自己的拓展
图中的牛是以obj文件格式给出,直接在win下双击打开会得到这个模型。
而实际上,用文本编辑器打开是文本格式的文件。
其中#的部分是注释
####
#
# OBJ File Generated by Meshlab
#
####
# Object spot_triangulated_good.obj
#
# Vertices: 3225
# Faces: 5856
#
####
后面的一部分定义了一系列的点,其中v代表一个点,vn是一个法向量,vt是纹理坐标
vn 0.713667 0.093012 -0.694283
vt 0.854030 0.663650
v 0.348799 -0.334989 -0.083233
vn 0.742238 0.092067 0.663782
vt 0.724960 0.675077
v 0.313132 -0.399051 0.881192
这里说明,第一个v是0.348799 -0.334989 -0.083233,第二个v是 0.313132 -0.399051 0.881192
第三部分定义了一系列面,这里的面都是由三角形组成的。
f 739/739/739 735/735/735 736/736/736
f 189/189/189 736/736/736 735/735/735
f 192/192/192 738/738/738 737/737/737
f 739/739/739 737/737/737 738/738/738
f 190/190/190 741/741/741 740/740/740
f 743/743/743 740/740/740 741/741/741
其中f代表一个面,一个面由三个vertex组成,每个vertex包含纹理坐标、坐标和法线方向三个部分。比如第一行 f 的第一个点是739/739/739,第一个数是顶点坐标id,第二个纹理坐标id,第三个数是 法向量id。注意id的编号是v vn vt分别各自按顺序编号,编号值从1开始。
投影矩阵是一个很有争议的地方,我自己调的时候经常上下颠倒或者左右颠倒,使用我这里做了这个处理。首先把main函数中的正数改成负数。
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;
}
我的投影矩阵采用这个形式:
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
// TODO: Use the same projection matrix from the previous assignments
Eigen::Matrix4f projection = Eigen::Matrix4f::Zero();
eye_fov = eye_fov * MY_PI / 180;
float t = -zNear * tan(eye_fov / 2);
float r = t * aspect_ratio;
projection(0, 0) = zNear / r;
projection(1, 1) = zNear / t;
projection(2, 2) = (zFar + zNear) / (zNear - zFar);
projection(2, 3) = 2 * zFar * zNear / (zFar - zNear);
projection(3, 2) = 1;
return projection;
}
我采用了课上的矩阵
[
z
N
e
a
r
/
r
0
0
0
0
z
N
e
a
r
/
t
0
0
0
0
(
z
F
a
r
+
z
N
e
a
r
)
(
z
N
e
a
r
−
z
F
a
r
)
2
∗
z
F
a
r
∗
z
N
e
a
r
(
z
F
a
r
−
z
N
e
a
r
)
0
0
1
0
]
\left[
通过两个特殊点看矩阵的效果。
矩阵会把(*, *, zNear, 1)映射到(*, *, zNear, zNear),经过齐次除法,z=1。
把(*, *, zFar, 1)映射到(*, *, -zFar, zFar),经过齐次除法,z = -1。
注意此时zFar是更负的,也就是更小的。利用这个矩阵映射,原来比较小的z,(zFar < zNear),在映射后依然会比较小(-1 < 1)
接下来,需要完成void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
函数中的内容。
有几个问题我想做一下重点的记录。
首先看一下投影后z值大小与物体的前后关系。在经过model和view两个变换后,我们发现物体的z,这里称为 z 0 z_0 z0,均为负,且 z 0 z_0 z0越大(越接近0)则物体越靠近相机。经过上面的透视投影矩阵,z的大小相对关系不变。
在函数rst::rasterizer::draw
中,出现如下代码,又一次修改了透视投影后的z。
vert.z() = vert.z() * f1 + f2
此时f1是正值,也就是说依然不改变z的相对大小。综上,z越大,物体离相机越近。
该函数输入的x,y是int类型的,但是考虑到像素中心 i + 0.5 的情况,手动把它改成float类型的,不然会出错。
代码中对三角形深度的差值使用了透视矫正插值,推导见这个博客。
我当时有个疑惑,为什么要用这么复杂的方式,既然插值要使用透视投影之前,在相机空间中的坐标进行插值,那么为什么不把相机空间的坐标存下来,直接在这个函数计算alpha, beta和gamma呢?
这是因为,我们把三角形的相机空间坐标存下来是很容易的,但是,我们要插值的点的x,y坐标是用屏幕空间(i + 0.5, j + 0.5 ) 表示的,如何为插值点找到原始的相机空间x,y坐标呢?其实这个反推相机坐标空间的过程就是透视矫正插值的思路。
对于纹理坐标等,我也在代码中实现了透视矫正插值,但是好像结果没有太大的影响。
代码的实现如下:
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) { auto v = t.toVector4(); int l = std::min(std::min(v[0].x(), v[1].x()), v[2].x()); int r = std::max(std::max(v[0].x(), v[1].x()), v[2].x()) + 1; int d = std::min(std::min(v[0].y(), v[1].y()), v[2].y()); int top = std::max(std::max(v[0].y(), v[1].y()), v[2].y()) + 1; // TODO : Find out the bounding box of current triangle. // iterate through the pixel and find if the current pixel is inside the triangle for (int x = l; x <= r; x++) { for (int y = d; y <= top; y++) { // vector<vector<double>> pList = {{x + 0.5, y + 0.5} }; // auto [alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v); if (insideTriangle(x + 0.5, y + 0.5, t.v )){ auto [alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, 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; int idx = get_index(x, y); if (depth_buf[idx] < z_interpolated) { depth_buf[idx] = z_interpolated; auto interpolated_color = interpolate(alpha, beta, gamma, static_cast<Eigen::Vector3f>(t.color[0] / v[0].w()), t.color[1] / v[1].w(), t.color[2] / v[2].w(), 1 / w_reciprocal); Vector3f interpolated_normal = interpolate(alpha, beta, gamma, static_cast<Eigen::Vector3f>(t.normal[0] / v[0].w()), t.normal[1] / v[1].w(), t.normal[2] / v[2].w(), 1 / w_reciprocal).normalized(); auto interpolated_texcoords = interpolate(alpha, beta, gamma, static_cast<Eigen::Vector2f>(t.tex_coords[0] / v[0].w()), t.tex_coords[1] / v[1].w(), t.tex_coords[2] / v[2].w(), 1 / w_reciprocal); /*auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1 / w_reciprocal); auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1 / w_reciprocal);*/ // auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1); //这里的interpolated_shadingcoords要使用view_pos的坐标而不是t.v的坐标,因为t.v是经过了mvp变换加上视口变换的像素坐标,而view_pos是以相机为原点的真实坐标 auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, static_cast<Eigen::Vector3f>(view_pos[0] / v[0].w()), view_pos[1] / v[1].w(), view_pos[2] / v[2].w(), 1 / w_reciprocal); auto tx = interpolated_shadingcoords.x(); auto ty = interpolated_shadingcoords.y(); auto tz = interpolated_shadingcoords.z(); //0.881108 0.50367 -9.70347 // 1.36768 0.428212 // auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0] , view_pos[1], view_pos[2], 1 / w_reciprocal); 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); } } } } // TODO: From your HW3, get the triangle rasterization code. // TODO: Inside your rasterization loop: // * 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 // 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; // TODO: Interpolate the attributes: // auto interpolated_color // auto interpolated_normal // auto interpolated_texcoords // auto interpolated_shadingcoords // Use: fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr); // Use: payload.view_pos = interpolated_shadingcoords; // 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; // Use: auto pixel_color = fragment_shader(payload); }
这是使用命令行参数 normal 跑出来的结果
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}; result_color += ka.cwiseProduct(amb_light_intensity); 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. auto l = (light.position - point).normalized(); auto r2 = (light.position - point).dot(light.position - point); auto v = (eye_pos - point).normalized(); auto h = (v + l).normalized(); auto Ls = ks.cwiseProduct(light.intensity / (r2)) * std::pow(std::max(0.0f, normal.dot(h)), p); auto Ld = kd.cwiseProduct(light.intensity / (r2)) * std::max(0.0f, normal.dot(l)); result_color = result_color + Ls + Ld; } return result_color * 255.f; }
注意几个点:
ka.cwiseProduct(amb_light_intensity);
向量-向量按元素相乘,用的是cwiseProduct这个成员函数。结果:
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
// TODO: Get the texture value at the texture coordinates of the current fragment
float u = payload.tex_coords.x();
float v = payload.tex_coords.y();
return_color = payload.texture->getColor(u, v);
}
这部分比较简单,直接查纹理坐标然后设上去就好了。效果如下:
这一段我参考了其他人的做法,但是觉得有点问题。
这里我有两个不理解的地方,感觉不符合逻辑:
t = ( x ∗ y x 2 + z 2 , x 2 + z 2 , z ∗ y x 2 + z 2 ) ( 1 ) t = (\frac{x*y} {\sqrt{x^2 + z^2}}, \sqrt{x^2 + z^2}, \frac{z*y} {\sqrt{x^2 + z^2}}) (1) t=(x2+z2 x∗y,x2+z2 ,x2+z2 z∗y)(1)
n = ( x , y , z ) n = (x, y, z) n=(x,y,z)
t ⋅ n = 1 x 2 + z 2 ( x 2 y + x 2 y + z 2 y + z 2 y ) t \cdot n = \frac{1}{\sqrt{x^2 + z^2}} (x^2y + x^2y + z^2y + z^2y) t⋅n=x2+z2 1(x2y+x2y+z2y+z2y)
很显然不为0,而如果把 t 的第二项加个符号,变为
t
2
=
(
x
∗
y
x
2
+
z
2
,
−
x
2
+
z
2
,
z
∗
y
x
2
+
z
2
)
(
2
)
t_2 = (\frac{x*y} {\sqrt{x^2 + z^2}}, - \sqrt{x^2 + z^2}, \frac{z*y} {\sqrt{x^2 + z^2}}) (2)
t2=(x2+z2
x∗y,−x2+z2
,x2+z2
z∗y)(2)
t
2
⋅
n
=
1
x
2
+
z
2
(
x
2
y
−
x
2
y
−
z
2
y
+
z
2
y
)
=
0
t_2 \cdot n = \frac{1}{\sqrt{x^2 + z^2}} (x^2y - x^2y - z^2y + z^2y) = 0
t2⋅n=x2+z2
1(x2y−x2y−z2y+z2y)=0
如果按照第二种是正交的,但是跑出来的结果和答案不一样。
payload.texture->getColor(u + 1.0f/w, v).norm()
去进行计算,用的是模长。我看这个文章结合理解,法线贴图是用RGB通道来表示XYZ坐标吧,这里有人给出解答吗?除了两个地方,还有几个注意点:
payload.texture->getColor(u + 1.0f/w, v)
给出下一个u的时候,1要除以w,竖直方向同理,因为这里的纹理坐标是[0,1]如果直接+1就会超过范围上限而报错。按照代码注释的 t 向量的计算方法(1)式,结果如下:
按照(2)式的结果
代码实现如下:
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] // 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 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, b, normal; float u = payload.tex_coords.x(); float v = payload.tex_coords.y(); float w = payload.texture->width; float h = payload.texture->height; auto dU = kh * kn * (payload.texture->getColor(u + 1.0f/w, v).norm() - payload.texture->getColor(u, v).norm()); auto 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; }
代码如下:
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) 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, b, normal; float u = payload.tex_coords.x(); float v = payload.tex_coords.y(); float w = payload.texture->width; float h = payload.texture->height; auto dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm()); auto 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; point += (kn * normal * payload.texture->getColor(u, v).norm()); normal = (TBN * ln).normalized(); Eigen::Vector3f result_color = {0, 0, 0}; result_color += ka.cwiseProduct(amb_light_intensity); 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. auto l = (light.position - point).normalized(); auto r2 = (light.position - point).dot(light.position - point); auto v = (eye_pos - point).normalized(); auto h = (v + l).normalized(); auto Ls = ks.cwiseProduct(light.intensity / (r2)) * std::pow(std::max(0.0f, normal.dot(h)), p); auto Ld = kd.cwiseProduct(light.intensity / (r2)) * std::max(0.0f, normal.dot(l)); result_color = result_color + Ls + Ld; } return result_color * 255.f; }
结果
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。