当前位置:首页 > 芯闻号 > 充电吧
[导读]在这一课里,我们将添加光照和键盘控制,它让程序看起来更美观。现在设置4个变量来控制绕x轴和y轴旋转角度的步长,以及绕x轴和y轴的旋转速度。另外还创建了一个z变量来控制进入屏幕深处的距离。并添加一个布尔

在这一课里,我们将添加光照和键盘控制,它让程序看起来更美观。

现在设置4个变量来控制绕x轴和y轴旋转角度的步长,以及绕x轴和y轴的旋转速度。另外还创建了一个z变量来控制进入屏幕深处的距离。并添加一个布尔型变量light来控制光源的开和关。

bool light;              // 点击“L”键开关光源
GLfloat	xrot;		     // X 旋转
GLfloat	yrot;			// Y 旋转
GLfloat xspeed;			// X 旋转速度
GLfloat yspeed;			// Y 旋转速度
GLfloat	z=-5.0f;	   // 深入屏幕的距离

接着设置用来创建光源的数组。我们将使用两种不同的光。第一种称为环境光。环境光来自于四面八方。所有场景中的对象都处于环境光的照射中。第二种类型的光源叫做漫射光。漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照射下的任何对象表面都变得很亮,而几乎未被照射到的区域就显得要暗一些。这样在我们所创建的木板箱的棱边上就会产生的很不错的阴影效果。
创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alpha通道参数。
因此,下面的代码我们得到的是半亮(0.5f)的白色环境光。如果没有环境光,未被漫射光照到的地方会变得十分黑暗。

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; 	// 环境光参数

下一行代码我们生成最亮的漫射光。所有的参数值都取成最大值1.0f。它将照在我们木板箱的前面,看起来挺好。

GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };	  // 漫射光参数

最后我们保存光源的位置。前三个参数和glTranslate中的一样。依次分别是XYZ轴上的位移。
由于我们想要光线直接照射在木箱的正面,所以XY轴上的位移都是0.0f。第三个值是Z轴上的位移。为了保证光线总在木箱的前面,所以我们将光源的位置朝着观察者(就是您哪。)挪出屏幕。我们通常将屏幕也就是显示器的屏幕玻璃所处的位置称作Z轴的0.0f点。所以Z轴上的位移最后定为2.0f。假如您能够看见光源的话,它就浮在您显示器的前方。当然,如果木箱不在显示器的屏幕玻璃后面的话,您也无法看见箱子。
最后一个参数取为1.0f。这将告诉OpenGL这里指定的坐标就是光源的位置,以后的教程中我会多加解释。

GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };	// 光源位置

filter 变量跟踪显示时所采用的纹理类型。第一种纹理(texture 0) 使用gl_nearest(近邻滤波)方式构建。第二种纹理 (texture 1) 使用gl_linear(线性滤波) 方式,离屏幕越近的图像看起来就越光滑。第三种纹理 (texture 2) 使用mipmapped滤波方式,这将创建一个外观十分优秀的纹理。根据我们的使用类型,filter 变量的值分别等于 0, 1 或 2 。下面我们从第一种纹理开始。
GLuint texture[3] 为三种不同纹理分配储存空间。它们分别位于在 texture[0], texture[1] 和 texture[2]中。

GLuint filter;			// 滤波类型
GLuint texture[3];	// 3种纹理的储存空间

lesson6.h

#ifndef LESSON6_H
#define LESSON6_H

#include#include#includeclass QPainter;
class QOpenGLContext;
class QOpenGLPaintDevice;

class Lesson6 : public QWindow, QOpenGLFunctions_1_1
{
    Q_OBJECT
public:
    explicit Lesson6(QWindow *parent = 0);
    ~Lesson6();

    virtual void render(QPainter *);
    virtual void render();
    virtual void initialize();

public slots:
    void renderNow();

protected:
    void exposeEvent(QExposeEvent *);
    void resizeEvent(QResizeEvent *);
    void keyPressEvent(QKeyEvent *); // 键盘事件

private:
    void loadGLTexture();

private:
    QOpenGLContext *m_context;

    bool light;         // 点击“L”键开关光源
    GLfloat	xrot;		// X 旋转
    GLfloat	yrot;		// Y 旋转
    GLfloat xspeed;	    // X 旋转速度
    GLfloat yspeed;		// Y 旋转速度
    GLfloat	z;	        // 深入屏幕的距离

    GLfloat *LightAmbient;  // 环境光参数
    GLfloat *LightDiffuse;  // 漫射光参数
    GLfloat *LightPosition; // 光源位置

    GLuint filter;		// 滤波类型
    GLuint texture[3];	// 3种纹理的储存空间
};

#endif // LESSON6_H

lesson6.cpp

#include "lesson6.h"

#include#include#include#include#include#includeLesson6::Lesson6(QWindow *parent) :
    QWindow(parent)
  , m_context(0)
{
    setSurfaceType(QWindow::OpenGLSurface);

    light=false;
    xrot=45.0f;
    yrot=45.0f;
    xspeed=0.0f;
    yspeed=0.0f;
    z=-5.0f;
    filter=0;

    LightAmbient=new GLfloat[4]{ 0.5f, 0.5f, 0.5f, 1.0f };
    LightDiffuse=new GLfloat[4]{ 1.0f, 1.0f, 1.0f, 1.0f };
    LightPosition=new GLfloat[4]{ 0.0f, 0.0f, 2.0f, 1.0f };
}

Lesson6::~Lesson6()
{
    glDeleteTextures(3, &texture[0]);
}

void Lesson6::render(QPainter *painter)
{
    Q_UNUSED(painter);
}

void Lesson6::render()
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glViewport(0,0,(GLint)width(),(GLint)height()); // 重置当前视口
    glMatrixMode(GL_PROJECTION);                    // 选择投影矩阵
    glLoadIdentity();                               // 重置投影矩阵为单位矩阵
    gluPerspective(45.0f,(GLdouble)width()/(GLdouble)height(),0.1f,100.0f);

    glMatrixMode(GL_MODELVIEW);// 选择模型视图矩阵
    glLoadIdentity();          // 重置模型视图矩阵为单位矩阵

    glTranslatef(0.0f,0.0f,z);  // 移入屏幕z个单位
    //下面两行使立方体绕X、Y轴旋转。旋转多少依赖于变量xrot和yrot的值。
    glRotatef(xrot,1.0f,0.0f,0.0f); // X轴旋转
    glRotatef(yrot,0.0f,1.0f,0.0f); // Y轴旋转
    //下一行与我们在第五课中的类似。有所不同的是,这次我们绑定的纹理是texture[filter],而不是上一课中的texture[0]。
    //任何时候,我们按下F键,filter 的值就会增加。如果这个数值大于2,变量filter 将被重置为0。
    //程序初始时,变量filter 的值也将设为0。使用变量filter 我们就可以选择三种纹理中的任意一种。
    glBindTexture(GL_TEXTURE_2D, texture[filter]); // 选择由filter决定的纹理
    //为了将纹理正确的映射到四边形上,您必须将纹理的右上角映射到四边形的右上角,纹理的左上角映射到四边形的左上角,
    //纹理的右下角映射到四边形的右下角,纹理的左下角映射到四边形的左下角。
    //如果映射错误的话,图像显示时可能上下颠倒,侧向一边或者什么都不是。
    //glTexCoord2f 的第一个参数是X坐标。 0.0f 是纹理的左侧。 0.5f 是纹理的中点, 1.0f 是纹理的右侧。
    //glTexCoord2f 的第二个参数是Y坐标。 0.0f 是纹理的底部。 0.5f 是纹理的中点, 1.0f 是纹理的顶部。
    //所以纹理的左上坐标是 X:0.0f,Y:1.0f ,四边形的左上顶点是 X: -1.0f,Y:1.0f 。其余三点依此类推。
    //试着玩玩 glTexCoord2f X, Y坐标参数。把 1.0f 改为 0.5f 将只显示纹理的左半部分,把 0.0f 改为 0.5f 将只显示纹理的右半部分。

    //glNormal3f是这一课的新东西。Normal就是法线的意思,所谓法线是指经过面(多边形)上的一点且垂直于这个面(多边形)的直线。
    //使用光源的时候必须指定一条法线。法线告诉OpenGL这个多边形的朝向,并指明多边形的正面和背面。
    //如果没有指定法线,什么怪事情都可能发生:不该照亮的面被照亮了,多边形的背面也被照亮....。对了,法线应该指向多边形的外侧。
    //看着木箱的前面您会注意到法线与Z轴正向同向。这意味着法线正指向观察者-您自己。这正是我们所希望的。
    //对于木箱的背面,也正如我们所要的,法线背对着观察者。如果立方体沿着X或Y轴转个180度的话,
    //前侧面的法线仍然朝着观察者,背面的法线也还是背对着观察者。换句话说,不管是哪个面,只要它朝着观察者这个面的法线就指向观察者。
    //由于光源紧邻观察者,任何时候法线对着观察者时,这个面就会被照亮。并且法线越朝着光源,就显得越亮一些。
    //如果您把观察点放到立方体内部,你就会法线里面一片漆黑。因为法线是向外指的。如果立方体内部没有光源的话,当然是一片漆黑。
    glBegin(GL_QUADS);
    // 前面
    glNormal3f( 0.0f, 0.0f, 1.0f);					            // 法线指向观察者
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// 纹理和四边形的左下
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// 纹理和四边形的右下
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// 纹理和四边形的右上
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// 纹理和四边形的左上
    // 后面
    glNormal3f( 0.0f, 0.0f,-1.0f);					            // 法线背向观察者
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// 纹理和四边形的右下
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// 纹理和四边形的右上
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// 纹理和四边形的左上
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// 纹理和四边形的左下
    // 顶面
    glNormal3f( 0.0f, 1.0f, 0.0f);					            // 法线向上
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// 纹理和四边形的左上
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// 纹理和四边形的左下
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// 纹理和四边形的右下
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// 纹理和四边形的右上
    // 底面
    glNormal3f( 0.0f,-1.0f, 0.0f);					            // 法线朝下
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// 纹理和四边形的右上
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// 纹理和四边形的左上
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// 纹理和四边形的左下
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// 纹理和四边形的右下
    // 右面
    glNormal3f( 1.0f, 0.0f, 0.0f);			                    // 法线朝右
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// 纹理和四边形的右下
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// 纹理和四边形的右上
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// 纹理和四边形的左上
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// 纹理和四边形的左下
    // 左面
    glNormal3f(-1.0f, 0.0f, 0.0f);					            // 法线朝左
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// 纹理和四边形的左下
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// 纹理和四边形的右下
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// 纹理和四边形的右上
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// 纹理和四边形的左上
    glEnd();
}

void Lesson6::initialize()
{
    loadGLTexture();                      // 加载纹理
    glEnable(GL_TEXTURE_2D);              // 启用纹理映射
    glShadeModel(GL_SMOOTH);              // 启用平滑着色
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景
    glClearDepth(1.0f);                   // 设置深度缓存
    glEnable(GL_DEPTH_TEST);              // 启用深度测试
    glDepthFunc(GL_LEQUAL);               // 深度测试类型
    // 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    // 现在开始设置光源。
    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);	  // 设置环境光
    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);	  // 设置漫射光
    glLightfv(GL_LIGHT1, GL_POSITION, LightPosition); // 设置光源位置
    // 最后,我们启用一号光源。
    // 记住:只对光源进行设置、定位,光源都不会工作。除非我们启用GL_LIGHTING。
    glEnable(GL_LIGHT1);							  // 启用一号光源
    if(!light)
    {
        glDisable(GL_LIGHTING);		// 禁用光源
    }
    else
    {
        glEnable(GL_LIGHTING);		// 启用光源
    }
}

void Lesson6::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        needsInitialize = true;
    }

    m_context->makeCurrent(this);

    if (needsInitialize) {
        initializeOpenGLFunctions();
        initialize();
    }

    render();

    m_context->swapBuffers(this);
}
//filter 变量跟踪显示时所采用的纹理类型。第一种纹理(texture 0) 使用gl_nearest(近邻滤波)方式构建。
//第二种纹理 (texture 1) 使用gl_linear(线性滤波) 方式,离屏幕越近的图像看起来就越光滑。
//第三种纹理 (texture 2) 使用 mipmapped滤波方式,这将创建一个外观十分优秀的纹理。
//根据我们的使用类型,filter 变量的值分别等于 0, 1 或 2 。下面我们从第一种纹理开始。
//GLuint texture[3] 为三种不同纹理分配储存空间。它们分别位于在 texture[0], texture[1] 和 texture[2]中。
void Lesson6::loadGLTexture()
{
    // 现在载入图像,并将其转换为纹理。
    QImage image(":/image/Crate.bmp");
    image = image.convertToFormat(QImage::Format_RGB888);
    image = image.mirrored();
    // 创建纹理
    glGenTextures(3, &texture[0]);
    // 第五课中我们使用了线性滤波的纹理贴图。这需要机器有相当高的处理能力,但它们看起来很不错。
    // 这一课中,我们接着要创建的第一种纹理使用 GL_NEAREST 方式。从原理上讲,这种方式没有真正进行滤波。
    // 它只占用很小的处理能力,看起来也很差。唯一的好处是这样我们的工程在很快和很慢的机器上都可以正常运行。
    // 您会注意到我们在 MIN 和 MAG 时都采用了GL_NEAREST,你可以混合使用 GL_NEAREST 和 GL_LINEAR。
    // 创建 Nearest 滤波贴图
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                 image.width(),image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());
    // 创建线性滤波纹理
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                 image.width(),image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());
    // 下面是创建纹理的新方法。 Mipmapping!『译者注:这个词的中文我翻不出来,不过没关系。看完这一段,您就知道意思最重要。』
    // 您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失。刚才还很不错的图案变得很难看。
    // 当您告诉OpenGL创建一个mipmapped的纹理后,OpenGL将尝试创建不同尺寸的高质量纹理。当您向屏幕绘制一个mipmapped纹理的时候,
    // OpenGL将选择它已经创建的外观最佳的纹理(带有更多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失)。
    // 我曾经说过有办法可以绕过OpenGL对纹理宽度和高度所加的限制——64、128、256,等等。
    // 办法就是gluBuild2DMipmaps。据我的发现,您可以使用任意的位图来创建纹理,OpenGL将自动将它缩放到正常的大小。
    // 因为是第三个纹理,我们将它存到texture[2]。这样本课中的三个纹理全都创建好了。
    // 创建 MipMapped 纹理
    glBindTexture(GL_TEXTURE_2D, texture[2]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, image.width(),
                      image.width(), GL_RGB, GL_UNSIGNED_BYTE, image.bits());
}

void Lesson6::exposeEvent(QExposeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
    {
        renderNow();
    }
}

void Lesson6::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
    {
        renderNow();
    }
}

void Lesson6::keyPressEvent(QKeyEvent *event)
{
    int key=event->key();
    switch(key)
    {
    case Qt::Key_L:
    {
        light = !light;
        if(!light)
        {
            glDisable(GL_LIGHTING);	// 禁用光源
        }
        else
        {
            glEnable(GL_LIGHTING);	// 启用光源
        }
        break;
    }
    case Qt::Key_F:
    {
        filter+=1;
        if(filter > 2)
        {
            filter = 0;
        }
        break;
    }
    case Qt::Key_PageUp:
    {
        z-=1.0f;
        break;
    }
    case Qt::Key_PageDown:
    {
        z+=1.0f;
        break;
    }
    case Qt::Key_Up:
    {
        xspeed=-1.0f;
        xrot+=xspeed;
        break;
    }
    case Qt::Key_Down:
    {
        xspeed=1.0f;
        xrot+=xspeed;
        break;
    }
    case Qt::Key_Right:
    {
        yspeed=1.0f;
        yrot+=yspeed;
        break;
    }
    case Qt::Key_Left:
    {
        yspeed=-1.0f;
        yrot+=yspeed;
        break;
    }

    }
    if(key==Qt::Key_L||key==Qt::Key_F||key==Qt::Key_PageUp||key==Qt::Key_PageDown
            ||key==Qt::Key_Up||key==Qt::Key_Down||key==Qt::Key_Right||key==Qt::Key_Left)
    {
        renderNow();
    }

}

main.cpp

#include#includeint main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QSurfaceFormat format;
    format.setSamples(16);

    Lesson6 window;
    window.setFormat(format);
    window.resize(640, 480);
    window.show();

    return app.exec();
}

运行效果


按键控制

F键:切换三种滤波方式

L键:打开和关闭光源

PageUp和PageDown:控制木箱的远近

方向键Up和Down:控制木箱绕X轴旋转

方向键Left和Right:控制木箱绕Y轴旋转


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭