Opengl es2.0 学习笔记(三)shader的使用
一.shader
Opengl es渲染管线中有两处可以编程,一个是顶点着色器 一个是像素着色器/片圆着色器 不清楚的可以 看看OpenGLes 渲染管线二.API
//创建shader,返回shader Id
//param:
//GL_VERTEX_SHADER 定点shader
//GL_FRAGMENT_SHADER像素shader
glCreateShader(GLenum type);
//设置shader来源
//param
//shader:shader 的id
//count:数量
//string:内容
//length: 如果length为NULL,则认为每个字符串都以null结尾。如果length不是NULL,则它指向包含字符串的每个相应元素的字符串长度的数组。
glShaderSource (GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
//编译shader
//传入shaderId
glCompileShader(GLuint shader)
//获取shader状态
//shaderId
//查看GL_COMPILE_STATUS
//返回参数
glGetShaderiv(GLuint shader, GLenum pname, GLint* params);
//获取shader日志
glGetShaderInfoLog (GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* infolog);
//创建program,返回id
glCreateProgram( );
//关联shader到program上
glAttachShader(GLuint program, GLuint shader)
//链接program
glLinkProgram(GLuint program);
//获取program信息
glGetProgramiv(GLuint program, GLenum pname, GLint* params);
//获取program日志
glGetProgramInfoLog(GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog);
//使用program
glUseProgram(GLuint program);
//方式shader对象
glDeleteShader (GLuint shader);
//删除program对象
glDeleteProgram (GLuint program);
//获取属性位置,这样CPU和GPU的变量就互通了。
//我们可以传入我们的变量
glGetAttribLocation (GLuint program, const GLchar* name)
glGetUniformLocation (GLuint program, const GLchar* name);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
//Uniform 全局变量
uniforms保存由应用程序传递给着色器的只读常量数据。在顶点着色器中,这些数据通常是变换矩阵,光照参数,颜色等。由 uniform 修饰符修饰的变量属于全局变量,该全局性对顶点着色器与片元着色器均可见,也就是说,这两个着色器如果被连接到同一个应用程序中,它们共享同一份 uniform 全局变量集。因此如果在这两个着色器中都声明了同名的 uniform 变量,要保证这对同名变量完全相同:同名+同类型,因为它们实际是同一个变量。此外,uniform 变量存储在常量存储区,因此限制了 uniform 变量的个数,OpenGL ES 2.0 也规定了所有实现应该支持的最大顶点着色器 uniform 变量个数不能少于 128 个,最大的片元着色器 uniform 变量个数不能少于 16 个。
//Attribute 局部变量
由 vertext array 提供的顶点数据,如空间位置,法向量,纹理坐标以及顶点颜色,它是针对每一个顶点的数据。属性只在顶点着色器中才有,片元着色器中没有属性。属性可以理解为针对每一个顶点的输入数据。OpenGL ES 2.0 规定了所有实现应该支持的最大属性个数不能少于 8 个。
三.调用说明
1.首先需要创建窗口,可以参考之前的博客
2.opengl es初始化
3.创建program对象,创建顶点shader,像素shader,编译shader,链接shader
4.使用glGetAttribLocation/glGetUniformLocation/glGetUniformLocation
关联shader中的变量
5.把shader attach到program对象中,调用glUseProgram 使用program对象
6.使用glEnableVertexAttribArray 开启数组传值
7.使用glUniformMatrix4fv/glUniform4f/glVertexAttribPointer 把变量的值传入shader
8.glDrawArrays画图形
9.关闭传值,关闭shader
glDisableVertexAttribArray(_position);
glUseProgram(0);
10.eglSwapBuffers 显示到屏幕上
1. 顶点shader会根据顶点个数调用次数
2. 像素shader会根据绘制大小计算次数
3. demo使用,正交投影glOrtho,改函数在1.0版本有,2.0需要自己实现
4. PERSPECTIVE是透视投影
5. 窗口坐标 左上开始是0,0
注意:
Uniform
Attribute
的意义
四.撸代码
CELLShader.hpp
#pragma once
#include <assert.h>
class ShaderId
{
public:
ShaderId()
{
_shaderId = -1;
}
int _shaderId;
};
/**
* 程序
*/
class ProgramId
{
public:
int _programId;
ShaderId _vertex;
ShaderId _fragment;
public:
ProgramId()
{
_programId = -1;
}
public:
/**
* 加载函数
*/
bool createProgram( const char* vertex,const char* fragment )
{
bool error = false;
do
{
//顶点shader
if (vertex)
{
_vertex._shaderId = glCreateShader( GL_VERTEX_SHADER );
glShaderSource( _vertex._shaderId, 1, &vertex, 0 );
glCompileShader( _vertex._shaderId );
GLint compileStatus;
glGetShaderiv( _vertex._shaderId, GL_COMPILE_STATUS, &compileStatus );
error = compileStatus == GL_FALSE;
if( error )
{
GLchar messages[256];
glGetShaderInfoLog( _vertex._shaderId, sizeof(messages), 0,messages);
assert( messages && 0 != 0);
break;
}
}
//像素shader
if (fragment)
{
_fragment._shaderId = glCreateShader( GL_FRAGMENT_SHADER );
glShaderSource( _fragment._shaderId, 1, &fragment, 0 );
glCompileShader( _fragment._shaderId );
GLint compileStatus;
glGetShaderiv( _fragment._shaderId, GL_COMPILE_STATUS, &compileStatus );
error = compileStatus == GL_FALSE;
if( error )
{
GLchar messages[256];
glGetShaderInfoLog( _fragment._shaderId, sizeof(messages), 0,messages);
assert( messages && 0 != 0);
break;
}
}
_programId = glCreateProgram( );
if (_vertex._shaderId)
{
glAttachShader( _programId, _vertex._shaderId);
}
if (_fragment._shaderId)
{
glAttachShader( _programId, _fragment._shaderId);
}
glLinkProgram( _programId );
GLint linkStatus;
glGetProgramiv( _programId, GL_LINK_STATUS, &linkStatus );
if (linkStatus == GL_FALSE)
{
GLchar messages[256];
glGetProgramInfoLog( _programId, sizeof(messages), 0, messages);
break;
}
glUseProgram(_programId);
} while(false);
if (error)
{
if (_fragment._shaderId)
{
glDeleteShader(_fragment._shaderId);
_fragment._shaderId = 0;
}
if (_vertex._shaderId)
{
glDeleteShader(_vertex._shaderId);
_vertex._shaderId = 0;
}
if (_programId)
{
glDeleteProgram(_programId);
_programId = 0;
}
}
return true;
}
/**
* 使用程序
*/
virtual void begin()
{
glUseProgram(_programId);
}
/**
* 使用完成
*/
virtual void end()
{
glUseProgram(0);
}
};
class PROGRAM_P2_C4 :public ProgramId
{
public:
typedef int attribute;
typedef int uniform;
public:
attribute _position;
uniform _color;
uniform _MVP;
public:
PROGRAM_P2_C4()
{
_position = -1;
_color = -1;
_MVP = -1;
}
~PROGRAM_P2_C4()
{
}
/// 初始化函数
virtual bool initialize()
{
const char* vs =
{
"precision lowp float; "
"uniform mat4 _MVP;"
"attribute vec2 _position;"
"void main()"
"{"
" vec4 pos = vec4(_position,0,1);"
" gl_Position = _MVP * pos;"//点乘以正交投影矩阵,等于一个位置
"}"
};
const char* ps =
{
"precision lowp float; "
"uniform vec4 _color;"
"void main()"
"{"
" gl_FragColor = _color;"
"}"
};
bool res = createProgram(vs,ps);
if(res)
{
//关联cpu与 gpu变量
_position = glGetAttribLocation(_programId,"_position");
_color = glGetUniformLocation(_programId,"_color");
_MVP = glGetUniformLocation(_programId,"_MVP");
}
return res;
}
/**
* 使用程序
*/
virtual void begin()
{
glUseProgram(_programId);
glEnableVertexAttribArray(_position);
}
/**
* 使用完成
*/
virtual void end()
{
glDisableVertexAttribArray(_position);
glUseProgram(0);
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
CELLWinapp.hpp
#pragma once
#include <Windows.h>
#include <tchar.h>
#include <EGL/egl.h>
#include <gles2/gl2.h>
#include "CELLMath.hpp"
#include "CELLShader.hpp"
namespace CELL
{
class CELLWinApp
{
public:
//! 实例句柄
HINSTANCE _hInstance;
//! 窗口句柄
HWND _hWnd;
//! 窗口的高度
int _width;
//! 窗口的宽度
int _height;
/// for gles2.0
EGLConfig _config;
EGLSurface _surface;
EGLContext _context;
EGLDisplay _display;
//! 增加shader
PROGRAM_P2_C4 _shader;
public:
CELLWinApp(HINSTANCE hInstance)
:_hInstance(hInstance)
{
WNDCLASSEX winClass;
winClass.lpszClassName = _T("CELLWinApp");
winClass.cbSize = sizeof(winClass);
winClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
winClass.lpfnWndProc = wndProc;
winClass.hInstance = hInstance;
winClass.hIcon = 0;
winClass.hIconSm = 0;
winClass.hCursor = LoadCursor(hInstance, IDC_ARROW);
winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winClass.lpszMenuName = NULL;
winClass.cbClsExtra = 0;
winClass.cbWndExtra = 0;
RegisterClassEx(&winClass);
}
virtual ~CELLWinApp()
{
UnregisterClass(_T("CELLWinApp"),_hInstance);
}
/**
* 初始化 OpenGLES2.0
*/
bool initOpenGLES20()
{
const EGLint attribs[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE,24,
EGL_NONE
};
EGLint format(0);
EGLint numConfigs(0);
EGLint major;
EGLint minor;
//! 1
_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//! 2init
eglInitialize(_display, &major, &minor);
//! 3
eglChooseConfig(_display, attribs, &_config, 1, &numConfigs);
eglGetConfigAttrib(_display, _config, EGL_NATIVE_VISUAL_ID, &format);
//! 4
_surface = eglCreateWindowSurface(_display, _config, _hWnd, NULL);
//! 5
EGLint attr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
_context = eglCreateContext(_display, _config, 0, attr);
//! 6
if (eglMakeCurrent(_display, _surface, _surface, _context) == EGL_FALSE)
{
return false;
}
eglQuerySurface(_display, _surface, EGL_WIDTH, &_width);
eglQuerySurface(_display, _surface, EGL_HEIGHT, &_height);
return true;
}
/**
* 销毁OpenGLES2.0
*/
void destroyOpenGLES20()
{
if (_display != EGL_NO_DISPLAY)
{
eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (_context != EGL_NO_CONTEXT)
{
eglDestroyContext(_display, _context);
}
if (_surface != EGL_NO_SURFACE)
{
eglDestroySurface(_display, _surface);
}
eglTerminate(_display);
}
_display = EGL_NO_DISPLAY;
_context = EGL_NO_CONTEXT;
_surface = EGL_NO_SURFACE;
}
protected:
static LRESULT CALLBACK wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
CELLWinApp* pThis = (CELLWinApp*)GetWindowLong(hWnd,GWL_USERDATA);
if (pThis)
{
return pThis->onEvent(hWnd,msg,wParam,lParam);
}
if (WM_CREATE == msg)
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
SetWindowLong(hWnd,GWL_USERDATA,(DWORD_PTR)pCreate->lpCreateParams);
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
public:
/**
* 事件函数
*/
virtual LRESULT onEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
case WM_DESTROY:
{
::PostQuitMessage(0);
}
break;
case WM_MOUSEMOVE:
break;
default:
return DefWindowProc( hWnd, msg, wParam, lParam );
}
return S_OK;
}
virtual void render()
{
//! 清空缓冲区
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
//! 视口,在Windows窗口指定的位置和大小上绘制OpenGL内容
glViewport(0,0,_width,_height);
//! 创建一个投影矩阵
/正交投影
CELL::matrix4 screenProj = CELL::ortho<float>(0,float(_width),float(_height),0,-100.0f,100);
_shader.begin();
{
float x = 100;
float y = 100;
float w = 100;
float h = 100;
//顶点位置
CELL::float2 pos[] =
{
CELL::float2(x,y),
CELL::float2(x + w,y),
CELL::float2(y,y + h),
CELL::float2(x + w, y + h),
};
//传入正交矩阵,uniform是定量不会被改变
glUniformMatrix4fv(_shader._MVP, 1, false, screenProj.data());
//传入颜色
glUniform4f(_shader._color,1,0,0,1);
//传入顶点坐标,传入的顶点坐标会根据数量 决定调用次数
glVertexAttribPointer(_shader._position,2,GL_FLOAT,false,sizeof(CELL::float2),pos);
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
}
_shader.end();
}
/**
* 主函数
*/
int main(int width,int height)
{
_hWnd = CreateWindowEx( NULL,
_T("CELLWinApp"),
_T("CELLWinApp"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
width,
height,
NULL,
NULL,
_hInstance,
this
);
if (_hWnd == 0)
{
return -1;
}
UpdateWindow(_hWnd);
ShowWindow(_hWnd,SW_SHOW);
if (!initOpenGLES20())
{
return false;
}
_shader.initialize();
MSG msg = {0};
while(msg.message != WM_QUIT)
{
if (msg.message == WM_DESTROY ||
msg.message == WM_CLOSE)
{
break;
}
/**
* 有消息,处理消息,无消息,则进行渲染绘制
*/
if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
render();
eglSwapBuffers(_display,_surface);
}
}
/**
* 销毁OpenGLES20
*/
destroyOpenGLES20();
return 0;
}
};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
main.cpp
#include "CELLWinApp.hpp"
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);
CELL::CELLWinApp app(hInstance);
app.main(800,600);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
namespace CELL
{
//正交投影
template <typename valType>
tmat4x4<valType> ortho
(
valType left,
valType right,
valType bottom,
valType top,
valType zNear,
valType zFar
)
{
tmat4x4<valType> res(1);
res[0][0] = valType(2) / (right - left);
res[1][1] = valType(2) / (top - bottom);
res[2][2] = - valType(2) / (zFar - zNear);
res[3][0] = - (right + left) / (right - left);
res[3][1] = - (top + bottom) / (top - bottom);
res[3][2] = - (zFar + zNear) / (zFar - zNear);
return res;
}
//
template <typename T>
struct tvec2
{
typedef T value_type;
typedef std::size_t size_type;
typedef tvec2<T> type;
value_type x;
value_type y;
value_type & operator[](size_type i)
{
assert(i < this->length());
return (&x)[i];
}
value_type const & operator[]( size_type i ) const
{
assert(i < this->length());
return (&x)[i];
}
tvec2() :
x(value_type(0)),
y(value_type(0))
{}
tvec2(tvec2<T> const & v) :
x(v.x),
y(v.y)
{}
tvec2(value_type const & s) :
x(s),
y(s)
{}
tvec2(value_type const & s1, value_type const & s2) :
x(s1),
y(s2)
{}
template <typename U>
tvec2(U const & x) :
x(value_type(x)),
y(value_type(x))
{}
template <typename U, typename V>
tvec2(U const & a, V b) :
x(value_type(a)),
y(value_type(b))
{}
template <typename U>
tvec2(tvec2<U> const & v) :
x(value_type(v.x)),
y(value_type(v.y))
{}
tvec2<T> & operator= (tvec2<T> const & v)
{
this->x = v.x;
this->y = v.y;
return *this;
}
template <typename U>
tvec2<T> & operator= (tvec2<U> const & v)
{
this->x = T(v.x);
this->y = T(v.y);
return *this;
}
template <typename U>
tvec2<T> & operator+=(U const & s)
{
this->x += T(s);
this->y += T(s);
return *this;
}
template <typename U>
tvec2<T> & operator+=(tvec2<U> const & v)
{
this->x += T(v.x);
this->y += T(v.y);
return *this;
}
template <typename U>
tvec2<T> & operator-=(U const & s)
{
this->x -= T(s);
this->y -= T(s);
return *this;
}
typedef tvec2<float> float2;
template <typename U>
tvec2<T> & operator-=(tvec2<U> const & v)
{
this->x -= T(v.x);
this->y -= T(v.y);
return *this;
}
template <typename U>
tvec2<T> & operator*=(U s)
{
this->x *= T(s);
this->y *= T(s);
return *this;
}
template <typename U>
tvec2<T> & operator*=(tvec2<U> const & v)
{
this->x *= T(v.x);
this->y *= T(v.y);
return *this;
}
template <typename U>
tvec2<T> & operator/=(U s)
{
this->x /= T(s);
this->y /= T(s);
return *this;
}
template <typename U>
tvec2<T> & operator/=(tvec2<U> const & v)
{
this->x /= T(v.x);
this->y /= T(v.y);
return *this;
}
tvec2<T> & operator++()
{
++ this->x;
++ this->y;
return *this;
}
tvec2<T> & operator--()
{
--this->x;
--this->y;
return *this;
}
void makeCeil( tvec2<T> cmp )
{
if( cmp.x > x ) x = cmp.x;
if( cmp.y > y ) y = cmp.y;
}
void makeFloor( tvec2<T> cmp )
{
if( cmp.x < x ) x = cmp.x;
if( cmp.y < y ) y = cmp.y;
}
};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
文章来源: yujiang.blog.csdn.net,作者:鱼酱2333,版权归原作者所有,如需转载,请联系作者。
原文链接:yujiang.blog.csdn.net/article/details/83895593
- 点赞
- 收藏
- 关注作者
评论(0)