tinyrenderer学习总结(3)

Lesson 6: Shaders for the software renderer

1.flat着色法、Gouraud着色法与phone着色法

参考:Flat、Gouraud、Phong Shading 着色方法对比 - Joe’s Blog

flat着色、gourand着色、phong着色_qiuchangyong的博客-CSDN博客

1)flat着色法是逐面片着色

以面片为单位进行着色,对整个三角形只计算一次光照值。通常计算光照的位置为三角形中心,表面法向量为三角形法向量。

其计算量小,但是效果较差,渲染出的模型可能会有面片感。下图是采用phone光照模型的flat着色。主要思想是在顶点着色中计算出三角形面片的颜色,最后对这三个顶点的颜色取平均去覆盖整个三角形面片。

struct flatShader : public IShader {
    mat<4, 4, float> uniform_M;   //  Projection*ModelView
    mat<4, 4, float> uniform_MIT; // (Projection*ModelView).invert_transpose()//求逆求转置
    TGAColor flatcolor[3];
    virtual Vec4f vertex(int iface, int nthvert) {
        Vec2f uv = model->uv(iface, nthvert);
        Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();//最终的法向量
        Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize();//最终的光线向量
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   // reflected light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv));//高光项,高光贴图里到底存的啥
        float diff = std::max(0.f, n * l);//漫反射项
        TGAColor c  = model->diffuse(uv);//基础颜色信息
        for (int i = 0; i < 3; i++)flatcolor[nthvert][i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255);//对RGBA信息依次设置
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert)); // read the vertex from .obj file
        return Viewport * Projection * ModelView * gl_Vertex; // transform it to screen coordinates
    }
    virtual bool fragment(Vec3f bar, TGAColor& color) {
        for (int i = 0; i < 3; i++)color[i] = (flatcolor[0][i] + flatcolor[1][i] + flatcolor[2][i]) / 3;
        return false;
    }
};

int main(int argc, char** argv) {
    if (2==argc) {
        model = new Model(argv[1]);
    } else {
        model = new Model("obj/african_head.obj");
    }

    lookat(eye, center, up);
    viewport(width/8, height/8, width*3/4, height*3/4);
    projection(-1.f/(eye-center).norm());
    light_dir.normalize();

    TGAImage image  (width, height, TGAImage::RGB);
    TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);

    flatShader shader;
    shader.uniform_M = Projection * ModelView;
    shader.uniform_MIT = (Projection * ModelView).invert_transpose();
    for (int i = 0; i < model->nfaces(); i++) {
        Vec4f screen_coords[3];
        for (int j = 0; j < 3; j++) {
            screen_coords[j] = shader.vertex(i, j);
        }
        triangle(screen_coords, shader, image, zbuffer);
    }
    image.flip_vertically(); // to place the origin in the bottom left corner of the image
    zbuffer.flip_vertically();
    image.write_tga_file("flatshader.tga");
    return 0;
}

2)phone着色法是逐像素着色

在光栅化遍历像素的时候,通过顶点的法线和uv信息对当前像素点的法线和uv信息进行插值,然后再拿这些信息去进行光照计算进行着色。其着色的计算是发生在片元着色阶段。

phone着色的效果已经是相当不错了,没有面片缺陷了,很细腻了已经。

struct phoneShader : public IShader {
    mat<2, 3, float> varying_uv;  // same as above
    mat<4, 4, float> uniform_M;   //  Projection*ModelView
    mat<4, 4, float> uniform_MIT; // (Projection*ModelView).invert_transpose()//求逆求转置

    virtual Vec4f vertex(int iface, int nthvert) {
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));//iface:第几个面,nthvert:第几个顶点。得到三个顶点对应的uv
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert)); // read the vertex from .obj file
        return Viewport * Projection * ModelView * gl_Vertex; // transform it to screen coordinates
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        Vec2f uv = varying_uv * bar;//重心坐标插值得到uv
        Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();//最终的法向量
        Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize();//最终的光线向量
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   // reflected light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv));//高光项,高光贴图里到底存的啥
        float diff = std::max(0.f, n * l);//漫反射项
        TGAColor c = model->diffuse(uv);//基础颜色信息
        color = c;
        for (int i = 0; i < 3; i++) color[i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255);//对RGBA信息依次设置
        return false;
    }
};

int main(int argc, char** argv) {
    if (2==argc) {
        model = new Model(argv[1]);
    } else {
        model = new Model("obj/african_head.obj");
    }

    lookat(eye, center, up);
    viewport(width/8, height/8, width*3/4, height*3/4);
    projection(-1.f/(eye-center).norm());
    light_dir.normalize();

    TGAImage image  (width, height, TGAImage::RGB);
    TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);

    phoneShader shader;
    shader.uniform_M = Projection * ModelView;
    shader.uniform_MIT = (Projection * ModelView).invert_transpose();
    for (int i = 0; i < model->nfaces(); i++) {
        Vec4f screen_coords[3];
        for (int j = 0; j < 3; j++) {
            screen_coords[j] = shader.vertex(i, j);
        }
        triangle(screen_coords, shader, image, zbuffer);
    }
    image.flip_vertically(); // to place the origin in the bottom left corner of the image
    zbuffer.flip_vertically();
    image.write_tga_file("phoneShader.tga");
    return 0;
}

3)Gouraud着色法是逐顶点着色

Gouraud shading 是三者中比较折衷的方法。类似 Phong shading,其多边形的每个顶点都有一个法向量,但它在着色时不是对向量进行插值。实际使用时,通常先计算多边形每个顶点的光照,再通过双线性插值计算三角形区域中其它像素的颜色。

大致就是在顶点着色中就计算出颜色了,在片元着色中则直接进行插值。相比flat着色,Gouraud着色的光滑性好很多了,但是在高光处仍有面片的痕迹。

struct gouraudShader : public IShader {
    mat<4, 4, float> uniform_M;   //  Projection*ModelView
    mat<4, 4, float> uniform_MIT; // (Projection*ModelView).invert_transpose()//求逆求转置
    TGAColor gouraudcolor[3];
    virtual Vec4f vertex(int iface, int nthvert) {
        Vec2f uv = model->uv(iface, nthvert);
        Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();//最终的法向量
        Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize();//最终的光线向量
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   // reflected light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv));//高光项,高光贴图里到底存的啥
        float diff = std::max(0.f, n * l);//漫反射项
        TGAColor c = model->diffuse(uv);//基础颜色信息
        for (int i = 0; i < 3; i++)gouraudcolor[nthvert][i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255);//对RGBA信息依次设置
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert)); // read the vertex from .obj file
        return Viewport * Projection * ModelView * gl_Vertex; // transform it to screen coordinates
    }
    virtual bool fragment(Vec3f bar, TGAColor& color) {
        for (int i = 0; i < 3; i++)color[i] = (gouraudcolor[0][i]*bar.x + gouraudcolor[1][i]*bar.y + gouraudcolor[2][i]*bar.z);
        return false;
    }
};
int main(int argc, char** argv) {
    if (2==argc) {
        model = new Model(argv[1]);
    } else {
        model = new Model("obj/african_head.obj");
    }

    lookat(eye, center, up);
    viewport(width/8, height/8, width*3/4, height*3/4);
    projection(-1.f/(eye-center).norm());
    light_dir.normalize();

    TGAImage image  (width, height, TGAImage::RGB);
    TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);

    gouraudShader shader;
    shader.uniform_M = Projection * ModelView;
    shader.uniform_MIT = (Projection * ModelView).invert_transpose();
    for (int i = 0; i < model->nfaces(); i++) {
        Vec4f screen_coords[3];
        for (int j = 0; j < 3; j++) {
            screen_coords[j] = shader.vertex(i, j);
        }
        triangle(screen_coords, shader, image, zbuffer);
    }
    image.flip_vertically(); // to place the origin in the bottom left corner of the image
    zbuffer.flip_vertically();
    image.write_tga_file("gouraudShader.tga");
    return 0;
}

可以看出渲染出来的效果与之前的效果相差无几。 

2.光照模型

参考:Phong氏着色模型 - 知乎

计算机图形学五:局部光照模型(Blinn-Phong 反射模型)与着色方法(Phong Shading)_剑 来!的博客-CSDN博客_局部光照模型

以下的光照模型都只是经验模型,通常是由环境光、漫反射光、高光这三项组成。

1)phone光照模型

由环境照明(每个场景恒定)、漫反射照明(我们目前计算的那个)和镜面反射照明。这三个部分组成。

环境光(Ambient):

 I_{a}是光强,K_{a}代表物体表面对环境光的反射率

漫反射(Diffuse):

以Lambert模型为例

 其中K_{d}为漫反射系数,I入射光强,nl分别如图中所示为法线向量和入射方向,max是为了剔除夹角大于90°的光(即光照在物体背面)。然后一般还需要考虑光线的衰减,因此考虑再除以一个r^{2}r表示光源与反射点之间的距离,在我们tinyrender的代码中没有考虑漫反射项的功率衰减问题,这个需要稍微注意。L_{d}为最终的光照强度。

 高光项(Specular):

对于漫反射,我们计算了向量nl之间的(余弦)角度,现在我们对向量r(反射光方向)和v(观察方向)之间的角度(余弦)感兴趣。 

首先,我们通常知道归一化后的nl。那么这时候根据平行四边形法则:r+l=2ncosa (alr之间的夹角,cosa=n\cdot l)。因此r=(2n\cdot l)n-l

其中K_{s}为镜面反射系数,I为入射光强,r为光源到入射点距离,注意这里在max剔除大于90°的光之后,我们还乘了一个指数p,添加该项的原因很直接,因为离反射光越远就越不应该看见反射光,需要一个指数p加速衰减。

但是一个有光泽的表面在一个方向上的反射比在其他方向上的反射要多得多!那么,如果我们取余弦的 10 次方会发生什么?回想一下,当我们应用幂时,所有小于 1 的数字都会减少。这意味着余弦的 10 次方将使反射光束的半径更小。并且百分之一的功率小得多的光束半径。这种力量存储在一个特殊的纹理(高光贴图)中,告诉每个点是否有光泽(即贴图中存的是这个指数p)。在我们tinyrender的代码中没有考虑光线功率衰减问题,因此K_{s}(I/r^{2})这一项直接设置成了常数,并且还默认视点方向为(0,0,1),而cosa=l\cdot r,故只需要取r.z便可表示cosa

最终:

 for (int i=0; i<3; i++) color[i] = std::min<float>(5 + c[i]*(diff + .6*spec), 255);

我为环境分量取 5,为漫反射分量取 1,为镜面反射分量取 0.6。选择什么系数其实是自由的。不同的选择赋予对象不同的外观。

2)Blinn-Phong光照模型

Blinn-Phong与phone这两个光照模型的区别在于高光项的计算。

在这里插入图片描述

Blinn-Phong光照模型将高光项的反射方向与人眼观察方向夹角替换成如下图所示的一个半程向量和法线向量的夹角。


Lesson 6bis: tangent space normal mapping

1.几个前置知识

1)贴图存的不一定是颜色,可能是法线、高度、温度等等。

2)法线贴图(NormalMap)存储的是表面的法线方向,即向量n = (x, y, z)。而方向是相对于坐标空间而言的。通常法线有两种坐标空间:Tangent Space(切线空间)、Object Space(对象空间或模型空间)

法线贴图是通过使用 RGB 纹理的红色、绿色和蓝色分量来存储法线的 XYZ 分量

2.对象空间法线贴图

1)读取规则

Vec3f Model::normal(Vec2f uvf) {
    Vec2i uv(uvf[0]*normalmap_.get_width(), uvf[1]*normalmap_.get_height());
    TGAColor c = normalmap_.get(uv[0], uv[1]);
    Vec3f res;
    for (int i=0; i<3; i++)
        res[2-i] = (float)c[i]/255.f*2.f - 1.f;//读取RGB赋值给对象空间的法向量
    return res;
}

在tinyrender里面是通过”res[2-i] = (float)c[i]/255.f*2.f - 1.f;“这个语句来读取法向量的。

c[i]为读出的RGB的值,其取值为:0\leq c[i]\leq 255,因此对其除以一个255,然后0\leq c[i]/255\leq 1,而归一化法线的每个取值应为(-1,1),而-1\leq 2.f*c[i]/255.f - 1.f\leq 1

因此,该映射规则合理,并且贴图中的RGB可以在(0,255)之间取值,因此他的颜色可能是五彩缤纷的。

最终渲染效果:

2) 从对象空间法线到世界空间法线

因为贴图里存的法线信息模型空间的法线,而我们的光线实际上是在世界空间的,因此我们需要对法线进行一个变换使其能在世界空间当中。

参考:Lesson 5: Moving the camera · ssloy/tinyrenderer Wiki · GitHub

假设模型变换矩阵M\Delta ABC的法线向量n\Delta ABC上的任意点为P1P2P1P2构成向

P1-P2,满足n^{T}(P1-P2)=0。这里设n为列向量,P1-P2为列向量。

P1P2在变换后为M(P1-P2),而因为有n^{T}(M^{-1}M)(P1-P2)=0,即

(n^{T}M^{-1})[M(P1-P2)]=0,因此新的法向量应为(n^{T}M^{-1})^{T}=(M^{-1})^{T}n

shader.uniform_MIT = (ModelView).invert_transpose();

 在tinyrender里面用到的uniform_MIT矩阵便是(M^{-1})^{T}矩阵。

推导还可参考:【OpenGL】法线变换详解(Normal Transform)[转] - 陈峰 - 博客园

3.TBN空间法线贴图

参考:第十三课:法线贴图

写给笨人的法线贴图原理 - 饭后温柔 - 博客园

DX12法线贴图篇:NormalMap - 知乎

【D3D11游戏编程】学习笔记二十四:切线空间(Tangent Space)_Brother灬Nam的博客-CSDN博客

切线空间TBN矩阵推导

1)大致介绍

TBN空间可以说是三角形面片空间,不同的三角形面片会建立不同的TBN空间,因此其需要比对象空间法线贴图多耗费一个计算TBN矩阵的过程。

也不要把TBN空间想的太复杂,无疑就是用一个新的坐标基去表述法向量罢了。只要知道坐标基的转换矩阵,将对象(模型)空间的法线转换成TBN空间的坐标值存在法线贴图里。

2)TBN矩阵的推导

TBN空间会将模型空间的XYZ轴映射为TBN轴。

在tinyrender中以插值后的法线作为TBN空间的N轴(Z轴),然后TB轴所在平面为三角形面片所在平面。

在tinyrender中给出的TBN矩阵的计算方法堪称绝妙,能减少许多的计算量。这里本质上用到了正交矩阵的逆等于其转置的思想。

 

 注意到通常(平面)三角形具有恒定的法线向量,而我在矩阵 A 的最后一行使用插值后的法线,个人猜测这是因为插值后的法线与法线贴图中存储的法线会与存储的切线更接近,而且这样能使渲染后的模型更加平滑,而不是呈现比较严重的面片效果。如果直接采用面法线,会有较严重的面片现象(毕竟法线贴图中存储的扰动与TBN中的N轴接近。在本项目中的面片面积较大,不同点之间的法线相差较大,直接采用面向量的话,会产生跟flat着色一样的效果,即看到比较严重的面片状),但是其计算量小。

struct normalShader : public IShader {
    mat<2, 3, float> varying_uv;  // triangle uv coordinates, written by the vertex shader, read by the fragment shader
    mat<4, 3, float> varying_tri; // triangle coordinates (clip coordinates), written by VS, read by FS
    mat<3, 3, float> varying_nrm; // normal per vertex to be interpolated by FS
    mat<3, 3, float> ndc_tri;     // triangle in normalized device coordinates

    virtual Vec4f vertex(int iface, int nthvert) {
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));
        varying_nrm.set_col(nthvert, proj<3>((Projection * ModelView).invert_transpose() * embed<4>(model->normal(iface, nthvert), 0.f)));
        Vec4f gl_Vertex = Projection * ModelView * embed<4>(model->vert(iface, nthvert));
        varying_tri.set_col(nthvert, gl_Vertex);
        ndc_tri.set_col(nthvert, proj<3>(gl_Vertex / gl_Vertex[3]));
        return gl_Vertex;
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        /**/
       // Vec3f bn = varying_nrm*Vec3f(1./3,1./3,1./3);//(varying_nrm * bar).normalize()
        Vec3f bn = (varying_nrm * bar).normalize();
        Vec2f uv = varying_uv * bar;

        mat<3, 3, float> A;
        A[0] = ndc_tri.col(1) - ndc_tri.col(0);//向量p1p0
        A[1] = ndc_tri.col(2) - ndc_tri.col(0);//向量p2p0
        //bn = ((cross(A[0], A[1]).normalize() + bn) / 2).normalize();
        A[2] = bn;

        mat<3, 3, float> AI = A.invert();

        Vec3f i = AI * Vec3f(varying_uv[0][1] - varying_uv[0][0], varying_uv[0][2] - varying_uv[0][0], 0);
        Vec3f j = AI * Vec3f(varying_uv[1][1] - varying_uv[1][0], varying_uv[1][2] - varying_uv[1][0], 0);

        mat<3, 3, float> B;
        B.set_col(0, i.normalize());
        B.set_col(1, j.normalize());
        B.set_col(2, bn);// cross(i,j).normalize()
        static bool t=1;
        Vec3f n = (B * model->normal(uv)).normalize();
        if (t) {
            cout << n << endl;
            cout << bn << endl;
            cout << cross(i, j) << endl;
            cout << cross(A[0], A[1]).normalize() << endl;
            cout<<model->normal(uv)<<endl;
//可自行打印不同法线的差异
            t = 0;
        }
        Vec3f l = proj<3>(embed<4>(light_dir)).normalize();//最终的光线向量
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   // reflected light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv));//高光项,高光贴图里到底存的啥
        float diff = std::max(0.f, n * l);//漫反射项
        TGAColor c = model->diffuse(uv);//基础颜色信息
        color = c;
        for (int i = 0; i < 3; i++) color[i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255);//对RGBA信息依次设置

        return false;
    }

采用TBN空间的最终渲染效果与采用对象空间的渲染效果差别不大。

直接采用三角形的面片向量(由三角形两条边叉乘得出)会造成一定的面片现象,如下所示。

除此之外,个人还尝试对原有代码的矩阵进行优化,减少TBN矩阵的计算量。

    struct normalShader1 : public IShader {
        mat<2, 3, float> varying_uv;  // triangle uv coordinates, written by the vertex shader, read by the fragment shader
        mat<4, 3, float> varying_tri; // triangle coordinates (clip coordinates), written by VS, read by FS
        mat<3, 3, float> varying_nrm; // normal per vertex to be interpolated by FS
        mat<3, 3, float> ndc_tri;     // triangle in normalized device coordinates
        mat<3, 3, float> B;
        virtual Vec4f vertex(int iface, int nthvert) {
            varying_uv.set_col(nthvert, model->uv(iface, nthvert));
            varying_nrm.set_col(nthvert, proj<3>((Projection * ModelView).invert_transpose() * embed<4>(model->normal(iface, nthvert), 0.f)));
            Vec4f gl_Vertex = Projection * ModelView * embed<4>(model->vert(iface, nthvert));
            varying_tri.set_col(nthvert, gl_Vertex);
            ndc_tri.set_col(nthvert, proj<3>(gl_Vertex / gl_Vertex[3]));
            return gl_Vertex;
        }

        virtual bool fragment(Vec3f bar, TGAColor& color) {
            Vec2f uv = varying_uv * bar;

            B.set_col(2, (varying_nrm * bar).normalize());
            Vec3f n = (B * model->normal(uv)).normalize();

            Vec3f l = proj<3>(embed<4>(light_dir)).normalize();//最终的光线向量
            Vec3f r = (n * (n * l * 2.f) - l).normalize();   // reflected light
            float spec = pow(std::max(r.z, 0.0f), model->specular(uv));//高光项,高光贴图里到底存的啥
            float diff = std::max(0.f, n * l);//漫反射项
            TGAColor c = model->diffuse(uv);//基础颜色信息
            color = c;
            for (int i = 0; i < 3; i++) color[i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255);//对RGBA信息依次设置

            return false;
        }
        void set1() {//加速方法1,在原有的基础上先计算出TB轴
            Vec3f bar = Vec3f(1. / 3, 1. / 3, 1. / 3);
            Vec3f bn = (varying_nrm * bar).normalize();//(varying_nrm * bar).normalize()
            mat<3, 3, float> A;//mat<2, 3, float> A

            A[0] = ndc_tri.col(1) - ndc_tri.col(0);//向量BA
            A[1] = ndc_tri.col(2) - ndc_tri.col(0);//向量CA
            bn = (cross(A[0], A[1]) + bn).normalize();//暂且用这个向量来先代替插值法向量来计算出T跟B,后面再用插值法向量替换回这个向量
            A[2] = bn;
            mat<3, 3, float> AI = A.invert();
            Vec3f i = AI * Vec3f(varying_uv[0][1] - varying_uv[0][0], varying_uv[0][2] - varying_uv[0][0], 0);
            Vec3f j = AI * Vec3f(varying_uv[1][1] - varying_uv[1][0], varying_uv[1][2] - varying_uv[1][0], 0);
            B.set_col(0, i.normalize());
            B.set_col(1, j.normalize());
        }
        void set2() {//加速方法2--先算出TB轴。也是目前用的较多的
            mat<2, 3, float> A;
            A[0] = ndc_tri.col(1) - ndc_tri.col(0);//向量BA
            A[1] = ndc_tri.col(2) - ndc_tri.col(0);//向量CA
            mat<2, 2, float> uv;
            uv[0] = { varying_uv[0][1] - varying_uv[0][0], varying_uv[1][1] - varying_uv[1][0] };
            uv[1] = { varying_uv[0][2] - varying_uv[0][0], varying_uv[1][2] - varying_uv[1][0] };
            mat<2, 2, float> uvI = uv.invert();
            mat<2, 3, float> TB = uvI * A;
            Vec3f i = TB[0];
            Vec3f j = TB[1];
            Vec3f bar = Vec3f(1. / 3, 1. / 3, 1. / 3);
            Vec3f bn = (varying_nrm * bar).normalize();//(varying_nrm * bar).normalize()
            B.set_col(0, i.normalize());
            B.set_col(1,j.normalize());//j.normalize()  cross(bn,i)
            cout << B.col(0) * B.col(1) << endl;
        }

    };
    int main(int argc, char** argv) {
        if (2 == argc) {
            model = new Model(argv[1]);
        }
        else {
            model = new Model("obj/african_head.obj");
        }

        lookat(eye, center, up);
        viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);
        projection(-1.f / (eye - center).norm());
        //light_dir.normalize();
        light_dir = proj<3>((Projection * ModelView * embed<4>(light_dir, 0.f))).normalize();
        TGAImage image(width, height, TGAImage::RGB);
        TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);

        normalShader1 shader;//normalShader
        for (int i = 0; i < model->nfaces(); i++) {
            for (int j = 0; j < 3; j++) {
                shader.vertex(i, j);
            }
            shader.set2();//shader.set1()
            triangle(shader.varying_tri, shader, image, zbuffer);
        }
        delete model;
        image.flip_vertically(); // to place the origin in the bottom left corner of the image
        zbuffer.flip_vertically();
        image.write_tga_file("normalShader2.tga");

        return 0;
}
    

3)切线空间法线贴图的优势与特色

参考:谈谈法线贴图 - 可可西 - 博客园

a.不同的面可以共用法线贴图中的UV坐标以减少存储量

b.因为切线空间法线贴图中存的法线信息与实际的法线差值比较小,而法线又是TBN空间中的Z轴方向,因此贴图中存的值会往(0,0,255)靠近,这是其为什么呈现蓝色的原因。

c.因为切线空间是针对三角形面片的,所以即使三角形面片发生凹陷效果了,也能得到正确的法线方向。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]:在这个引用中,作者描述了他想通过编写一个简化版本的克隆来展示OpenGL的工作方式。这个克隆是一个软件渲染器,不使用第三方库,目标是展示OpenGL是如何工作的。作者提供了一些关于渲染器的要求和目标,包括处理多边形线和纹理图片,并生成渲染模型。这个渲染器没有图形界面,只生成图像。作者还提到了一些关于图像处理的起点和可用功能的限制。 引用\[2\]:这个引用是关于线框绘制的代码片段。它使用了一个循环来遍历模型的面,并使用线段绘制函数来绘制每个面的边界。 引用\[3\]:这个引用是关于绘制线段的第一个尝试的代码片段。它使用了一个循环来在两个点之间插值并绘制线段。 根据这些引用,"tinyrenderer"是一个作者正在编写的一个简化版本的OpenGL克隆,它是一个软件渲染器,用于生成图像。它不使用第三方库,目的是展示OpenGL的工作方式。它包括处理多边形线和纹理图片,并生成渲染模型。它没有图形界面,只生成图像。作者提供了一些关于图像处理的起点和可用功能的限制。在代码中,有关线框绘制和线段绘制的代码片段被引用。 #### 引用[.reference_title] - *1* [TinyRenderer(1):500行代码实现软件渲染器](https://blog.csdn.net/qq_40680501/article/details/112913733)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [tinyrenderer学习总结(1)](https://blog.csdn.net/qq_42987967/article/details/124831459)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值