【OpenGL】计算机图形学实验四: 直线、多边形裁剪实验(直线和多边形的裁剪)

本文详细介绍了二维图形裁剪的Cohen-Sutherland和Liang-Barsky算法的原理及实现。通过模拟矩形窗口,展示了这两种算法如何对线段和多边形进行裁剪。Cohen-Sutherland算法利用编码判断点的位置,Liang-Barsky算法基于直线参数方程计算交点。实验结果以图形形式呈现了裁剪过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实验四: 直线、多边形裁剪实验

(直线和多边形的裁剪)

1、实验目的和要求

实验目的:

  1. 理解二维图形裁剪的算法原理;
  2. 掌握Cohen-SutherLand裁剪算法、Liang-Barsky裁剪算法;
  3. 了解OpenGL程序实现SH多边形裁剪算法;
  4. 了解OpenGL程序实现Weiler-Atherton多边形裁剪算法。

实验内容:

  1. 用一个矩形窗口模拟裁剪多变形,对线段进行Cohen-Sutherland裁减。
  2. 用一个矩形窗口模拟裁剪多变形,对线段进行Liang-Barsky裁减。
  3. 用OpenGL程序实现SH多边形裁剪算法
  4. 用OpenGL程序实现Weiler-Atherton多边形裁剪算法

2、实验设备

PC机、CodeBlocks\VS系列\OpenGL安装包

3、实验内容及原理

实验原理(基本知识)

1)    Cohen-Sutherland裁剪算法

又称编码裁剪算法,对每条直线段P_1\left( x_1,y_1 \right) ,P_2\left( x_2,y_2 \right)分三种情况处理:
①    如果点P1, P2完全在裁剪窗口内,则该直线段完全可见,“简取”之;
②    如果点P1, P2都在窗口外,且在窗口的同一外侧,则该直线段完全不可见,“简弃”之;
③    若不满足“简取”和“简弃”的条件,则直线段可能与窗口相交,此时对直线段按交点进行分段,分段后重复上述处理。

2)    Liang-Barsky裁剪算法
从直线的参数方程出发,计算pk、qk和uk。
对于pk=0的情况,u_{max}=\max \left\{ 0, u_k|_{p_k<0} \right\}u_{min}=\min \left\{ u_k|_{p_k>0},1 \right\}。若u_{max}\le u_{min}时,将其代入参数方程式。
对于pk≠0的情况,Liang-Barsky算法将直线段与窗口边界的实交点和虚交点分为两组,下限组以pk<0为特征,表示在该处直线段从裁剪边界延长线的外部延伸到内部;上限组以pk>0为特征,表示在该处直线段从裁剪边界延长线的内部延伸到外部。在有交点的情况下,下限组分布于直线段的起点一侧,上限组则分布于直线段终点一侧,则下限组的最大值和上限值的最小值就分别对应于直线段在窗口内部分的端点。
3)    Sutherland-Hodgeman多边形裁剪算法
又称逐边裁剪算法,其基本思想是将多边形的边界作为一个整体,每次用窗口的一条边界对要裁剪的多边形进行裁剪,体现“分而治之”的思想。每次裁剪时把落在窗口外部区域的图形去掉,只保留落在窗口内部区域的图形,并把它作为下一次裁剪的多边形。依次用窗口的4条边界对多边形进行裁剪,则原始多边形即被裁剪完毕。
4)    Weiler-Atherton多边形裁剪算法
双边裁剪算法,可用于任意凸的和凹的多边形裁剪。
假定按顺时针方向处理顶点,且将用户多边形定义为Ps,窗口矩形为Pw。算法从Ps的任一点出发,跟踪检测P的每一条边,当Ps与Pw相交时(实交点),按如下规则处理:
①    若是由窗口外进入窗口内,则输出可见直线段,转③。
②    若是由窗口内到窗口外,则从当前交点开始,沿窗口边界顺时针检测Pw的边,即用窗口的有效边界去裁剪Ps的边,找到Ps与Pw最靠近当前交点的另一交点。输出可见直线段和由当前交点到另一交点之间窗口边界上的线段,然后返回处理的当前交点。
③    沿着Ps处理各条边,直到处理完Ps的每一条边,回到起点。

4、实验源程序代码、运行结果

4.1 Cohen-Sutherland裁剪算法

4.1.1源程序代码

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <vector>
#include <string>

using namespace std;

string getCode(int x, int y, vector<double> &w) {
    int yt = w[0], yb = w[1], xl = w[2], xr = w[3];
    string ans = "0000";
    if (y > yt) { // top
        ans[0] = '1';
    }
    if (y < yb) { // bottom
        ans[1] = '1';
    }
    if (x > xr) { // right
        ans[2] = '1';
    }
    if (x < xl) { // left
        ans[3] = '1';
    }

    return ans;
}

void drawLine(vector<double> p1, vector<double> p2) {
    glColor3f(0.0f, 0.0f, 0.0f);
    glLineWidth(4);
    glBegin(GL_LINES);
        glVertex2d(p1[0], p1[1]);
        glVertex2d(p2[0], p2[1]);
    glEnd();
}

int XOR(string code1, string code2) { // 按位或
    for (int i = 0; i < 4; i++) {
        if (code1[i] == '1' || code2[i] == '1') {
            return 1;
        }
    }
    return 0;
}

int BAND(string code1, string code2) { // 按位与
    for (int i = 0; i < 4; i++) {
        if (!(code1[i] == '1' && code2[i] == '1')) {
            return 0;
        }
    }
    return 1;
}

void CS_Cropping(vector<double> p1, vector<double> p2, vector<double> w) {
    double yt = w[0], yb = w[1], xl = w[2], xr = w[3];
    vector<double> s(2);

    string code1 = getCode(p1[0], p1[1], w), code2 = getCode(p2[0], p2[1], w);
//  cout << code1 << ' ' << code2 << endl;
    while (XOR(code1, code2) != 0) {
        // cout << code1 << ' ' << code2 << endl;
        if (BAND(code1, code2) != 0) { // 简弃之
            return;
        }

        if (code1 == "0000") {
            swap(code1, code2);
            swap(p1, p2);
        }

        int pos = -1; // 0:top, 1:bottom, 2:right, 3:left
        for (int i = 3; i >= 0; i--) {
            if (code1[i] == '1') {
                pos = i;
                break;
            }
        }

        s = vector<double>(2); // s(x, y)

        if (pos < 2) { // top || bottom
            if (pos == 0) { // top
                s[1] = yt;
            } else { // bottom
                s[1] = yb;
            }
            double k = (p2[1] - p1[1]) / (p2[0] - p1[0]);
            // double k = (y2 - y1) / (x2 - x1);
            s[0] = p1[0] + (s[1] - p1[1]) / k;
            // s[0] = x1 + (s[1] - y1) / k;
        } else { // right || left
            if (pos == 2) {
                s[0] = xr;
            } else {
                s[0] = xl;
            }
            double k = (p2[1] - p1[1]) / (p2[0] - p1[0]);
            // double k = (y2 - y1) / (x2 - x1);
            s[1] = p1[1] + k * (s[0] - p1[0]);
            // s[1] = y1 + k * (s[0] - x1);
        }

        swap(p1, s);

        code1 = getCode(p1[0], p1[1], w), code2 = getCode(p2[0], p2[1], w);
    }

    if (XOR(code1, code2) == 0) { // 简取之
        // drawLine
         drawLine(p1, p2);
        // cout << "completed successfully" << endl;
    }
}

void resize(int w, int h) {
    if (h == 0) {
        h = 1;
    }

    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (w <= h) {
        glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
    } else {
        glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3d(1, 0, 0);

    // 用 w 来表示裁剪窗口
    vector<double> p1(2), p2(2), w(4);
    p1[0] = -56.0f, p2[0] = 76.0f;
    p1[1] = 10.0f, p2[1] = -50.0f;
    // yt = w[0], yb = w[1], xl = w[2], xr = w[3]
    w[0] = 50.0f, w[1] = -50.0f, w[2] = -50.0f, w[3] = 50.0f;

    // 绘制窗口
    glColor3f(1.0f, 0.0f, 0.0f);
    glLineWidth(2);
    glBegin(GL_LINE_LOOP);
        glVertex2d(w[0], w[1]);
        glVertex2d(w[1], w[2]);
        glVertex2d(w[2], w[3]);
        glVertex2d(w[0], w[3]);
    glEnd();

    // 绘制原始的直线
    glColor3f(0, 0, 1);
    glLineWidth(3);
    glBegin(GL_LINES);
        glVertex2d(p1[0], p1[1]);
        glVertex2d(p2[0], p2[1]);
    glEnd();

    // 裁剪
    CS_Cropping(p1, p2, w);

    glutSwapBuffers();
}

int main(int argc, char *argv[]) {
    glutInit(&argc, argv);
    glutInitWindowSize(400, 400);
    glutInitWindowPosition(10,10);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);

    glutCreateWindow("CS Cropping");

    glutReshapeFunc(resize);
    glutDisplayFunc(display);

    glClearColor(1,1,1,1);

    glutMainLoop();

    return 0;
}

4.1.2运行结果

图中,黑色加粗部分为裁剪后的,蓝色部分是被裁剪去的,红色矩形表示模拟的裁剪窗口。

4.2 Liang-Barsky算法

4.2.1源代码

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>

using namespace std;

int LBLineClipTest(float p, float q, float &umax, float &umin) {
    float r = 0.0f;
    if (p < 0.0) {
        r = q / p;
        if (r > umin) {
            return 0;
        } else if (r > umax) {
            umax = r;
        }
    } else if (p > 0.0) {
        r = q / p;
        if (r < umax) {
            return 0;
        } else if (r < umin) {
            umin = r;
        }
    } else if (q < 0.0) {
        return 0;
    }
    return 1;
}

// drawPixel
void drawPixel(int x, int y) {
    glBegin(GL_POINTS);
        glVertex2i(x, y);
    glEnd();
}

// BresenhamLine
void BresenhamLine(vector<int> p0, vector<int> p1) {
    int x0 = p0[0], y0 = p0[1],
        x1 = p1[0], y1 = p1[1];

    // 两种特殊情况: x0 == x1 || y0 == y1
    if (x0 == x1) {
        if (y0 > y1) {
            swap(y0, y1);
        }
        for (int i = y0; i <= y1; i++) {
            drawPixel(x0, i);
        }
        return ;
    } else if (y0 == y1) {
        if (x0 > x1) {
            swap(x0, x1);
        }
        for (int i = x0; i <= x1; i++) {
            drawPixel(i, y0);
        }
        return ;
    }

    int kRev = (y1 - y0) / (x1 - x0);

    bool isKGT1 = false; // 1 : K is greater than 1
    bool isKNeg = (y1 - y0) * (x1 - x0) >= 0 ? false : true; // 1 : K is negative
    if (abs(kRev) >= 1) { // |k| > 1
        swap(x0, y0);
        swap(x1, y1);
        isKGT1 = true;
    }

    if (isKGT1) {
        if (isKNeg) {
            if (y0 < y1) {
                swap(y0, y1);
                swap(x0, x1);
            }
        } else {
            if (y0 > y1) {
                swap(y0, y1);
                swap(x0, x1);
            }
        }

    } else {
        if (x0 > x1) {
            swap(y0, y1);
            swap(x0, x1);
        }
    }

    int x = x0, y = y0;
    int dx = x1 - x0, dy = y1 - y0;
    if (isKNeg) {
        dy = -dy;
    }

    int e = -dx;

    while (x <= x1) {
        if (isKGT1) {
            drawPixel(y, x);
        } else {
            drawPixel(x, y);
        }
        x++;
        e += 2 * dy;

        if (e > 0) {
            if (isKNeg) {
                y--;
            } else {
                y++;
            }
            e -= 2 * dx;
        }
    }

}

void LB_Cropping(float xwl, float xwr, float ywb, float ywt, float x1, float y1, float x2, float y2) {
    float deltax = x2 - x1,
          deltay = y2 - y1,
          umax   = 0.0f,
          umin   = 1.0f;
    if (LBLineClipTest(-deltax, x1 - xwl, umax, umin)) {            // 左边界
        if (LBLineClipTest(deltax, xwr - x1, umax, umin)) {         // 右边界
            if (LBLineClipTest(-deltay, y1 - ywb, umax, umin)) {    // 下边界
                int tempx1 = x1, tempy1 = y1; // 保存临时值
                if (LBLineClipTest(deltay, ywt - y1, umax, umin)) { // 上边界
                    x1 = int(tempx1 + umax * deltax + 0.5);
                    y1 = int(tempy1 + umax * deltay + 0.5);
                    x2 = int(tempx1 + umin * deltax + 0.5);
                    y2 = int(tempy1 + umin * deltay + 0.5);
                }
                vector<int> p1(2), p2(2);
                p1[0] = int(x1), p1[1] = int(y1);
                p2[0] = int(x2), p2[1] = int(y2);
                BresenhamLine(p1, p2);
            }
        }
    }
}

void resize(int w, int h) {
    if (h == 0) {
        h = 1;
    }

    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (w <= h) {
        glOrtho(0.0f, 400.0f, 0.0f, 400.f * h / w, 1.0f, -1.0f);
    } else {
        glOrtho(0.0f, 400.0f * w / h, 0.0f, 400.0f, 1.0f, -1.0f);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3d(1, 0, 0);

    // 用 w 来表示裁剪窗口
    vector<float> p1(2), p2(2), w(4);
    p1[0] = 1.0f, p1[1] = 1.0f;
    p2[0] = 200.0f, p2[1] = 300.0f;
    // yt = w[0], yb = w[1], xl = w[2], xr = w[3]
    w[0] = 200, w[1] = 10, w[2] = 10.0f, w[3] = 200.0f;

    // 绘制窗口
    glColor3f(1.0f, 0.0f, 0.0f);
    glLineWidth(2);
    glBegin(GL_LINE_LOOP);
        glVertex2d(w[0], w[1]);
        glVertex2d(w[1], w[2]);
        glVertex2d(w[2], w[3]);
        glVertex2d(w[0], w[3]);
    glEnd();

    // 绘制原始的直线
    glColor3f(0.8, 0.8, 0.8);
    glLineWidth(2);
    vector<int> pi1(2), pi2(2);
    pi1[0] = int(p1[0]), pi1[1] = int(p1[1]);
    pi2[0] = int(p2[0]), pi2[1] = int(p2[1]);
    BresenhamLine(pi1, pi2);

    // 裁剪
    glColor3f(1, 0, 0);
    glLineWidth(5);
    LB_Cropping(10, 200, 10, 200, p1[0], p1[1], p2[0], p2[1]);

    glutSwapBuffers();
}

int main(int argc, char *argv[]) {
    glutInit(&argc, argv);
    glutInitWindowSize(400, 400);
    glutInitWindowPosition(10,10);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);

    glutCreateWindow("LB Cropping");

    glutReshapeFunc(resize);
    glutDisplayFunc(display);

    glClearColor(1,1,1,1);

    glutMainLoop();

    return 0;
}

4.2.2运行结果

浅灰色部分表示被裁剪去的部分,红色部分直线表示裁剪后的结果。红色矩形表示模拟的裁剪窗口。

4.3 Sutherland-Hodgeman多边形裁剪算法

4.3.1源代码

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>

using namespace std;

struct Point {
    double x, y;
};

void resize(int w, int h) {
    if (h == 0) {
        h = 1;
    }

    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (w <= h) {
        glOrtho(0.0f, 400.0f, 0.0f, 400.f * h / w, 1.0f, -1.0f);
    } else {
        glOrtho(0.0f, 400.0f * w / h, 0.0f, 400.0f, 1.0f, -1.0f);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/**
 *
 * @param A
 * @param B
 * @param type 0: l, 1 : b, 2: r, 3: t
 * @return
 */
Point clip(Point A, Point B, int type, int val) {
    double deltaX = A.x - B.x, deltaY = A.y - B.y;
    // y = kx + b;
    /*
     * k = deltay / deltax;
     * b = ya - k * xa
     * x = (y - b) / k;
     */
    double k = deltaY / deltaX;
    double b = A.y - k * A.x;
    Point ans;
    if (type == 0 || type == 2) {
        ans.x = val;
        if (deltaY == 0) {
            ans.y = A.y;
        } else {
            ans.y = k * val + b;
        }
    } else if (type == 1 || type == 3) {
        ans.y = val;
        if (deltaX == 0) {
            ans.x = A.x;
        } else {
            ans.x = (ans.y - b) / k;
        }
    }
    return ans;
}

void SHPolygonClip(vector<Point> &input, int x, int y, int width, int height) {
    // left, _x = x
    vector<Point> afterLeft;
    for (unsigned int i = 0; i < input.size(); i++) {
        if (i == input.size() - 1) {
            Point A = input[i], B = input[0];
            if (A.x < x && B.x < x) {
                continue ;
            } else if (A.x >= x && B.x >= x) {
                afterLeft.push_back(B);
            } else {
                Point C = clip(A, B, 0, x);
                if (B.x < x) {
                    afterLeft.push_back(C);
                } else if (A.x < x) {
                    afterLeft.push_back(C);
                    afterLeft.push_back(B);
                }
            }
        } else {
            Point A = input[i], B = input[i + 1];
            if (A.x < x && B.x < x) {
                continue ;
            } else if (A.x >= x && B.x >= x) {
                afterLeft.push_back(B);
            } else {
                Point C = clip(A, B, 0, x);
                if (B.x < x) {
                    afterLeft.push_back(C);
                } else if (A.x < x) {
                    afterLeft.push_back(C);
                    afterLeft.push_back(B);
                }
            }
        }
    }

    // bottom, _y = y
    vector<Point> afterBottom;
    for (unsigned int i = 0; i < afterLeft.size(); i++) {
        if (i == afterLeft.size() - 1) {
            Point A = afterLeft[i], B = afterLeft[0];
            if (A.y < y && B.y < y) {
                continue ;
            } else if (A.y >= y && B.y >= y) {
                afterBottom.push_back(B);
            } else {
                Point C = clip(A, B, 1, y);
                if (B.y < y) {
                    afterBottom.push_back(C);
                } else if (A.y < y) {
                    afterBottom.push_back(C);
                    afterBottom.push_back(B);
                }
            }
        } else {
            Point A = afterLeft[i], B = afterLeft[i + 1];
            if (A.y < y && B.y < y) {
                continue ;
            } else if (A.y >= y && B.y >= y) {
                afterBottom.push_back(B);
            } else {
                Point C = clip(A, B, 1, y);
                if (B.y < y) {
                    afterBottom.push_back(C);
                } else if (A.y < y) {
                    afterBottom.push_back(C);
                    afterBottom.push_back(B);
                }
            }
        }
    }

    // right, _x = x + width
    x += width;
    vector<Point> afterRight;
    for (unsigned int i = 0; i < afterBottom.size(); i++) {
        if (i == afterBottom.size() - 1) {
            Point A = afterBottom[i], B = afterBottom[0];
            if (A.x > x && B.x > x) {
                continue;
            } else if (A.x <= x && B.x <= x) {
                afterRight.push_back(B);
            } else {
                Point C = clip(A, B, 2, x);
                if (B.x > x) {
                    afterRight.push_back(C);
                } else if (A.x > x) {
                    afterRight.push_back(C);
                    afterRight.push_back(B);
                }
            }
        } else {
            Point A = afterBottom[i], B = afterBottom[i + 1];
            if (A.x > x && B.x > x) {
                continue;
            } else if (A.x <= x && B.x <= x) {
                afterRight.push_back(B);
            } else {
                Point C = clip(A, B, 2, x);
                if (B.x > x) {
                    afterRight.push_back(C);
                } else if (A.x > x) {
                    afterRight.push_back(C);
                    afterRight.push_back(B);
                }
            }
        }
    }

    // top, _y = y + height
    y += height;
    vector<Point> afterTop;
    for (unsigned int i = 0; i < afterRight.size(); i++) {
        if (i == afterRight.size() - 1) {
            Point A = afterRight[i], B = afterRight[0];
            if (A.y > y && B.y > y) {
                continue;
            } else if (A.y <= y && B.y <= y) {
                afterTop.push_back(B);
            } else {
                Point C = clip(A, B, 3, y);
                if (B.y > y) {
                    afterTop.push_back(C);
                } else if (A.y > y) {
                    afterTop.push_back(C);
                    afterTop.push_back(B);
                }
            }
        } else {
            Point A = afterRight[i], B = afterRight[i + 1];
            if (A.y > y && B.y > y) {
                continue;
            } else if (A.y <= y && B.y <= y) {
                afterTop.push_back(B);
            } else {
                Point C = clip(A, B, 3, y);
                if (B.y > y) {
                    afterTop.push_back(C);
                } else if (A.y > y) {
                    afterTop.push_back(C);
                    afterTop.push_back(B);
                }
            }
        }
    }

    // draw
    glBegin(GL_POLYGON);
    for (unsigned int i = 0; i < afterTop.size(); i++) {
        glVertex2d(afterTop[i].x, afterTop[i].y);
    }
    glEnd();

}

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3d(1, 0, 0);

    vector<Point> points;
    Point p[8];
    // 0-200 0-200
    p[0].x = 105, p[0].y = 105;
    p[1].x = 170, p[1].y = 20;
    p[2].x = 320, p[2].y = 170;
    p[3].x = 350, p[3].y = 310;
    p[4].x = 170, p[4].y = 350;

//    p[5].x = 350, p[5].y = 310;
//    p[6].x = 350, p[6].y = 310;

    // borders
    glColor3f(0, 0, 0);
    glBegin(GL_LINE_LOOP);
        glVertex2i(40, 40);
        glVertex2i(40, 300);
        glVertex2i(300, 300);
        glVertex2i(300, 40);
    glEnd();

    // origin
    glColor3f(0.9, 0.9, 0.9);
    glBegin(GL_POLYGON);
    for (int i = 0; i < 5; i++) {
        glVertex2d(p[i].x, p[i].y);
    }
    glEnd();

    for (int i = 0; i < 5; i++) {
        points.push_back(p[i]);
    }

    glColor3f(0, 0, 1);
    SHPolygonClip(points, 40, 40, 260, 260);

    glutSwapBuffers();
}

int main(int argc, char *argv[]) {
    glutInit(&argc, argv);
    glutInitWindowSize(400, 400);
    glutInitWindowPosition(10, 10);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);

    glutCreateWindow("SH Polygon Clip");

    glutReshapeFunc(resize);
    glutDisplayFunc(display);

    glClearColor(1,1,1,1);

    glutMainLoop();

    return 0;
}

 

4.3.2运行结果

浅灰色部分表示被裁剪去的部分,蓝色部分表示被保留的部分,黑色矩形是模拟的裁剪窗口。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值