标签:
第一讲
1. 增强现实技术AR:
--融合了三维动画、立体视觉和图像处理;
--建模、渲染、位置标定、图像融合;
2. OpenGL是一种用于创建实时3D图像的编程接口。
3. 三维这个术语表示一个正在描述或显示的物体具有三维维度:宽度、高度、深度;
--计算机3D图形实质上也是平面的;
--在计算机屏幕上显示的二维图像,提供深度(或第三维)的错觉;
2D+透视 = 3D
透视使人产生深度的错觉。
4. 真正的3D是通过人的两只眼睛观察同一个物体,在视网膜上生成具有视差的两个图像,在人的大脑产生真正的3D视觉感受。
5. 名词解释:渲染,将数学和图形数据转换成3D空间图像的操作。
6. 变换:平移、旋转和缩放。
7. 投影:将3D坐标转换成二维坐标。
8. 光栅化:使用像素进行图形填充。
9. 纹理贴图:纹理是一幅贴到三角形或多边形上的图片。
10. 视口映射:
--把绘图坐标映射到窗口坐标;
--从逻辑笛卡尔坐标映射到物理屏幕像素坐标;
--视口就是窗口内部用于绘制裁剪区域的客户区域;视口不一定就是整个窗口,逻辑坐标的每个单位不一定与屏幕像素一一对应。
11. 投影:从3D到2D,两种投影方式:正投影和透视投影。
12. 正投影:也叫平行投影;
特点:没有畸变,视觉不真实;
主要用于建筑设计、CAD、2D绘图;
13. 透视投影:
近大远小、视觉真实。
14. 什么是OpenGL?
Open Graphics Library
定义:图形硬件的一种软件接口;
最初由SGI创建,用于在不同硬件体系的图形设备上进行二维、三维图形的绘制。OpenGL不是编程语言,特点是并行浮点运算速度极快,但不能进行流程控制。
15. ARB:标准扩展;
EXT:多家支持的扩展;
16. 背面剔除:画家算法:
--将需要绘制的图元排序,首先绘制最远的,依次靠近。
第二讲
1. 物质由原子组成,3D图形由图元组成。
2. 点:每个顶点在屏幕上都是一个单独的点。
3. 线:每一对顶点定义一个线段。
4. 线带:从第一个顶点依次经过后续顶点而绘制的线条。
5. 线环:最后一个顶点和第一个顶点连接。
6. 什么是向量?
三维向量用一个三元组(X,Y,Z)表示。
--顶点是一个向量:表示空间中的一个位置;
--三维坐标可以表示一个向量:向量有长度,也有方向,所以,向量=向+量。
7. 向量的定义:
--向量是从坐标系原点到点(x,y,z)的带箭头的线段。
--空间中的一个点,既是顶点,又是向量。
8.单位向量:
--长度为1的向量称为单位向量;
--将一个任意向量转化为单位向量,称为归一化。将该向量除以向量的长度。
8. 点乘:
--两个三维向量的点乘是一个标量;
--表示一个向量投影到另外一个向量的长度;
--在单位向量中,两个向量的点乘是夹角的余弦值;
9. 叉乘:
两个向量的叉乘得到的是垂直于这两个向量的第三个向量;
用途:计算平面的法向;
特点:顺序不可交换;
10. 矩阵:是一个由行和列构成的数据结构;在程序中一般以二维数组存放。
11. 矩阵的作用:
--三维空间中的仿射变换都是使用矩阵运算进行的:旋转、平移、缩放;
--OpenGL中的矩阵:视图变换、模型变换、投影变换。
12. 几个基本概念
视图变换:设定观察者或摄像机的位置;
模型变换:在场景中移动物体;
模型视图:模型和视图的一致性;
投影变换:设定视景体的大小和形状;
视口变换:窗口缩放
13. 视图变换:
--视图变换的作用就是设定观察者的位置,以及视线;
--可以理解为在场景中摆放摄像机;摄像机所在的位置,摄像机对准的方向;
--在任何其他变换之前应该先使用视图变换,能够保证与视觉坐标系一致;
--默认从(0,0,0)看向Z轴负方向;
14. 模型变换:
--模型变换用于操作模型和其中的特定对象;
--将对象移动到需要的位置,然后进行旋转和缩放;
15. 投影变换:在模型变换、视图变换之后应用;
投影变换实际上定义了视景体并创建了裁剪平面;
投影变换分为正投影和透视投影。
16. 视口变换
--从颜色缓冲区到窗口像素之间的映射;
影响画面显示的比例;
可以进行最终画面的缩放;
17. 模型视图矩阵
--模型视图矩阵是一个4*4矩阵;
--原始顶点坐标是一个四维向量,与模型视图矩阵相乘,得到变换后的新坐标;
--注意:在数学上向量应该放在右边,左乘一个变换矩阵;
--在OpenGL中,向量是行向量,矩阵式列主矩阵,相当于整体转置了;
18. 平移:将一个向量沿着一个或者多个坐标轴方向移动。
19.旋转:绕着三个坐标轴方向分别按照设定的因子放大或者缩小向量。
第三讲
1. 颜色只是某种波长的光,真实世界中的各种颜色是由许多不同类型的光组合而成的,这些类型的光是通过他们的波长来区分的。
2. 光的波长是根据光波相邻波峰之间的距离来测量的。
3. 白色的物体均匀的反射了所有波长的颜色;而黑色物体均匀的吸收了所有波长的颜色。
4. OpenGL通过分别制定红R、绿G、蓝B成分的强度来指定一种颜色。
5. 对所有可用的颜色进行建模,创建立方体,成为RGB颜色空间;
--原点(0,0,0),黑色;
--对角线顶点(255,255,255),白色;
--从原点到每根轴的方向,分别是红绿蓝的饱和分布;
6. 光照模型就是用于计算几何物体表面任一点上的光亮度和色彩组成的数学计算公式。
光照模型就是用数学方法来描述现实世界中的光照情况。
局部光照模型:光强仅与被照明物体和光源有关;
全局光照模型:光强与场景中任一点都相关;
7. 局部光照模型:假定光是点光源,物体是非透明物体,且表面光滑,透射光和散射光近似于零。
--局部光照模型中只考虑反射光的作用;
--反射光包括环境光、漫反射光、镜面反射光;
8. 光照概述:
光照通常是在纹理映射之前进行的;
光照效果:
--可以看到纹理贴图;
--光照可以大幅度增加场景的真实感;
--启用光照,将看不到物体表面的颜色信息;
--启用光照,可以看到物体表面的材质信息;
--物体表面的法线,将决定光的反射方向;
9. OpenGL中的光照模型:
--环境光;
--漫反射光;
--镜面光;
(1)环境光:
--环境光并不来自任何特定的方向,他来自某个光源,但是光线在场景中四处反射;
--环境光将物体的各个方向的表面均匀照亮;
--颜色与旋转、观察角度无关;
环境光是全局光源,只有颜色,没有方向和位置,且只有一个。
OpenGL至少支持8个独立光源,具有位置和照射方向;
(2)漫反射光:
--OpenGL的漫反射光具有方向性,来自一个特定的方向;
--根据入射光线的角度在表面均匀的反射,是分布在各个方向上的;
--从任何角度看上去,光照效果都是一样的;
(3)镜面光:
--镜面光具有很强的方向性;
--照射表面形成亮点;
--反射方向几乎是一致的;
--镜面反射可以使物体看起来闪闪发亮;
--不同的角度,镜面反射的效果是不一样的;
10.全局光照模型:光线跟踪算法,辐射度算法。
第四讲
1. 雾:可以使远处的物体呈现出朦胧的感觉,距离视点很远的物体几乎就看不见了。雾是一种有效的深度暗示。
2. 积累缓冲区
原理:
(1) OpenGL在渲染到颜色缓冲区之后,不是直接显示在窗口上,而是复制到积累缓冲区;
(2) 在积累缓冲区反复混合后,再执行缓冲区交换进行显示。
作用:
(1) 使用不同的视点多次渲染场景,积累后可以达到整个场景的完全抗锯齿,效果优于多重采样;
(2) 实现运动模糊的效果;
3. 抖动:使用少量的颜色生成丰富的颜色。
4. 位图和像素图
--位图用2色(1位)表示一个点;
--像素图用256色(8位)表示一个点;
5. 将图像映射到屏幕的两种方式:
(1) 使用像素图绘制:图像像素与屏幕像素严格对应;
(2) 纹理贴图:图像像素经过一定变换后映射到屏幕像素;
6. 纹理贴图:
基本概念:
(1) 纹理贴图就是把图像数据应用到三维图元中;
(2) 纹理贴图给三维图形带来了丰富的表面特征;
(3) 纹理单元是纹理中的个体图像元素;
7. 凹凸纹理分为:
(1) 位移映射;
(2) 法线映射;
8. 位移映射:位移贴图是使用高度图将经过纹理化的表面上实际几何点位置沿着表面法线根据保存在纹理中的数值进行移位的技术。
第五讲
1. 渲染:计算机根据模型创建图像。
--模型是由几何图元构成的,而几何图元是通过顶点来指定的,OpenGL将点、直线、多边形、图形和位图视为图元。
--最终渲染的图像由屏幕像素组成;
2. 渲染的过程:
(1) 建模:使用几何图元建立模型,从而得到物体的数学描述;
(2) 变换:在三维空间排列物体,选择观察场景的有利位置;
(3) 着色:计算所有物体的颜色;
(4) 光栅化:将物体的数学描述和相关的颜色信息转换为屏幕像素;
3. 什么是渲染管线?
当我们把绘制的图形传递给OpenGL后,OpenGL还要做许多才能完成3D空间到屏幕的投影。这一系列的过程称为OpenGL的渲染管线。
一般的渲染步骤如下:
(1) 显示列表;
(2) 求值程序;
(3) 顶点操作;
(4) 图元装配;
(5) 像素操作;
(6) 纹理装配;
(7) 光栅化;
(8) 片段操作;
4. OpenGL建立三维模型的基本步骤:
(1) 视点变换;
(2) 模型变换;
(3) 投影变换;
(4) 视口变换;
这样,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的计算机屏幕上正确显示了。
5. OpenGL中的渲染管线主要分为两个阶段:
(1) 首先是基于顶点的操作,然后图元被光栅化,产生片段;
(2) 在片段写入到帧缓冲之前进行纹理、雾和其他基于片段的操作;
6. 顶点处理分为4个阶段:顶点变换、光照、纹理坐标和变换、裁剪。
7. 基于片段操作的结果是一个颜色值。
8. OpenGL着色语言(GLSL)是一种对GPU进行编程的高级语言,具有紧凑的代码,良好的可读性和更高的效率。
GLSL的语法和C语言非常接近。
GLSL使用两种类型的对象:着色器对象和程序对象。
第六讲
1. 什么是动画?
(1) 从制作层面上讲:动画是指不使用真人表演拍摄,而是用各种技术手段产生的具有艺术价值的活动影像;
(2) 从技术层面上讲:动画是将一系列静态图像顺序播放,利用人的视觉滞留效应,产生连续运动的观赏效果。
视觉滞留效应:当被观察者的物体消失后,影像仍能在大脑中停留一段时间,大约为1/10s.
2. 三维动画的种类:变形动画、骨骼动画。
3. 动作引擎技术:与物理引擎类似,在游戏引擎中引入动作引擎。主要处理动作控制和动作反馈。
第八讲(考试要点复习)
1. 什么是OpenGL?
(1) OpenGL = Open Graphics Library
(2) 定义:图形硬件的一种软件接口;
(3) 最初由SGI创建,用于在不同硬件体系结构的图形设备上进行二维和三维图形的绘制;
(4) OpenGL不是编程语言,但是包含了GLSL,其特点是并行浮点矢量运算速度极快,但不能进行流程控制。
2. OpenGL的扩展机制:
(1) ARB:标准扩展;
(2) EXT:多家支持的扩展;
3. OpenGL与平台
(1) 面向嵌入式平台的OpenGL ES;
(2) 面向浏览器的WebGL;
(3) 面向MS Windows的wgl;
(4) 面向mac os的cgl;
(5) 面向X Windows的glx;
4. OpenGL与相关工具
(1)跨平台工具箱:GLUT = OpenGL Utility Toolkit
--glut由SGI开发;
--能够兼容win/linux/mac;
--最新开源的freeglut;
(2)处理扩展的包装库:GLEW
5. OpenGL状态机
(1) OpenGL使用一组状态变量的集合来保持图形渲染管线的状态;
(2) OpenGL使用了一种状态模型(状态机)来跟踪所有状态变量;
(3) 当一个值被设置后,就一直保持这个状态;
6.基本几何图元
(1)GL_POINTS:每个顶点在屏幕上都是一个单独的点;
(2)GL_LINES:每一对顶点定义一个线段;
(3)GL_LINE_STRIP:从第一个顶点依次经过后续顶点而绘制的线条;
(4)GL_LINE_LOOP:同上,但最后一个顶点和第一个顶点连接;
(5)GL_TRIANGLES:每三个顶点定义一个新的三角形;
(6)GL_TRIANGLE_STRIP:公用一个条带上的顶点的一组三角形;
(7)GL_TRIANGLE_FAN:以一个原点为中心呈扇形排列,共用相邻顶点的一组三角形;
6. 三角形的环绕属性:
逆时针为正方向,可以通过glFrontFace(GL_CW)修改。
7. 背面的剔除
(1) 画家算法
--将需要绘制的图元排序,首先绘制最远的,依次靠近;
--低效和资源占用;
(2)隐面剔除
-- glEnable(GL_CULL_FACE)
-- glDisable(GL_CULL_FACE)
8. 深度测试的对比
自遮挡的对象,没有启用深度测试就会产生显示错误。
9. 多边形偏移:当图元的坐标非常接近,即使启用深度测试,也会产生闪烁,Z值相同的情况。
10. 什么是向量?
三维向量用一个三元组(X,Y,Z)表示。
(1) 顶点是一个向量,表示空间中的一个位置;
(2) 三维坐标可以表示一个向量,向量有长度,向量也有方向,所以,向量=向+量。
11. 向量的定义
(1) 向量是从坐标系原点到点(X,Y,Z)的带箭头的线段;
(2) 空间中的一个点,既是顶点,又是向量;
12.向量在图形中的作用
(1)表示位置:顶点;
(2)表示方向:视线方向,平面法线;
13.单位向量
(1)长度为1的向量称为单位向量;
(2)将一个任意向量转化为单位向量,称为归一化,将该向量除以向量的长度。
14.点乘
(1)两个三维向量的点乘是一个标量;
(2)表示一个向量投影到另外一个向量的长度;
(3)在单位向量中,两个向量的点乘是其夹角的余弦值;
15.叉乘
(1)两个向量的叉乘得到的是垂直于这两个向量的第三个向量;
(2)用途:计算平面的法向;
(3)特点:顺序不可交换;
16.矩阵
(1)矩阵是一个由行和列构成的数据结构;
(2)在程序中一般以二维数组存放;
(3)OpenGL是以列主存放的;
17.矩阵的作用:空间变换
(1)三维空间中的仿射变换都是使用矩阵运算进行的:旋转、平移、缩放;
(2)OpenGL中的矩阵:视图变换、模型变换、投影变换;
19. 几个基本概念
(1) 视图变换:设定观察者或摄像机的位置;
(2) 模型变换:在场景中移动物体;
(3) 模型视图:模型和视图的一致性;
(4) 投影变换:设定视景体的大小和形状;
(5) 视口变换:窗口缩放;
20. 视图变换
(1) 视图变换的作用就是设定观察者的位置,以及视线防线;
(2) 可以理解为在场景中摆放摄像机:
--摄像机所在的位置;
--摄像机对准的方向;
(3)在任何其他变换之前先用视图变换,能够保证与视觉坐标系一致;
(4)默认从(0,0,0)看向Z轴负方向;
(5)正投影中,视点在Z轴正无穷远处,可以看到视景体中的任何物体;
21. 模型变换
(1) 模型变换用于操作模型和其中的特定对象;
(2) 将对象移动到需要的位置,然后进行旋转和缩放;
22. 模型和视图的一致性:模型变换和视图变换最后形成一个统一的模型视图矩阵。
23. 模型视图矩阵
(1) 模型视图矩阵是一个4*4矩阵;
(2) 原始顶点坐标是一个四维向量,与模型视图矩阵相乘,得到变换后的新坐标;
(3) 注意:在数学上向量应该放在右边,左乘一个变换矩阵;
(4) 在OpenGL中,向量是行向量,矩阵是列主矩阵,相当于整体转置了;
24. 什么是投影?
从三维裁剪空间到二维屏幕空间。
25. 投影的方式及其特点?
正投影
透视投影
26. 正投影
特点:没有畸变,视觉不真实;
主要用于建筑设计、CAD、或2D绘图。
视景体为方形。
27. 透视投影
特点:近大远小,视觉真实。
视景体为平截头体;
28. OpenGL通过分别指定红、绿、蓝成分的强度来指定一种颜色。
29. 对所有可用的颜色进行建模,创建立方体,称为RGB颜色空间。
--原点(0,0,0),黑色;
--对角线顶点(255,255,255),白色;
--从原点到每根轴的方向,分别是红绿蓝的饱和分布。
30. 光照模型
(1) 局部光照模型:光强仅与被照明的物体的光源有关;
(2) 全局光照模型:光强与场景中任一点都相关;
31. 局部光照模型:假定光是点光源,物体是非透明物体,且表面光滑,透射光和散射光将近似于零。
--局部光照模型中只考虑反射光的作用;
--反射光包括环境光、漫反射光、镜面反射光;
32. 环境光
(1) 环境光并不来自任何特定的方向,他来自某个光源,但是光线在场景中四处反射。
(2) 环境光将物体的各个方向的表面均匀照亮。
(3) 颜色与旋转和观察角无关。
(4) 照射在物体上的光来自各个方向,又均匀向各个方向反射。
33. 散射光
(1) OpenGL的散射光具有方向性,来自一个特定的方向;
(2) 根据入射光线的角度在表面均匀地反射。
(3) 在点光源的照射下,物体表面的不同部分亮度不同,亮度的大小依赖于物体表面的朝向以及它与点光源之间的距离。
(4) 漫反射特点:光源来自一个方向,反射光均匀地射向各个方向。
34. 镜面光
(1) 镜面光具有很强的方向性;
(2) 照射表面形成亮点;
35. 镜面反射
(1) 反射方向是几乎一致的;
(2) 镜面反射可以使物体看起来闪闪发光;
(3) 不同的角度,镜面反射的效果是不一样的;
36. 表面法线:法线的方向决定了多边形正面的朝向。
37. 局部光照模型的局限:
(1) 仅考虑从光源直接发出的光线对物体表面光亮度的贡献;
(2) 没有考虑光线在物体之间的相互反射和透射。
38. 全局光照模型:Whitted模型;
可模拟现实世界中景物表面之间的镜面反射和透射现象的贡献。
39. Whitted模型
假设从某一观察方向V所观察到的物体表面某点P的光亮度的贡献来自三个方面:
(1) 由光源直接照射引起的反射光亮度;
(2) 环境镜面反射光;
(3) 环境规则透射光;
40. 为什么会走样?
连续几何空间和离散屏幕像素之间的矛盾。
41. 多重采样
反锯齿功能的局限性:在复杂场景中,需要对所有图元进行前后排序。
几个术语:
全屏反锯齿;
超级采样抗锯齿;
多重采样抗锯齿;
42. 积累缓冲区
原理:
(1) OpenGL在渲染到颜色缓冲区之后,不是直接显示在窗口上,而是复制到积累缓冲区;
(2) 在积累缓冲区反复混合后,再执行缓冲区交换进行显示;
作用:
(1) 使用不同的视点多次渲染场景,积累后可以达到整个场景的完全抗锯齿,效果优于多重采样;
(2) 实现运动模糊效果;
43.位图和像素图
(1)位图用2色(1位)表示一个点;
(2)像素图用256色(8位)表示一个点;
43. 纹理贴图基本概念
(1) 纹理贴图就是把图像数据应用到三维图元中;
(2) 纹理贴图给三维图形带来了丰富的表面特征;
(3) 纹理单元是纹理中的个体图像元素;
44. 最临近过滤
(1) 最临近过滤:会出现马赛克;
(2) 线性过滤:平滑更接近真实;
45. 为什么使用mipmapping?
(1) 解决闪烁的问题;
(2) 减少纹理加载的浪费;
mipmapping纹理由一系列纹理图像组成,每个图形的大小都是前面的一半大小。
Mipmapping是一种LOD技术。
46. 纹理对象
(1) 纹理对象可以使我们一次加载多个图像,并且在这些纹理对象之间切换;
(2) 纹理对象是一个无符号整数;
47. 各向异性过滤
(1) 对周围各个方向上的像素进行取样计算后映射到目标像素上的技术;
(2) 与双线性过滤和三线性过滤相比,它在大角度显示方面具有更高的精度,令画面更逼真,但计算量也更大,对显卡的要求更高。
48. OpenGL渲染管线,什么是渲染管线?
(1) 当我们把绘制的图形传递给OpenGL后,OpenGL还要做许多步骤才能完成3D空间到屏幕的投影,这一系列的过程称为OpenGL渲染流水线。
(2) 一般的渲染流水线有如下几步:
--显示列表;
--求值程序;
--顶点操作;
--图元装配;
--像素操作;
--纹理装配;
--光栅化;
--片段操作;
49. OpenGL中的固定管线总结:OpenGL中的渲染管线主要分为两个阶段:
(1) 首先是基于顶点的操作,然后图元被光栅化,产生片段;
(2) 在片段写入到帧缓冲区之前进行纹理、雾以及其他基于片段的操作;
50. 固定的顶点处理:
--基于顶点的阶段从一组顶点属性开始;
--这些属性包括对象空间位置,法线,主颜色和辅助颜色以及纹理坐标;
--基于顶点的处理的结果是裁剪空间位置、正面和背面主颜色和辅助颜色、一个雾坐标、纹理坐标、点大小;
--顶点处理分为4个阶段:顶点变换、光照、纹理坐标变换、裁剪;
51. 固定的片段操作
--用一个片段以及与它相关的数据作为输入,包括纹理坐标、主颜色和辅助颜色、雾坐标;
--基于片段操作的结果是一个颜色值;
--基于片段的固定功能管线分为4个阶段:
(1)纹理应用和环境;
(2)颜色求和;
(3)雾应用;
(4)抗锯齿应用;
52.可编程渲染管线
--使用着色器替代固定管线中的部分阶段;
--着色器也可以称为程序;
--着色器实质是应用程序所定义的自定义程序,用于接替固定功能管线阶段的职责;
52. OpenGL着色语言
--OpenGL着色语言(GLSL)是一种对GPU进行编程的高级语言,具有紧凑的代码,良好的可读性和更高的效率;
--GLSL语法和C语言的非常接近;
53. 着色器对象
--GLSL使用两种类型的对象:着色器对象和程序对象;
--着色器对象加载着色器文本并对它进行编译;
--着色器对象是最小的作用单元,但是不能独立运行,需要绑定到“程序”中执行。
54. 着色器统一值
--属性是每个顶点位置、表面法向和纹理坐标等都需要的,而统一值则用于整个图元批次想保持不变的着色器传递数据;
--对于顶点着色器,统一值可能是变换矩阵;
--统一值是只读的;
55. 高度图和凹凸纹理
--使用高度图的像素值确定物体表面的光照特性;
--凹凸纹理分为:位移映射和法线映射;
位移映射:位移贴图是使用高度图将经过纹理化的表面上实际几何点位置沿着表面法线根据保存在纹理中的数值进行移位的技术。
56. 顶点数组:使用顶点数组可以加速数据载入,在多次绘制间保存和共享数据。
57. 索引顶点数组:索引顶点数组不是按顺序从头遍历顶点数组,由一个单独的索引数组指定。
优点:两个三角形带之间共享顶点,节省内存,减少变换开销;
58. 动画的分类:
按照制作方式分类:
(1) 传统动画:手绘动画;
(2) 定格动画:粘土动画、木偶动画;
(3) 计算机动画:二维动画、三维动画;
59. 三维动画的种类
(1) 变形动画;
(2) 骨骼动画;
60. 变形动画的优点:
(1) 能够精细的控制形状的变化;
(2) 针对面部表情动画特别适用;
变形动画的缺点:
(1) 动画数据量大;
(2) 制作复杂度高;
(3) 很难在多个角色间重用;
61. 骨骼动画技术:
--模型创建;
--骨骼创建;
--骨骼绑定;
--动画设定:关键帧、动作数据
62. 朝向的表示:
(1) 使用矩阵表示;
(2) 使用欧拉角;
(3) 使用四元数;
63. 动作捕捉的优点:
--真实记录了每个动作细节;
缺点:
--难以修改:对动作数据的修改很容易导致失真;
--难以控制:没有很好的方法对捕获的动作进行控制;
--难以重用:应用到不同的角色、骨架;
标签:
原文地址:http://blog.csdn.net/chenyufeng1991/article/details/50530204