Qt 使用自带的OpenGL模块开发程序
Posted ybqjymy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt 使用自带的OpenGL模块开发程序相关的知识,希望对你有一定的参考价值。
QT中使用opengl
.pro文件中添加
QT += opengl
1、使用指定版本的OpenGL
如下使用opengl4.5调用方法,使用指定版本的接口,必须设备图形显示设备支持对应OpenGL版本才可。
Q:什么是CoreProfile和Compatibility Profile?
A:在OpenGL的发展历程中,总是兼顾向下兼容的特性,但是到了一定的程度之后,这些旧有的OpenGLAPI不再适应时代的需要,还有一些扩展并不是驱动一定要实现的扩展,这些被统一划入可选的CompatibilityProfile;而由OpenGL规范规定必须支持的扩展,则是Core Profile,想要支持先进的OpenGL,相应的CoreProfile扩展必须被实现。所以如果使用#include < QOpenGLFunctions > 或者 #include <QOpenGLFunctions_4_5_Core>时,会发现如glDrawPixels等接口找不到的问题。所以我们使用Cmpatibility版本。所有opengl的接口函数都可以使用。
1 #include <QOpenGLFunctions_4_5_Cmpatibility>
2 //QOpenGLFunctions类提供了跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API,并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承:
3 class Widget : public QOpenGLWidget, protected QOpenGLFunctions_4_5_Cmpatibility
4 {
5 public:
6 Widget(QWidget *parent = 0);
7 ~Widget();
8 void initializeGL(); ///< 初始化
9 void resizeGL(int w, int h); ///< 当窗口发生变化时重新初始化
10 void paintGL(); ///< 绘制
11 }
1 void Widget::initializeGL()
2 {
3 //获取上下文
4 //QOpenGLFunctions_4_5_Compatibility* compatibility= QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_4_5_Compatibility>();
5 //compatibility->initializeOpenGLFunctions();
6
7 /* 0. 初始化函数,使得函数可以使用 */
8 initializeOpenGLFunctions();
9
10 const GLubyte* name = glGetString(GL_VENDOR); //返回负责当前OpenGL实现厂商的名字
11 const GLubyte* biaoshifu = glGetString(GL_RENDERER); //返回一个渲染器标识符,通常是个硬件平台
12 const GLubyte* OpenGLVersion =glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
13 // const GLubyte* OpenGLExensions =glGetString(GL_EXTENSIONS); //
14
15 QString str;
16 str.sprintf("%s | %s | %s",name,biaoshifu,OpenGLVersion);
17
18 qDebug()<<str;
19 }
2、使用QOpenGLFunctions
QOpenGLFunctions类提供跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API, 并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承,同时在初始化函数中void initializeGL() 调用此接口initializeOpenGLFunctions() 进行初始化。如下:
OpenGL ES相对OpenGL删减了一切低效能的操作方式,有高性能的决不留低效能的,即只求效能不求兼容性(和苹果的作风类似)。也就是说很多opengl函数无法使用,如glDrawPixels等。
典型:
1.没有double型数据类型,但加入了高性能的定点小数数据类型。
2.没有glBegin/glEnd/glVertex,只能用glDrawArrays/glDraw…
3.没有实时将非压缩图片数据转成压缩贴图的功能,程序必须直接提供压缩好的贴图
数据类型:
1: i GLint 整数型
2: f GLfixed 定点小数
3: x GLclampx 限定型定点小数
删除的功能:
1.glBegin/glEnd
2.glArrayElement
3.显示列表
4.求值器
5.索引色模式
6.自定义裁剪平面
7.glRect
8.图像处理(这个一般显卡也没有,FireGL/Quadro显卡有)
9.反馈缓冲
10.选择缓冲
11.累积缓冲
12.边界标志
13.glPolygonMode
14.GL_QUADS,GL_QUAD_STRIP,GL_POLYGON
15.glPushAttrib,glPopAttrib,glPushClientAttrib,glPopClientAttrib
15.TEXTURE_1D、TEXTURE_3D、TEXTURE_RECT、TEXTURE_CUBE_MAP
16.GL_COMBINE
17.自动纹理坐标生成
18.纹理边界
19.GL_CLAMP、GL_CLAMP_TO_BORDER
20.消失纹理代表
21.纹理LOD限定
22.纹理偏好限定
23.纹理自动压缩、解压缩
24.glDrawPixels,glPixelTransfer,glPixelZoom
25.glReadBuffer,glDrawBuffer,glCopyPixels
其它注意事项:
1.glDrawArrays等函数中数据必须紧密排列,即间隔为0
2.各种数据的堆栈深度较低
参考代码:
1 #ifndef WIDGET_H
2 #define WIDGET_H
3
4 #include <QOpenGLWidget>
5 #include <QOpenGLFunctions>
6 #include <QOpenGLShaderProgram>
7
8 //QOpenGLFunctions类提供了跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API,并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承:
9 class Widget : public QOpenGLWidget, protected QOpenGLFunctions
10 {
11 public:
12 Widget(QWidget *parent = 0);
13 ~Widget();
14 void initializeGL(); ///< 初始化
15 void resizeGL(int w, int h); ///< 当窗口发生变化时重新初始化
16 void paintGL(); ///< 绘制
17
18 void initVbo(); ///< 初始化Vbo
19 void loadTextures(); ///< 加载纹理
20 void keyPressEvent(QKeyEvent * e); ///< 键盘事件
21 private:
22 /* [1] 需要定点着色器和片段着色器,不然做不了任何渲染 */
23 /* 这里定义了一个着色器[顶点着色器、片段着色器]编译对象 */
24 QOpenGLShaderProgram * program;
25 ///< 可以根据此id,利用glGetUniformLocation等方法获取shader里面的属性
26 GLuint programid;
27
28 ///< 其实还有视图矩阵、投影矩阵、MVP矩阵,这里简单的用下,不区分特别细!
29 ///< 分三个这样的矩阵,分别是模型矩阵、视图矩阵、透视矩阵,这样以后灵活性更强:
30 /// 1. 比如单独控制模型灯光跟随,shader可能需要传入除了mvp矩阵外的模型矩阵*视图矩阵这样一个矩阵
31 QMatrix4x4 m_projection;
32
33 ///< 矩阵、顶点、颜色在着色器里面的位置
34 GLuint matrixLocation, vertexLocation, textureLocation,
35 samplerLocation;
36
37 ///< 顶点、索引、颜色->buffer的标识
38 GLuint verVbo, v_indexVbo, textureVbo;
39 GLuint texture;
40
41 int vVerticesLen; ///< 顶点数组长度
42 int tri_indexLen; ///< 索引数组长度
43 int textureCoordLen;///< 纹理坐标数组长度
44 };
45
46 #endif // WIDGET_H
1 #include "widget.h"
2 #include<QKeyEvent>
3 #include<QGLWidget>
4 #include<QOpenGLFunctions_3_2_Core>
5
6 Widget::Widget(QWidget *parent) : QOpenGLWidget(parent)
7 {
8 ///< 官方文档有这样设置,具体还没细细看,但是意思吧,就是告诉渲染属性,使用版本等;有空可以深入研究,光看效果不一定能看出什么区别!
9 ///< 这个有些是放到main.cpp中去了,通过new widget.setFormat(format)那样去设置,不清楚...我觉得本质是一样,都是给该widget设置属性。
10 // QSurfaceFormat format;
11 // format.setDepthBufferSize(24);
12 // format.setStencilBufferSize(8);
13 // format.setVersion(3, 2);
14 // format.setProfile(QSurfaceFormat::CoreProfile);
15 // setFormat(format);
16 }
17
18 Widget::~Widget()
19 {
20 glDeleteBuffers(1, &verVbo);
21 glDeleteBuffers(1, &v_indexVbo);
22 glDeleteProgram(programid);
23 glDeleteTextures(1, &texture);
24 }
25
26 /* 1.1 着色器代码 */
27 /* *********************************************
28 * 顶点着色器定义一个输入,它是 4 个成员的矢量 vPosition。
29 * 主函数声明着色器宣布着色器开始执行。着色器主体非常简单,
30 * 它复制输入 vPosition 属性到 gl_Position 输出变量中。
31 * 每个顶点着色器必须输出位置值到 gl_Position 变量中,
32 * 这个变量传入到管线的下一个阶段中。
33 * matrix主要是模型视图矩阵,控制位置和旋转等
34 * ******************************************** */
35 /* 顶点着色器 */
36 static const char *vertexShaderSourceCore =
37 "attribute vec4 vPosition;
"
38 "uniform highp mat4 matrix;
"
39 "attribute vec2 TexCoord;
"
40 "varying vec2 TexCoord0;
"
41 "void main() {
"
42 " TexCoord0 = TexCoord;
"
43 " gl_Position = matrix * vPosition;
"
44 "}
";
45
46 /* *********************************************
47 * gl_FragColor,gl_FragColor是片段着色器最终的输出值,
48 * 本例中输出值来自外部传入的颜色数组。
49 * ******************************************** */
50
51 /* 片段着色器 */
52 static const char *fragmentShaderSourceCore =
53 "varying vec2 TexCoord0;
"
54 "uniform sampler2D gSampler;
"
55 "void main() {
"
56 " gl_FragColor = texture2D(gSampler, TexCoord0.st);
"
57 "}
";
58
59
60 ///* 2.1 三角形顶点的坐标 */
61 //GLfloat vVertices[] = {0.0f, 0.5f, 0.0f,
62 // -0.5f, -0.5f, 0.0f,
63 // 0.5f, -0.5f, 0.0f};
64 ///* 2.2 三角形顶点的索引 */
65 //GLuint tri_index[] = {0, 1, 2};
66 ///* 2.3 顶点颜色数组 */
67 //GLfloat colors[] = {1.0f, 0.0f, 0.0f,0.5f,
68 // 0.0f, 1.0f, 0.0f,0.5f,
69 // 0.0f, 0.0f, 1.0f,0.5f};
70
71 /* 2.1 正方体顶点的坐标 */
72 GLfloat vVertices[] = {-0.5f, -0.5f, 0.5f,
73 0.5f, -0.5f, 0.5f,
74 -0.5f, 0.5f, 0.5f,
75 0.5f, 0.5f, 0.5f,
76
77 -0.5f, -0.5f, -0.5f,
78 0.5f, -0.5f, -0.5f,
79 -0.5f, 0.5f, -0.5f,
80 0.5f, 0.5f, -0.5f,
81
82 -0.5f, -0.5f, -0.5f,
83 -0.5f, -0.5f, 0.5f,
84 -0.5f, 0.5f, -0.5f,
85 -0.5f, 0.5f, 0.5f,
86
87 0.5f, -0.5f, -0.5f,
88 0.5f, -0.5f, 0.5f,
89 0.5f, 0.5f, -0.5f,
90 0.5f, 0.5f, 0.5f,
91
92 -0.5f, 0.5f, -0.5f,
93 -0.5f, 0.5f, 0.5f,
94 0.5f, 0.5f, -0.5f,
95 0.5f, 0.5f, 0.5f,
96
97 -0.5f, -0.5f, -0.5f,
98 -0.5f, -0.5f, 0.5f,
99 0.5f, -0.5f, -0.5f,
100 0.5f, -0.5f, 0.5f};
101 /* 2.2 正方体顶点的索引 */
102 GLuint tri_index[] = {0, 3, 2,
103 0, 1, 3,
104 4, 7, 6,
105 4, 5, 7,
106 8, 11, 10,
107 8, 9, 11,
108 12, 15, 14,
109 12, 13, 15,
110 16, 19, 18,
111 16, 17, 19,
112 20, 23, 22,
113 20, 21, 23};
114
115 ///< 纹理点 6个面 每个面四个纹理坐标映射???好像不对呀,绘制出来花花的...
116 ///< 还得好好思考下纹理坐标和现在的顶点索引坐标如何对应!!!
117 /// 下面这个先注释掉,上面的说法:六个面,每个面四个纹理坐标映射好像不对....
118 //float texCoords[] =
119 //{
120 // 0.0f, 0.0f,
121 // 1.0f, 0.0f,
122 // 0.0f, 1.0f,
123 // 1.0f, 1.0f,
124
125 // 0.0f, 0.0f,
126 // 1.0f, 0.0f,
127 // 0.0f, 1.0f,
128 // 1.0f, 1.0f,
129
130 // 0.0f, 0.0f,
131 // 1.0f, 0.0f,
132 // 0.0f, 1.0f,
133 // 1.0f, 1.0f,
134 // 0.0f, 0.0f,
135 // 1.0f, 0.0f,
136 // 0.0f, 1.0f,
137 // 1.0f, 1.0f,
138 // 0.0f, 0.0f,
139 // 1.0f, 0.0f,
140 // 0.0f, 1.0f,
141 // 1.0f, 1.0f,
142 // 0.0f, 0.0f,
143 // 1.0f, 0.0f,
144 // 0.0f, 1.0f,
145 // 1.0f, 1.0f
146 //};
147 ///< 我们就来手动修改下,直到不花为止,然后回过头去细细分析下,具体原因?
148 /// 这样学习起来更快,毕竟现有感觉,带着感觉去更加有激情和感悟...
149 /// 不过按照自己认为的坐标去对应,始终不对(上下两面还是花)...哎,缓一缓,先仔细研究下再回过头来修改...
150 float texCoords[] =
151 {
152 0.0f, 0.0f,
153 1.0f, 0.0f,
154 0.0f, 1.0f,
155 1.0f, 1.0f,
156
157 0.0f, 0.0f,
158 1.0f, 0.0f,
159 0.0f, 1.0f,
160 1.0f, 1.0f,
161
162 0.0f, 0.0f,
163 1.0f, 0.0f,
164 0.0f, 1.0f,
165 1.0f, 1.0f,
166
167 0.0f, 0.0f,
168 1.0f, 0.0f,
169 0.0f, 1.0f,
170 1.0f, 1.0f,
171
172 0.0f, 0.0f,
173 1.0f, 0.0f,
174 0.0f, 1.0f,
175 1.0f, 1.0f,
176
177 0.0f, 0.0f,
178 1.0f, 0.0f,
179 0.0f, 1.0f,
180 1.0f, 1.0f
181 };
182
183 /**
184 * @brief 初始化模型信息vbo【显存】
185 */
186 void Widget::initVbo()
187 {
188 ///< 计算获得数组长度,之后会用到该变量,这样只需要改动这里即可!如果用链表,直接.size()即可求出!
189 vVerticesLen = sizeof(vVertices)/sizeof(GLfloat);
190 tri_indexLen = sizeof(tri_index)/sizeof(GLuint);
191 textureCoordLen = sizeof(texCoords)/sizeof(GLfloat);
192
193 qDebug() << vVerticesLen;
194 qDebug() << tri_indexLen;
195
196 ///< 初始化顶点buffer并装载数据到显存
197 glGenBuffers(1, &verVbo);
198 glBindBuffer(GL_ARRAY_BUFFER, verVbo);
199 glBufferData(GL_ARRAY_BUFFER, vVerticesLen * sizeof(GLfloat), vVertices, GL_STATIC_DRAW);
200
201 ///< 初始化索引buffer并装载数据到显存
202 glGenBuffers(1, &v_indexVbo);
203 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v_indexVbo);
204 glBufferData(GL_ELEMENT_ARRAY_BUFFER, tri_indexLen * sizeof(GLuint), tri_index, GL_STATIC_DRAW);
205
206 ///< 初始化纹理坐标buffer并装载到显存
207 glGenBuffers(1, &textureVbo);
208 glBindBuffer(GL_ARRAY_BUFFER, textureVbo);
209 glBufferData(GL_ARRAY_BUFFER, textureCoordLen * sizeof(GLfloat), texCoords, GL_STATIC_DRAW);
210 }
211
212 /**
213 * @brief 装载纹理,具体纹理知识可以参考网友资料:
214 * http://www.cnblogs.com/tornadomeet/archive/2012/08/24/2654719.html
215 */
216 void Widget::loadTextures()
217 {
218 QImage tex, buf;
219 if (!buf.load("../2012082420060914.jpg"))
220 {
221 qWarning("annot open the image...");
222 QImage dummy(128, 128, QImage::Format_RGB32);
223 dummy.fill(Qt::green);
224 buf = dummy;
225 }
226
227 ///< 转换为OpenGL支持的格式
228 tex = QGLWidget::convertToGLFormat(buf);
229
230 ///< 开辟一个纹理内存,内存指向texture
231 glGenTextures(1, &texture);
232 ///< 将创建的纹理内存指向的内容绑定到纹理对象GL_TEXTURE_2D上,
233 /// 经过这句代码后,以后对GL_TEXTURE_2D的操作的任何操作都同时对应与它所绑定的纹理对象
234 glBindTexture(GL_TEXTURE_2D, texture);
235 ///< 开始真正创建纹理数据
236 glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(),
237 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());
238
239 ///< 当所显示的纹理比加载进来的纹理小时,采用GL_LINEAR的方法来处理
240 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
241 ///< 当所显示的纹理比加载进来的纹理大时,采用GL_LINEAR的方法来处理
242 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
243 }
244
245 void Widget::initializeGL()
246 {
247 qDebug("+++ initializeGL +++");
248 /* 0. 初始化函数,使得函数可以使用 */
249 initializeOpenGLFunctions();
250
251 /* 创建项目对象链接着色器 */
252 /* 1. 初始化最大的任务是装载顶点和片段着色器 */
253 program = new QOpenGLShaderProgram(this);
254 /* 一旦应用程序已经创建了顶点、片段着色器对象,
255 * 它需要去创建项目对象,项目是最终的链接对象,
256 * 每个着色器在被绘制前都应该联系到项目或者项目对象。
257 * ***************************************** */
258 /* 1.2 加载 */
259 if(!program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSourceCore))
260 {
261 return;
262 }
263 if(!program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSourceCore))
264 {
265 return;
266 }
267
268 /* 1.3 设置属性位置,将vPosition属性设置为位置0, vertex为位置1
269 这里我就让程序自动分配,当然你也可以手动; 我在后面通过代码获取到了!
270 */
271 //program->bindAttributeLocation("vertex", 1);
272 //program->bindAttributeLocation("vPosition", 0);
273 //program->bindAttributeLocation("a_color", 1);
274 //program->bindAttributeLocation("matrix", 2);
275
276 /* 1.4 链接项目检查错误 */
277 if( !program->link() )
278 {
279 return;
280 }
281
282 if( !program->bind() ){
283 return ;
284 }
285
286 ///< 获取shaderprogram的id号,然后可以通过id号获取一些属性...
287 programid = program->programId();
288
289 ///< 从shaderprogram里面获取变量标识,总共用到两种方式,看你喜好!倾向第一种
290 matrixLocation = glGetUniformLocation(programid, "matrix");
291 vertexLocation = glGetAttribLocation(programid, "vPosition");
292 textureLocation = program->attributeLocation("TexCoord");
293 samplerLocation = program->uniformLocation("gSampler");
294
295 ///< 初始化vbo,对于实时变化的数据,可能需要在paintGL()里面每次调用!
296 initVbo();
297
298 ///< 装载纹理
299 loadTextures();
300
301 ///< 允许采用2D纹理技术
302 glEnable(GL_TEXTURE_2D);
303 ///< 设置背景颜色
304 glClearColor(0.5f, 0.5f, 0.5f, 0.0f);
305 ///< 开启深度测试,避免颜色相互透过,具体需要自己深入学习的哦!
306 glEnable(GL_DEPTH_TEST);
307 ///< 设置深度测试类型 - 不设置也会默认
308 glDepthFunc(GL_LEQUAL);
309
310 ///< 这个地方先于resizeGL运行,所以这里设置无效!我一开始犯了这个错误!!!
311 //m_projection.translate(0.0f, 0.0f, -1.0f);
312 }
313
314 void Widget::resizeGL(int w, int h)
315 {
316 /* 2.1 viewport 设定窗口的原点 origin (x, y)、宽度和高度 */
317 glViewport(0, 0, w, h);
318
319 ///< 模型矩阵重置
320 m_projection.setToIdentity();
321 ///< 透视投影【做了简单容错】
322 qreal aspect = qreal(w) / qreal(h ? h : 1);
323 m_projection.perspective(60.0f, aspect, 1.0f, 100.0f);
324 ///< 增加了模型矩阵,需要做一定偏移量,保证物体刚开始渲染出来时可以被看到!
325 m_projection.translate(0.0f, 0.0f, -2.0f);
326 }
327
328 void Widget::paintGL()
329 {
330 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
331
332 ///< shader传入模型视图矩阵
333 glUniformMatrix4fv(matrixLocation, 1, GL_FALSE, m_projection.data());
334
335 ///< shader绑定并启用顶点数组buffer
336 glBindBuffer(GL_ARRAY_BUFFER, verVbo);
337 glEnableVertexAttribArray(vertexLocation);
338 ///< 顶点xyz坐标,所以每三个作为一个顶点值
339 glVertexAttribPointer( vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
340
341 ///< shader绑定并顶点索引数组buffer - 索引无需启用
342 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v_indexVbo);
343
344 ///< 绑定纹理
345 glUniform1i(samplerLocation, 0);
346 glActiveTexture(GL_TEXTURE_2D);
347 glBindTexture(GL_TEXTURE_2D, texture);
348 glBindBuffer(GL_ARRAY_BUFFER, textureVbo);
349 glEnableVertexAttribArray(textureLocation);
350 ///< 2是标识两个float为一个纹理坐标,从varying vec2 TexCoord0也可以看出!
351 glVertexAttribPointer(textureLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
352
353 glDrawElements(GL_TRIANGLES, tri_indexLen, GL_UNSIGNED_INT, 0);
354
355 ///< 解绑buffer、关闭启用顶点、颜色数组、解绑纹理
356 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
357 glBindBuffer(GL_ARRAY_BUFFER, 0);
358 glBindTexture(GL_TEXTURE_2D, 0);
359 glBindBuffer(GL_ARRAY_BUFFER, 0);
360 glDisableVertexAttribArray(textureLocation);
361 glDisableVertexAttribArray(vertexLocation);
362 }
363
364 /**
365 * @brief 写了键盘监听事件,可以控制模型旋转,方便预览
366 * @param k
367 */
368 void Widget::keyPressEvent(QKeyEvent * k)
369 {
370 qDebug("+++ keyPressEvent +++");
371 if(k->key() == Qt::Key_A)
372 {
373 m_projection.rotate(4, 0, 1, 0);
374 }
375 else if(k->key() == Qt::Key_D)
376 {
377 m_projection.rotate(-4, 0, 1, 0);
378 }
379 else if(k->key() == Qt::Key_W)
380 {
381 m_projection.rotate(4, 1, 0, 0);
382 }
383 else if(k->key() == Qt::Key_S)
384 {
385 m_projection.rotate(-4, 1, 0, 0);
386 }
387 update();
388 }
1 #include "widget.h"
2 #include <QApplication>
3
4 int main(int argc, char *argv[])
5 {
6 QApplication a(argc, argv);
7
8 Widget widget(0);
9 widget.show();
10
11 return a.exec();
12 }
以上是关于Qt 使用自带的OpenGL模块开发程序的主要内容,如果未能解决你的问题,请参考以下文章