void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) {
int dx = abs(x1-x0),dy=abs(y1-y0);
if(dx>dy){
if(x0>x1) {
std::swap(x0, x1);
std::swap(y0, y1);
}
for (int x=x0; x<=x1; x++) {
float t = (x-x0)/(float)(x1-x0);
int y = y0*(1.-t) + y1*t;
image.set(x, y, color);
}
}
else{
if(y0>y1) {
std::swap(x0, x1);
std::swap(y0, y1);
}
for (int y=y0; y<=y1; y++) {
float t = (y-y0)/(float)(y1-y0);
int x = x0*(1.-t) + x1*t;
image.set(x, y, color);
}
}
}
- obj 格式
v 0.608654 -0.568839 -0.416318
表示点的坐标f 1193/1240/1193 1180/1227/1180 1179/1226/1179
表示三角形(1193,1180, 1179 三个点组成)
- 扫描线方法:逐行扫描
- 缺点:单线程老方法
void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color) {
std::vector<Vec2i> points={t0,t1,t2};
sort(points.begin(),points.end(),[](Vec2i a,Vec2i b){return a.y<b.y;});
for(int i=points[0].y;i<=points[1].y;i++)
{
int left=points[0].x+(points[1].x-points[0].x)*(i-points[0].y)/(points[1].y-points[0].y);
int right=points[0].x+(points[2].x-points[0].x)*(i-points[0].y)/(points[2].y-points[0].y);
if(left>right)std::swap(left,right);
for(int j=left;j<=right;j++)
{
image.set(j,i,color);
}
}
for(int i=points[1].y+1;i<=points[2].y;i++)
{
int left=points[1].x+(points[2].x-points[1].x)*(i-points[1].y)/(points[2].y-points[1].y);
int right=points[0].x+(points[2].x-points[0].x)*(i-points[0].y)/(points[2].y-points[0].y);
if(left>right)std::swap(left,right);
for(int j=left;j<=right;j++)
{
image.set(j,i,color);
}
}
}
- 包围盒算法,判断点是否在三角形内部
//求三角形的重心坐标
Vec3f barycentric(Vec2i *pts, Vec2i P) {
Vec3f u = Vec3f(pts[2][0]-pts[0][0], pts[1][0]-pts[0][0], pts[0][0]-P[0]) ^ Vec3f(pts[2][1]-pts[0][1], pts[1][1]-pts[0][1], pts[0][1]-P[1]);
if (std::abs(u.z)<1) return Vec3f(-1,1,1);
return Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z);
}
//包围盒绘制三角形
void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color) {
int minx = std::max(0,std::min(t0.x, std::min(t1.x, t2.x)));
int maxx = std::min(image.get_width()-1,std::max(t0.x, std::max(t1.x, t2.x)));
int miny = std::max(0,std::min(t0.y, std::min(t1.y, t2.y)));
int maxy = std::min(image.get_height()-1,std::max(t0.y, std::max(t1.y, t2.y)));
for(int i=minx;i<=maxx;i++){
for(int j=miny;j<=maxy;j++){
Vec3f bc_screen = barycentric(&t0, Vec2i(i,j));
if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue;
image.set(i, j, color);
}
}
}
- 稍作修改可以得到 3 f 版本
//zBuffer单例类
class zBuffer {
private:
zBuffer(int width, int height) {
this->width = width;
this->height = height;
buffer = new float[width*height];
for(int i=0;i<width*height;i++){
buffer[i] = -std::numeric_limits<float>::max();
}
}
~zBuffer() {
delete [] buffer;
}
public:
int width, height;
float *buffer;
static zBuffer* getInstance(int width, int height) {
static zBuffer instance(width, height);
return &instance;
}
bool test(int x, int y, float z) {
if(x<0||x>=width||y<0||y>=height) return false;
if(z>buffer[y*width+x]) {
buffer[y*width+x] = z;
return true;
}
return false;
}
};
- 正交投影就是从-1~1 的立方体映射到二维平面上
Vec3f world2screen(Vec3f v) {
return Vec3f(int((v.x+1.)*width/2.+.5), int((v.y+1.)*height/2.+.5), v.z);
}
- 将摄像机旋转转化为物体的旋转
- 对于平移以及旋转操作,只需要将摄像机的运动转化为物体的反方向运动
-
- 摄像机旋转之后的 z 轴实际上就是摄像机和物体之间的连线方向
-
- 此外还定义一个 up 方向,这样就可以完全确定新坐标系
-
$up\times z=x$ ,$z\times x=y$
void lookat(Vec3f eye, Vec3f center, Vec3f up) {
Vec3f z = (eye-center).normalize();
Vec3f x = cross(up,z).normalize();
Vec3f y = cross(z,x).normalize();
Matrix Minv = Matrix::identity();
Matrix Tr = Matrix::identity();
for (int i=0; i<3; i++) {
Minv[0][i] = x[i];
Minv[1][i] = y[i];
Minv[2][i] = z[i];
Tr[i][3] = -eye[i];
}
ModelView = Minv*Tr;
}
Matrix Viewport(int x, int y, int w, int h, int depth) {
Matrix m = Matrix::identity(4);
m[0][3] = x+w/2.f;
m[1][3] = y+h/2.f;
m[2][3] = depth/2.f;
m[0][0] = w/2.f;
m[1][1] = h/2.f;
m[2][2] = depth/2.f;
return m;
}
- 常用于处理光照
- 着色器接口
struct IShader {
virtual ~IShader();
virtual Vec3i vertex(int iface, int nthvert) = 0;
virtual bool fragment(Vec3f bar, TGAColor &color) = 0;
};
- 顶点着色器:转换顶点坐标(mvp),并为片段着色器准备数据。(如法线,用于处理光照)
- 面着色器:对三角形内的像素进行具体着色
struct GouraudShader : public IShader {
//存储三个点的法向量
Vec3f varying_intensity;
Vec3f vertex(int iface, int nthvert) override {
varying_intensity[nthvert] = std::max(0.f, model->normal(iface, nthvert)*light_dir);
Vec3f gl_Vertex = model->vert(iface, nthvert);
return m2v(ModelView*v2m(gl_Vertex));
}
bool fragment(Vec3f bar, TGAColor &color) override {
float intensity = varying_intensity*bar;
color = Vec3f(255,255,255)*intensity;
return false;
}
};
- 由于便利顺序是,枚举每一个面,然后对面上的点进行处理,最后对面进行渲染,因此在点着色时一方面进行了坐标变化,另一方面收集了法向量,用于之后的插值
- 在面渲染阶段使用法向量结合重心坐标进行插值计算。
- 面渲染器的返回值可以用于在光栅化渲染时跳过一些绘图
TGAColor color;
bool discard = shader.fragment(c, color);
if (!discard) {
zbuffer.set(P.x, P.y, TGAColor(P.z));
image.set(P.x, P.y, color);
}
- phongshader 版本,使用法线贴图对每个点的反射进行具体计算
bool fragment(Vec3f bar, TGAColor &color, Vec3f p) override {
Vec2f uv = varying_uv[0]*bar.x + varying_uv[1]*bar.y + varying_uv[2]*bar.z;
Vec3f normal = m2v((ModelView).transpose()*v2m(model->normal(uv))).normalize();
Vec3f light = m2v(ModelView*v2m(light_dir)).normalize();
Vec3f see = (m2v(ModelView*v2m(camera))-m2v(ModelView*v2m(p))).normalize();
Vec3f mid = (light+see).normalize();
float dis = (m2v(ModelView*v2m(p))-m2v(ModelView*v2m(camera))).norm()/100;
float attenuation = 100.0 / (0.5 * dis + 0.1 * dis * dis);
Vec3f ambient = Vec3f(1.2, 1.2, 1.2);
Vec3f diffuse = Vec3f(1, 1, 1)*std::max(0.f, normal*mid)*attenuation;
Vec3f specular = Vec3f(0.2, 0.2, 0.2)*pow(std::max(0.f, normal*mid), 50)*attenuation;
Vec3f result = ambient+diffuse+specular;
TGAColor c = model->diffuse(uv);
color = TGAColor(std::min(255.f,c[2]*result[2]), std::min(255.f,c[1]*result[1]), std::min(255.f,c[0]*result[0]));
return false;
}
-
全局坐标系 (Global Frame):
- 在全局坐标系中,纹理的坐标是直接映射到3D世界空间中的坐标系上的。例如,一个法线贴图中的法线向量直接对应到全局空间中的方向。这意味着纹理是独立于模型表面的方向和位置的。
- 全局坐标系下的法线贴图不依赖于模型表面的局部属性,因此,它可能在模型的不同部位重复使用时出现不连贯的情况,尤其是在模型拥有复杂的几何形状和多个面时。
-
达布桑坐标系 (Darboux Frame):
- 达布桑坐标系,也被称为切线空间(Tangent Space),是一种依赖于模型表面的局部坐标系。在这个坐标系中,纹理坐标是相对于模型表面上某一点的局部坐标系定义的。法线贴图通常存储在这个坐标系中,因此它们表示的法线是相对于表面局部区域的。
- 使用达布桑坐标系的好处是,即使模型在空间中旋转或者变形,纹理(尤其是法线贴图)仍然可以正确地表现模型表面的局部细节。这使得法线贴图可以更加灵活地应用于复杂的几何体,并在视觉上保持一致性。
- 此外,还能在运动时保持正确切线方向(如张嘴)
- 并且也可以减少一部分贴图,如只给出一只手臂,另一只对称计算
- 法线贴图中存储的是切线空间下的一个向量,使用 RGB 三个通道来存储
-
切线空间:TBN 变换
-
计算过程
- 先从光源位置看,看哪些部分可以被点亮,哪些部分会被隐藏起来
- 使用类似 zbuffer 的系统
- 对 pongshader 的修改
struct PhongShader : public IShader {
Vec2f varying_uv[3];
Vec3f sb_p[3];
Vec3f vertex(int iface, int nthvert) override {
varying_uv[nthvert] = model->uv(iface, nthvert);
Vec3f gl_Vertex = model->vert(iface, nthvert);
sb_p[nthvert] = gl_Vertex;
return m2v(ModelView*v2m(gl_Vertex));
}
bool fragment(Vec3f bar, TGAColor &color, Vec3f p) override {
Vec3f shadowp = m2v(ShadowView*v2m(sb_p[0]*bar.x + sb_p[1]*bar.y + sb_p[2]*bar.z));
float shadowdeep = shadowBuffer.getdeep(shadowp.x, shadowp.y);
float shadow_deffuse = 0.7+.3*(shadowdeep<shadowp.z+43.34);
float shadow_reflect = .3+.7*(shadowdeep<shadowp.z+43.34);
Vec2f uv = varying_uv[0]*bar.x + varying_uv[1]*bar.y + varying_uv[2]*bar.z;
Vec3f normal = m2v((ModelView).transpose()*v2m(model->normal(uv))).normalize();
Vec3f light = m2v(ModelView*v2m(light_p)).normalize();
Vec3f see = (m2v(ModelView*v2m(camera))-m2v(ModelView*v2m(p))).normalize();
Vec3f mid = (light+see).normalize();
float dis = (m2v(ModelView*v2m(p))-m2v(ModelView*v2m(camera))).norm()/100;
float attenuation = 300.0 / (0.5 * dis + 0.1 * dis * dis);
Vec3f ambient = Vec3f(0.8, 0.8, 0.8);
Vec3f diffuse = Vec3f(1, 1, 1)*std::max(0.f, normal*mid)*attenuation;
Vec3f specular = Vec3f(0.3, 0.3, 0.3)*pow(std::max(0.f, normal*mid), model->specular(uv))*attenuation;
Vec3f result = ambient*shadow_deffuse+(diffuse+specular)*shadow_reflect;
TGAColor c = model->diffuse(uv);
color = TGAColor(std::min(255.f,c[2]*result[2]), std::min(255.f,c[1]*result[1]), std::min(255.f,c[0]*result[0]));
return false;
}
};
- 使用 ssaa 4
class zBuffer {
private:
int width, height;
Vec3f *frameBuffer;
float *deepBuffer;
zBuffer(int width, int height) {
this->width = width;
this->height = height;
frameBuffer = new Vec3f[width*height*4];
deepBuffer = new float[width*height*4];
for(int i=0;i<width*height*4;i++)
{
frameBuffer[i] = Vec3f(0,0,0);
deepBuffer[i] = -std::numeric_limits<float>::max();
}
}
~zBuffer() {
delete [] frameBuffer;
delete [] deepBuffer;
}
public:
static zBuffer* getInstance(int width=0, int height=0) {
static zBuffer instance(width, height);
return &instance;
}
bool test(int x, int y, int i, float z, Vec3f color) {
if(x<0||x>=width||y<0||y>=height) return false;
if(z>deepBuffer[y*width*4+x*4+i]) {
deepBuffer[y*width*4+x*4+i] = z;
frameBuffer[y*width*4+x*4+i] = color;
return true;
}
return false;
}
float getdeep(int x, int y) {
float res=-std::numeric_limits<float>::max();
for(int i=0;i<4;i++) {
res = std::max(res, deepBuffer[y*width*4+x*4+i]);
}
return res;
}
Vec3f getcolor(int x, int y) {
Vec3f res(0,0,0);
for(int i=0;i<4;i++) {
res = res + frameBuffer[y*width*4+x*4+i];
}
return res;
}
};
class SSAA4 {
std::vector<Vec3f> spts;
public:
SSAA4(float x, float y, float z) {
spts.emplace_back(x+0.25f, y+0.25f, z);
spts.emplace_back(x+0.75f, y+0.25f, z);
spts.emplace_back(x+0.25f, y+0.75f, z);
spts.emplace_back(x+0.75f, y+0.75f, z);
//spts.emplace_back(x,y,z);
}
void cal(Vec3f *pts, Vec2i *uvs, Model*model,TGAImage&image) {
Vec3f colorAccumulator;
int num=0;
for(int i=0;i<spts.size();i++) {
Vec3f a = spts[i];
Vec3f bc_screen = barycentric(pts, a);
if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue;
a.z = 0;
Vec2i uv;
for (int j=0; j<3; j++)
{
a.z += pts[j][2]*bc_screen[j];
uv = uv + uvs[j]*bc_screen[j];
}
TGAColor color = model->diffuse(uv);
zBuffer::getInstance()->test(a.x,a.y,i,a.z,Vec3f(color.r/4.f, color.g/4.f, color.b/4.f));
}
}
};
- 双线性插值
//双线性插值
TGAColor bilerp(Vec2f uv, Model*model) {
float x0=floor(uv.x), y0=floor(uv.y), x1=x0+1, y1=y0+1;
float u=uv.x-x0, v=uv.y-y0;
TGAColor c00 = model->diffuse(Vec2i(x0, y0));
TGAColor c01 = model->diffuse(Vec2i(x0, y1));
TGAColor c10 = model->diffuse(Vec2i(x1, y0));
TGAColor c11 = model->diffuse(Vec2i(x1, y1));
Vec3f color = Vec3f(c00.r*(1-u)*(1-v) + c01.r*(1-u)*v + c10.r*u*(1-v) + c11.r*u*v,
c00.g*(1-u)*(1-v) + c01.g*(1-u)*v + c10.g*u*(1-v) + c11.g*u*v,
c00.b*(1-u)*(1-v) + c01.b*(1-u)*v + c10.b*u*(1-v) + c11.b*u*v);
return color;
}