学习TinyRenderer

news2024/9/21 14:39:12

1图形学

图形学,简单来讲是将3D数据以各个视觉效果呈现在2D画布(屏幕)上;

2 TinyRenderer

TinyRenderer从零开始实现了一个渲染器;

TinyRenderer代码地址:https://github.com/ssloy/tinyrenderer

内容介绍在:https://github.com/ssloy/tinyrenderer/wiki

知乎上也有讲解TinyRenderer的系列文章:TinyRenderer从零实现(C++);

TinyRenderer将3D或2D模型数据渲染在一张tga图像上;tga的格式可以参见【数据压缩】TGA文件格式分析;

wiki里的链接代码没有CMakeLists.txt,需要自己手动创建;

CMakeLists.txt可以先这么写:

cmake_minimum_required(VERSION 3.20)
project(render)
add_definitions(-std=c++11)
include_directories(.)
file(GLOB SOURCES *.h *.cpp)
add_executable(${PROJECT_NAME} ${SOURCES})

2.1 tga文件读写相关

tgaimage.h和tgaimage.cpp文件内定义了关于tga文件的格式、读写操作;可以使用set和get接口设置或读取二维图像上的rgb值,是TinyRenderer的结果输出;

TGA_Header结构体是tga文件的格式头信息,具体内容可以看上面的tga格式的链接;

TGAColor结构体是单个像素对应的RGBA值,内部使用了union表示一个像素值,可以是4个unsigned char类型,也可以是一个unsigned char[4]数组,也可以是一个unsigned int类型;

TGAImage类中分配了一个width*height*byte_spp(每个像素的字节数)大小的data内存作为画布;

2.2 绘制点

对应课程里的Lesson 0: getting started,主要实现了绘制点功能;

绘制点是图形学里最基本最简单的操作,使用TGAImage类的set接口设置4个二维坐标对应的像素值,在原点附近绘制4个白色和红色的点;

注意:这里将图像的左下角设置为了原点(0,0);

#include "tgaimage.h"

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red   = TGAColor(255, 0,   0,   255);

int main(int argc, char** argv) {
    TGAImage image(50, 50, TGAImage::RGB);
    image.set(0, 0, red);
    image.set(0, 1, red);
    image.set(1, 0, white);
    image.set(1, 1, white);

    image.flip_vertically();

    image.write_tga_file("output.tga");
    return 0;
}

结果如下:

2.3 绘制线段

对应课程里的Lesson 1: Bresenham’s Line Drawing Algorithm;

主要内容是,在绘制点的基础上,绘制一组连续的点组成一条线段;

首先使用set设置了一组白色的点;

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red   = TGAColor(255, 0,   0,   255);

void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color);

int main(int argc, char** argv) {
    TGAImage image(500, 500, TGAImage::RGB);

    line(0, 0, 100, 100, image, white);
    image.flip_vertically();
    image.write_tga_file("output.tga");
    return 0;
}

void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
    for (float t = 0.; t < 1.; t += .01) {
        int x = x0 + (x1 - x0) * t;
        int y = y0 + (y1 - y0) * t;
        image.set(x, y, color);
    }
}

这里line函数由于没有考虑到浮点型小数在转换为int类型的精度丢失,造成了下面图像中缺失某些点;且line函数是按照0.01步进绘制点,会有精度不足的情况,但是如果把精度加大,又会造成循环次数过多的问题;

也可以按照直线的斜截式方程,计算每个X对应的Y坐标,设置(X,Y)坐标下的像素;不过图形学里一般是使用t,表示从X0向X1方向的时间点或者比例,一般t在0到1范围内,即表示X是从X0到X1范围内;

void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
    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);
    }
}

根据相似三角形,可以根据t在X0X1上的比例,计算得到y的值;这里仍然有精度的丢失,可以看到结果中图像直线上的缺角;同时,line函数中,X0必须小于等于X1,如果X0大于X1,就不会绘制线段了;

绘制三条线段:

line(13, 20, 80, 40, image, white); 
line(20, 13, 40, 80, image, red); 
line(80, 40, 13, 20, image, red);

结果如下:只绘制出了前两条线段,第三条线段没有绘制出来;

第一条线段看着正常,第二条线段就有点离散;因为在X方向上的距离小于在Y方向上的距离,每次X方向上步进1,Y方向步进就会大于1,造成了线段不连续;

改进:

  1. 判断线段在X方向上的距离和Y方向上的距离,如果Y方向上距离大于X方向上的距离,则点的X和Y互换,且标记steep为true;

  1. 如果X0大于X1,就互换X0和X1,Y0和Y1,永远使用较小的X开始遍历;

  1. 在set时,如果steep为true,就x和y互调;

void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
    bool steep = false;
    if (std::abs(x0 - x1) < std::abs(y0 - y1)) {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }
    if (x0 > x1) { // make it left−to−right 
        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;
        if (steep) {
            image.set(y, x, color);
        }
        else {
            image.set(x, y, color);
        }
    }
}

仍然是绘制出了两条线段,是因为第一条线段和第二条线段是同一条,后面绘制的红色线段把之前绘制的白色线段覆盖了;

绘制线段永远都是连续的,下一个绘制位置,应该在当前点(x,y)后的三个点位置,即(x+1,y)、(x+1,y-1)、(x+1,y+1),由于向上或者向下是有斜率k控制的,所以只需要关注两个点即可,是绘制在相同的y位置还是向上或者向下的位置;TinyRenderer从零实现(二):lesson 1 直线绘制算法里面讲的比较详细;相比之前的line函数,循环里主要工作从原来的除法和乘法变成了加法运算;

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) { 
    bool steep = false; 
    if (std::abs(x0-x1)<std::abs(y0-y1)) { 
        std::swap(x0, y0); 
        std::swap(x1, y1); 
        steep = true; 
    } 
    if (x0>x1) { 
        std::swap(x0, x1); 
        std::swap(y0, y1); 
    } 
    int dx = x1-x0; 
    int dy = y1-y0; 
    float derror = std::abs(dy/float(dx)); 
    float error = 0; 
    int y = y0; 
    for (int x=x0; x<=x1; x++) { 
        if (steep) { 
            image.set(y, x, color); 
        } else { 
            image.set(x, y, color); 
        } 
        error += derror; 
        if (error>.5) { 
            y += (y1>y0?1:-1); 
            error -= 1.; 
        } 
    } 
} 

为了进一步优化性能,则需要去掉0.5浮点运算和浮点除法;则将derror扩大2*dx倍,则相关联变量的比较和累加也要扩大2*dx倍,才能保证比较布尔运算的正确性;

则,原来的std::abs(dy/float(dx))就变成了std::abs(dy)*2,error>.5变成了error>dx,error -= 1.变成了error -= 2*dx;不会影响图像的正确性;

void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
    bool steep = false;
    if (std::abs(x0 - x1) < std::abs(y0 - y1)) {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }
    if (x0 > x1) {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }
    int dx = x1 - x0;
    int dy = y1 - y0;
    float derror = std::abs(dy)*2;
    float error = 0;
    int y = y0;
    for (int x = x0; x <= x1; x++) {
        if (steep) {
            image.set(y, x, color);
        }
        else {
            image.set(x, y, color);
        }
        error += derror;
        if (error > dx) {
            y += (y1 > y0 ? 1 : -1);
            error -= 2*dx;
        }
    }
}

2.4 绘制三角面

对应课程里的Lesson 2: Triangle rasterization and back face culling;

主要内容是,绘制一个三角形并填充颜色,是在点、线的基础上,绘制面;

在该节中,引入了几何结构2D点(向量)、3D点(向量),可以对这些向量进行归一化处理以及运算操作等等;

geometry.h内容如下:

template <class t> struct Vec2 {
    union {
        struct {t u, v;};
        struct {t x, y;};
        t raw[2];
    };
    Vec2() : u(0), v(0) {}
    Vec2(t _u, t _v) : u(_u),v(_v) {}
    inline Vec2<t> operator +(const Vec2<t> &V) const { return Vec2<t>(u+V.u, v+V.v); }
    inline Vec2<t> operator -(const Vec2<t> &V) const { return Vec2<t>(u-V.u, v-V.v); }
    inline Vec2<t> operator *(float f)          const { return Vec2<t>(u*f, v*f); }
    t operator [](int pos) const { return raw[pos]; }
    float norm() const { return std::sqrt(x * x + y * y); }
    Vec2<t>& normalize(t l = 1) { *this = (*this) * (l / norm()); return *this; }
    template <class > friend std::ostream& operator<<(std::ostream& s, Vec2<t>& v);
};

template <class t> struct Vec3 {
    union {
        struct {t x, y, z;};
        struct { t ivert, iuv, inorm; };
        t raw[3];
    };
    Vec3() : x(0), y(0), z(0) {}
    Vec3(t _x, t _y, t _z) : x(_x),y(_y),z(_z) {}
    inline Vec3<t> operator ^(const Vec3<t> &v) const { return Vec3<t>(y*v.z-z*v.y, z*v.x-x*v.z, x*v.y-y*v.x); }
    inline Vec3<t> operator +(const Vec3<t> &v) const { return Vec3<t>(x+v.x, y+v.y, z+v.z); }
    inline Vec3<t> operator -(const Vec3<t> &v) const { return Vec3<t>(x-v.x, y-v.y, z-v.z); }
    inline Vec3<t> operator *(float f)          const { return Vec3<t>(x*f, y*f, z*f); }
    inline t       operator *(const Vec3<t> &v) const { return x*v.x + y*v.y + z*v.z; }
    float norm () const { return std::sqrt(x*x+y*y+z*z); }
    Vec3<t> & normalize(t l=1) { *this = (*this)*(l/norm()); return *this; }
    template <class > friend std::ostream& operator<<(std::ostream& s, Vec3<t>& v);
};

typedef Vec2<float> Vec2f;
typedef Vec2<int>   Vec2i;
typedef Vec3<float> Vec3f;
typedef Vec3<int>   Vec3i;

template <class t> std::ostream& operator<<(std::ostream& s, Vec2<t>& v) {
    s << "(" << v.x << ", " << v.y << ")\n";
    return s;
}

template <class t> std::ostream& operator<<(std::ostream& s, Vec3<t>& v) {
    s << "(" << v.x << ", " << v.y << ", " << v.z << ")\n";
    return s;
}

修改line函数,将之前的xy坐标的方式,修改为传入Vec2的参数:

void line(Vec2i p0, Vec2i p1, TGAImage& image, TGAColor color) {
    bool steep = false;
    if (std::abs(p0.x - p1.x) < std::abs(p0.y - p1.y)) {
        std::swap(p0.x, p0.y);
        std::swap(p1.x, p1.y);
        steep = true;
    }
    if (p0.x > p1.x) {
        std::swap(p0, p1);
    }
    int dx = p1.x - p0.x;
    int dy = p1.y - p0.y;
    float derror = std::abs(dy)*2;
    float error = 0;
    int y = p0.y;
    for (int x = p0.x; x <= p1.x; x++) {
        if (steep) {
            image.set(y, x, color);
        }
        else {
            image.set(x, y, color);
        }
        error += derror;
        if (error > dx) {
            y += (p1.y > p0.y ? 1 : -1);
            error -= 2*dx;
        }
    }
}

绘制三角形函数triangle,其实就是绘制了组成三角形的三根线段:

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color) {
    line(t0, t1, image, color);
    line(t1, t2, image, color);
    line(t2, t0, image, color);
}

在画布上绘制三个空心的三角形:

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red   = TGAColor(255,   0,   0, 255);
const TGAColor green = TGAColor(0,   255,   0, 255);

int main(int argc, char** argv) {
    TGAImage image(200, 200, TGAImage::RGB);

    Vec2i t0[3] = { Vec2i(10, 70),   Vec2i(50, 160),  Vec2i(70, 80) };
    Vec2i t1[3] = { Vec2i(180, 50),  Vec2i(150, 1),   Vec2i(70, 180) };
    Vec2i t2[3] = { Vec2i(180, 150), Vec2i(120, 160), Vec2i(130, 180) };

    triangle(t0[0], t0[1], t0[2], image, red);
    triangle(t1[0], t1[1], t1[2], image, white);
    triangle(t2[0], t2[1], t2[2], image, green);

    image.flip_vertically();
    image.write_tga_file("output.tga");
    return 0;
}

将三角形的三个顶点按照Y坐标从小到大排序,三个值只需要比较交换三次就能排序完成;将三条三段颜色修改为Y方向上跨度最大线段为红色,其余两条颜色为绿色;

由于排序和交互后,t0.y<t1.y<t2.y;则t0->t2的线段为红色,其余两条为绿色;代码如下:

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color) {
    if (t0.y > t1.y) std::swap(t0, t1);
    if (t0.y > t2.y) std::swap(t0, t2);
    if (t1.y > t2.y) std::swap(t1, t2);
    line(t0, t1, image, green);
    line(t1, t2, image, green);
    line(t2, t0, image, red);
}

给三角形涂色,其实就是按照线扫描的方式,一行一行给坐标设置RGB值;此时的三角形中已经按照Y的大小进行了排序,那么依次从Y最小值到Y最大值遍历,每个Y值对应的X范围,即和两条线段的交点的X范围,就是要绘制的范围;根据t1的Y值,将三角形分为两部分,一部分在Y值的上方,另一部分在Y值的下方;

此时算法并不考虑性能问题,以实现功能为主:

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color) {
    // sort the vertices, t0, t1, t2 lower−to−upper (bubblesort yay!) 
    if (t0.y > t1.y) std::swap(t0, t1);
    if (t0.y > t2.y) std::swap(t0, t2);
    if (t1.y > t2.y) std::swap(t1, t2);
    int total_height = t2.y - t0.y;
    for (int y = t0.y; y <= t1.y; y++) {
        int segment_height = t1.y - t0.y + 1;
        float alpha = (float)(y - t0.y) / total_height;
        float beta = (float)(y - t0.y) / segment_height; // be careful with divisions by zero 
        Vec2i A = t0 + (t2 - t0) * alpha;
        Vec2i B = t0 + (t1 - t0) * beta;
        if (A.x > B.x) std::swap(A, B);
        for (int j = A.x; j <= B.x; j++) {
            image.set(j, y, color); // attention, due to int casts t0.y+i != A.y 
        }
    }
    for (int y = t1.y; y <= t2.y; y++) {
        int segment_height = t2.y - t1.y + 1;
        float alpha = (float)(y - t0.y) / total_height;
        float beta = (float)(y - t1.y) / segment_height; // be careful with divisions by zero 
        Vec2i A = t0 + (t2 - t0) * alpha;
        Vec2i B = t1 + (t2 - t1) * beta;
        if (A.x > B.x) std::swap(A, B);
        for (int j = A.x; j <= B.x; j++) {
            image.set(j, y, color); // attention, due to int casts t0.y+i != A.y 
        }
    }
}

绘制三角形两部分的代码部分逻辑基本上是一致的,只是在Y相关内容上有差别;如果要合并为一个循环,那么就得每次循环中增加判断,即判断当前Y是处于那个三角形部分;

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color) {
    if (t0.y == t1.y && t0.y == t2.y) return;

    if (t0.y > t1.y) std::swap(t0, t1);
    if (t0.y > t2.y) std::swap(t0, t2);
    if (t1.y > t2.y) std::swap(t1, t2);

    int total_height = t2.y - t0.y;

    for (int i = 0; i < total_height; i++) {
        bool second_half = i > t1.y - t0.y || t1.y == t0.y;
        int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
        float alpha = (float)i / total_height;
        float beta = (float)(i - (second_half ? t1.y - t0.y : 0)) / segment_height;
        Vec2i A = t0 + (t2 - t0) * alpha;
        Vec2i B = second_half ? t1 + (t2 - t1) * beta : t0 + (t1 - t0) * beta;
        if (A.x > B.x) std::swap(A, B);
        for (int j = A.x; j <= B.x; j++) {
            image.set(j, t0.y + i, color);
        }
    }
}

至此,面绘制的功能已经实现了,当场景中有成千上万个三角形要绘制时,如何提高性能也是一个关键点;图形学里一般都会使用并行计算提高计算效率;绘制实心三角形如何提高性能呢?

按照CUDA的思路,绘制三角形就是对三角形边上和内部的像素点进行判断,是否处于三角形上;可以使用大量的线程分别处理每个像素点的计算;按照这个思路走下去,即可以找到三角形的AABB包围盒,只对包围盒内的像素点进行计算,就会节省大量的线程资源;

实现逻辑是:

  1. 找到三角形的AABB包围盒;

  1. 包围盒内的点是否在三角形内,如果在三角形内,则设置RGB值;

这部分内容在TinyRenderer从零实现(三):lesson 2 三角形光栅化和背面剔除中相关知识点讲的特别清楚,不懂的可以去看看;

完整代码如下:

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]);
    /* `pts` and `P` has integer value as coordinates
       so `abs(u[2])` < 1 means `u[2]` is 0, that means
       triangle is degenerate, in this case return something with negative coordinates */
    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* pts, TGAImage& image, TGAColor color) {
    Vec2i bboxmin(image.get_width() - 1, image.get_height() - 1);
    Vec2i bboxmax(0, 0);
    Vec2i clamp(image.get_width() - 1, image.get_height() - 1);
    for (int i = 0; i < 3; i++) {
        bboxmin.x = std::max(0, std::min(bboxmin.x, pts[i].x));
        bboxmin.y = std::max(0, std::min(bboxmin.y, pts[i].y));

        bboxmax.x = std::min(clamp.x, std::max(bboxmax.x, pts[i].x));
        bboxmax.y = std::min(clamp.y, std::max(bboxmax.y, pts[i].y));
    }
    Vec2i P;
    for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++) {
        for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++) {
            Vec3f bc_screen = barycentric(pts, P);
            if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0) continue;
            image.set(P.x, P.y, color);
        }
    }
}

int main(int argc, char** argv) {
    TGAImage frame(200, 200, TGAImage::RGB);
    Vec2i pts[3] = { Vec2i(10,10), Vec2i(100, 30), Vec2i(190, 160) };
    triangle(pts, frame, TGAColor(255, 0, 0, 255));
    frame.flip_vertically(); // to place the origin in the bottom left corner of the image 
    frame.write_tga_file("framebuffer.tga");
    return 0;
}

结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/172217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ThreeDXF预览DXF文件集成到vue项目中

由于网上资料都是html的&#xff0c;而自己需要嵌入到vue项目中&#xff0c;查找资料都是在index.html引入script脚本&#xff0c;在写到Vue文件中&#xff0c;但是我尝试过了&#xff0c;各种报错&#xff0c;找不到&#xff0c;window. 根本无法用&#xff0c;于是改注入main…

主动服务再升级!这个品牌引领智慧生活进入“深度体验”

文|智能相对论作者| 佘凯文1月15日&#xff0c;一档央视新闻的新概念科技节目《KU A &#xff01;酷啊未来 | 中国科技创新之夜》正式播出&#xff0c;来自中国科学院的多领域顶级科学家及许多科技企业、青年科研人员代表&#xff0c;共同分享了科技创新之路上的成果和突破。不…

EDI文件处理失败如何汇总?

知行之桥EDI系统在后台自动运行的时候&#xff0c;有时会遇到处理文件失败的情况&#xff0c;导致失败的原因有很多&#xff0c;部分客户希望把处理失败的文件都汇总起来&#xff0c;便于分析失败原因&#xff0c;减少未来再出现类似的错误&#xff0c;同时也能够方便后期排查&…

ERD Online 4.0.7 在线数据库建模、元数据管理(免费、私有部署)

4.0.7❝ feat(erd): 增加新春火红主题feat(erd): 增加团队协作人员进入、退出提示fix(erd): 修复权限配置页面显示混乱doc(erd): 修改更新通告地址❞变化一览 增加新春火红主题 新春主题所有按钮、菜单、元素由原来的蓝色改为火红色修复权限配置页面显示混乱 团队功能增加团队协…

【算法基础】快速排序

目录 一、快速排序核心思想 二、快速排序步骤 (1)暴力做法 (2)双指针做法 三、代码模板 四、边界问题 五、总结 一、快速排序核心思想 分治&#xff0c;即将一个序列划分成左部分小于等于x,右部分大于等于x 二、快速排序步骤 ①确定一个分界点x。分界点可以是左端 a[l]、右…

【Linux】两个故事带你使用git命令行

目录一.历史故事背景经过git的诞生二.git版本管理1.小故事2.理解版本管理三.git的使用1.仓库的创建2.安装git和仓库克隆3.上传代码三板斧addcommitpushgithub和gitee是代码的托管平台&#xff0c;我们上传代码或文件在其中&#xff0c;来管理我们的代码和不同版本软件。 在多人…

【操作系统】——主流的操作系统(带你快速了解)

&#x1f4dc; “作者 久绊A” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴。 &#x1f341; 操作系统【带你快速了解】对于电脑来说&#xff0c;如果说…

【Java IO流】字符集使用详解

文章目录前言ASCIIGBKUnicode为什么会出现乱码前言 上一节关于字节流的文章中&#xff0c;在使用字节流读取本地文件中的数据时&#xff0c;文件中只存放了英文&#xff0c;而并没有存放中文数据。我们还提到了不建议使用字节流读取纯文本文件的数据&#xff0c;否则会出现乱码…

Elasticsearch7.8.0版本高级查询—— 匹配查询文档

目录一、初始化文档数据二、匹配查询文档示例2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; {"name":"张三","age&…

浅谈php原生类的利用 2(ErrorSoapClientSimpleXMLElement)

除了上篇文章浅谈 php原生类的利用 1(文件操作类)_php spl原生类_葫芦娃42的博客-CSDN博客 里提到的原生利用文件操作类读文件的功能&#xff0c;在CTF题目中&#xff0c;还可以利用php原生类来进行XSS,反序列化&#xff0c;SSRF&#xff0c;XXE。 常用内置类&#xff1a; Dire…

【SAP Abap】X档案:SAP Native SQL 简介及实现方式(EXEC SQL、ADBC、AMDP)

SAP Native SQL 简介及实现方式&#xff08;EXEC SQL、ADBC、AMDP&#xff09;1、SAP Open SQL 与 Native SQL 的特点2、Native SQL 的实现方式方式一&#xff1a;Exec SQL&#xff08;1&#xff09;获取单值&#xff08;2&#xff09;获取多行&#xff08;3&#xff09;游标应…

TCP协议的长连接和短连接详解

一 前言TCP在真正开始进行数据传输之前&#xff0c;Server 和 Client 之间必须建立一个连接。当数据传输完成后&#xff0c;双方不再需要这个连接时&#xff0c;就可以释放这个连接。TCP连接的建立是通过三次握手&#xff0c;而连接的释放是通过四次挥手。所以说&#xff0c;每…

【SpringCloud】Eureka的基本原理与使用

【SpringCloud】Eureka的基本原理与使用 一、Eureka-提供者与消费者 【问】如果服务A调用了服务B&#xff0c;而服务B又调用了服务C&#xff0c;服务B的角色是什么&#xff1f; 二、Eureka的结构和作用 什么是Eureka&#xff1f; Eureka 解决服务调用的问题 order-servic…

博物馆3d数字化全景展示设计方案

作为近几年新兴的营销方式&#xff0c;交互式营销能够让消费者对产品从主动感兴趣到互动体验&#xff0c;甚至自主自发传播&#xff0c;达到“在销售中传播&#xff0c;在传播中销售”的目的。进入数字体验经济时代&#xff0c;当3d数字化展示技术遇上传统行业&#xff0c;3d数…

Redis原理篇(三)通信协议

一、RESP协议 1、定义 Redis是一个cs架构的软件&#xff0c;通信一般分两步&#xff1a; 客户端client向服务端server发送一条命令服务端解析并执行命令&#xff0c;返回响应结果给客户端 因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范&#xff0c;这个规…

HashMap源码学习:JDK1.8版本源码解析

文章导航 HashMap源码学习&#xff1a;红黑树原理详解 HashMap源码学习&#xff1a;JDK1.8版本源码解析 目录文章导航前言正文HashMap重要属性HashMap构造方法HashMap扩容方法HashMap链表迁移HashMap红黑树迁移HashMap链表转红黑树HashMap红黑树转链表HashMap添加数据HashMap移…

让你彻底明白Java SPI与SpringBoot自动配置,内附实例代码演示

一、Java SPI的概念和术语 SPI&#xff1a;全称是Service Provider Interface&#xff1a;是一种基于ClassLoader来发现并加载服务的机制 SPI由三个组件构成&#xff1a;Service、Service Provider、ServiceLoader Service&#xff1a;是一个公开的接口或抽象类&#xff0c;定…

数说菊风2022

春风传捷报&#xff0c; 梅韵贺新年&#xff01; 2022&#xff0c;已悄然划过&#xff0c; 就让我们用数字说话&#xff0c; 述说这年的精彩&#xff01; 树十大标杆案例 国际运营商战略合作——Telkomsel 携手印尼运营商Telkomsel在RCS融合通信和RTC实时音视频领域形成合…

golang入门笔记——kitex

WSL的安装 由于Kitex并不支持Linux&#xff0c;所以需要首先安装WSL2 WSL一句话来说就是微软出的一个虚拟机工具 Win11下安装WSL2的步骤为&#xff1a; 1.“开始菜单”搜索功能&#xff0c;打开“启动或关闭Window功能” 2.勾选以下功能 1.适用于Linux的Window子系统 2.虚…

qsort函数

目录1.什么是qsort函数2.实现一个qsort函数3.用qsort函数排序一个结构体4.模仿qsort的功能实现一个通用的冒泡排序1.什么是qsort函数 我们以前学习过的一些排序算法&#xff0c;如冒泡、希尔、快排等等&#xff0c;它们速度有快有满&#xff0c;但是这些排序都只能排序一种类型…