使用Visual Studio SDK制作GLSL词法着色插件

使用Visual Studio SDK制作GLSL词法着色插件



我们在Visual Studio上开发OpenGL ES项目时,避免不了写Shader。这时在vs里直接编辑shader就会显得很方便。但是vs默认是不支持GLSL的语法着色的,我们只好自己动手创造。最简单的实现自定义语法着色的方法就是创建一个VSIX插件包,我们只需要安装Visual Studio SDK,使用内置的模版就可以构建一个插件项目。

1. 安装Visual Studio SDK



http://www.microsoft.com/en-us/download/details.aspx?id=40758下载最新的Visual Studio 2013 SDK。

双击安装,一路next即可。

安装完毕后我们可以在新建项目->模版->C#中看到“扩展性”这一条目,这些就是开发插件用的模版了。

2. 创建插件项目



新建项目,在扩展性标签中,选择Editor Classifier模版,命名为ShaderEditor,点击确定。

Visual Studio为我们生成了如下几个文件。

ShaderEditorFormat.cs文件的默认代码如下:

 1     [Export(typeof(EditorFormatDefinition))]
 2     [ClassificationType(ClassificationTypeNames = "ShaderEditor")]
 3     [Name("ShaderEditor")]
 4     [UserVisible(true)] //this should be visible to the end user
 5     [Order(Before = Priority.Default)] //set the priority to be after the default classifiers
 6     internal sealed class ShaderEditorFormat : ClassificationFormatDefinition {
 7         /// <summary>
 8         /// Defines the visual format for the "ShaderEditor" classification type
 9         /// </summary>
10         public ShaderEditorFormat() {
11             this.DisplayName = "ShaderEditor"; //human readable version of the name
12             this.BackgroundColor = Colors.BlueViolet;
13             this.TextDecorations = System.Windows.TextDecorations.Underline;
14         }
15     }

这段代码定义了一个名为"ShaderEditor"的着色类型,编译工程并运行,我们可以在Visual Studio实验实例的工具->选项->字体和颜色中找到一个名为"ShaderEditor"的条目。同时我们会发现所有文本文件的颜色都变成了Colors.BlueViolet并带上了下划线。修改this.DisplayName = "ShaderEditor"的内容,可以改变在字体和颜色中显示的名字。下面的格式设置可以任意修改成喜欢的样式,但要注意在这里的格式只是插件首次安装时的默认设置,这些条目和其它着色选项一样,都可以被用户任意更改。

3. 创建GLSL的着色类型


我们已经了解了如何将着色类型添加到Visual Studio,现在修改ShaderEditorFormat.cs,添加我们的着色类型。
 1     [Export(typeof(EditorFormatDefinition))]
 2     [ClassificationType(ClassificationTypeNames = "GLSLText")]
 3     [Name("GLSLText")]
 4     [UserVisible(true)]
 5     [Order(Before = Priority.Default)]
 6     internal sealed class GLSLTextFormatDefinition : ClassificationFormatDefinition {
 7         public GLSLTextFormatDefinition() {
 8             this.DisplayName = "GLSL文本";
 9             this.ForegroundColor = Colors.Brown;
10         }
11     }
12
13     [Export(typeof(EditorFormatDefinition))]
14     [ClassificationType(ClassificationTypeNames = "GLSLIdentifier")]
15     [Name("GLSLIdentifier")]
16     [UserVisible(true)]
17     [Order(Before = Priority.Default)]
18     internal sealed class GLSLIdentifierFormatDefinition : ClassificationFormatDefinition {
19         public GLSLIdentifierFormatDefinition() {
20             this.DisplayName = "GLSL标识符";
21             this.ForegroundColor = Colors.Brown;
22         }
23     }
24
25     [Export(typeof(EditorFormatDefinition))]
26     [ClassificationType(ClassificationTypeNames = "GLSLComment")]
27     [Name("GLSLComment")]
28     [UserVisible(true)]
29     [Order(Before = Priority.Default)]
30     internal sealed class GLSLCommentFormatDefinition : ClassificationFormatDefinition {
31         public GLSLCommentFormatDefinition() {
32             this.DisplayName = "GLSL注释";
33             this.ForegroundColor = Colors.DarkGray;
34         }
35     }
36
37     [Export(typeof(EditorFormatDefinition))]
38     [ClassificationType(ClassificationTypeNames = "GLSLKeyword")]
39     [Name("GLSLKeyword")]
40     [UserVisible(true)]
41     [Order(Before = Priority.Default)]
42     internal sealed class GLSLKeywordFormatDefinition : ClassificationFormatDefinition {
43         public GLSLKeywordFormatDefinition() {
44             this.DisplayName = "GLSL关键字";
45             this.ForegroundColor = Colors.Blue;
46         }
47     }
48
49     [Export(typeof(EditorFormatDefinition))]
50     [ClassificationType(ClassificationTypeNames = "GLSLClass")]
51     [Name("GLSLClass")]
52     [UserVisible(true)]
53     [Order(Before = Priority.Default)]
54     internal sealed class GLSLClassFormatDefinition : ClassificationFormatDefinition {
55         public GLSLClassFormatDefinition() {
56             this.DisplayName = "GLSL类型";
57             this.ForegroundColor = Colors.Green;
58         }
59     }
60
61     [Export(typeof(EditorFormatDefinition))]
62     [ClassificationType(ClassificationTypeNames = "GLSLQualifier")]
63     [Name("GLSLQualifier")]
64     [UserVisible(true)]
65     [Order(Before = Priority.Default)]
66     internal sealed class GLSLQualifierFormatDefinition : ClassificationFormatDefinition {
67         public GLSLQualifierFormatDefinition() {
68             this.DisplayName = "GLSL限定符";
69             this.ForegroundColor = Colors.Pink;
70         }
71     }
72
73     [Export(typeof(EditorFormatDefinition))]
74     [ClassificationType(ClassificationTypeNames = "GLSLVariable")]
75     [Name("GLSLVariable")]
76     [UserVisible(true)]
77     [Order(Before = Priority.Default)]
78     internal sealed class GLSLVariableFormatDefinition : ClassificationFormatDefinition {
79         public GLSLVariableFormatDefinition() {
80             this.DisplayName = "GLSL系统变量";
81             this.ForegroundColor = Colors.DarkOrange;
82         }
83     }
84
85     [Export(typeof(EditorFormatDefinition))]
86     [ClassificationType(ClassificationTypeNames = "GLSLFunction")]
87     [Name("GLSLFunction")]
88     [UserVisible(true)]
89     [Order(Before = Priority.Default)]
90     internal sealed class GLSLFunctionFormatDefinition : ClassificationFormatDefinition {
91         public GLSLFunctionFormatDefinition() {
92             this.DisplayName = "GLSL系统函数";
93             this.ForegroundColor = Colors.DarkTurquoise;
94         }
95     }

4. 导出着色类型



Editor Classifier使用了MEF框架,关于MEF的具体细节,请参考MSDN的相关文档。

我们需要注意的是,在MEF中,光定义了着色类型还不够,我们需要导出一个ClassificationTypeDefinition,才能在系统中生效。

打开ShaderEditorType.cs,我们看到系统生成的代码如下:

1     internal static class ShaderEditorClassificationDefinition {
2         [Export(typeof(ClassificationTypeDefinition))]
3         [Name("ShaderEditor")]
4         internal static ClassificationTypeDefinition ShaderEditorType = null;
5     }

这里的Name与之前默认生成的ShaderEditor相同,同理,我们将这里的代码修改成方才定义的类型

 1     internal static class ShaderEditorClassificationDefinition {
 2         [Export(typeof(ClassificationTypeDefinition))]
 3         [Name("GLSLText")]
 4         internal static ClassificationTypeDefinition GLSLTextType = null;
 5
 6         [Export(typeof(ClassificationTypeDefinition))]
 7         [Name("GLSLIdentifier")]
 8         internal static ClassificationTypeDefinition GLSLIdentifierType = null;
 9
10         [Export(typeof(ClassificationTypeDefinition))]
11         [Name("GLSLComment")]
12         internal static ClassificationTypeDefinition GLSLCommentType = null;
13
14         [Export(typeof(ClassificationTypeDefinition))]
15         [Name("GLSLKeyword")]
16         internal static ClassificationTypeDefinition GLSLKeywordType = null;
17
18         [Export(typeof(ClassificationTypeDefinition))]
19         [Name("GLSLClass")]
20         internal static ClassificationTypeDefinition GLSLClassType = null;
21
22         [Export(typeof(ClassificationTypeDefinition))]
23         [Name("GLSLQualifier")]
24         internal static ClassificationTypeDefinition GLSLQualifierType = null;
25
26         [Export(typeof(ClassificationTypeDefinition))]
27         [Name("GLSLVariable")]
28         internal static ClassificationTypeDefinition GLSLVariableType = null;
29
30         [Export(typeof(ClassificationTypeDefinition))]
31         [Name("GLSLFunction")]
32         internal static ClassificationTypeDefinition GLSLFunctionType = null;
33     }

5. 关联文件类型



打开ShaderEditor.cs

 1     [Export(typeof(IClassifierProvider))]
 2     [ContentType("text")]
 3     internal class ShaderEditorProvider : IClassifierProvider {
 4         [Import]
 5         internal IClassificationTypeRegistryService ClassificationRegistry = null; // Set via MEF
 6
 7         public IClassifier GetClassifier(ITextBuffer buffer) {
 8             return buffer.Properties.GetOrCreateSingletonProperty<ShaderEditor>(delegate { return new ShaderEditor(ClassificationRegistry); });
 9         }
10     }

代码ContentType("text")创建了一个Provider并将它们对所有text类型的文件生效。

GLSL主要的文件扩展名为.vsh和.fsh,为了只对这两个扩展名生效,我们需要自定义一个ContentType,并创建两个扩展名关联。将上述代码修改为:

 1     [Export(typeof(ITaggerProvider))]
 2     [ContentType("glsl")]
 3     [TagType(typeof(ClassificationTag))]
 4     internal sealed class GLSLClassifierProvider : ITaggerProvider {
 5
 6         [Export]
 7         [Name("glsl")]
 8         [BaseDefinition("code")]
 9         internal static ContentTypeDefinition GLSLContentType = null;
10
11         [Export]
12         [FileExtension(".vsh")]
13         [ContentType("glsl")]
14         internal static FileExtensionToContentTypeDefinition GLSLVshType = null;
15
16         [Export]
17         [FileExtension(".fsh")]
18         [ContentType("glsl")]
19         internal static FileExtensionToContentTypeDefinition GLSLFshType = null;
20
21         [Import]
22         internal IClassificationTypeRegistryService classificationTypeRegistry = null;
23
24         [Import]
25         internal IBufferTagAggregatorFactoryService aggregatorFactory = null;
26
27         public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag {
28             return new GLSLClassifier(buffer, classificationTypeRegistry) as ITagger<T>;
29         }
30     }

这样我们就创建了只针对vsh和fsh文件生效的Editor。

6. 使用gplex进行词法分析



我们需要使用词法分析扫描器来实现具体的着色功能,gplex可以为我们生成C#语言的扫描器,下载地址:

http://gplex.codeplex.com/

解压后在binaries文件夹下找到gplex.exe,把它拷贝到项目的根目录下。

在项目根目录下新建一个GLSL文件夹,新建GLSLLexer.lex文件。并把它们添加到proj中。

在proj上右键->属性,在生成事件选项卡中,在预先生成事件命令行中输入

cd $(ProjectDir)GLSL\
$(ProjectDir)\gplex GLSLLexer

打开GLSLLexer.lex,写入以下代码:

 1 %option unicode, codepage:raw
 2
 3 %{
 4         // User code is all now in ScanHelper.cs
 5 %}
 6
 7 %namespace Shane
 8 %option verbose, summary, noparser, nofiles, unicode
 9
10 %{
11     public int nextToken() { return yylex(); }
12     public int getPos() { return yypos; }
13     public int getLength() { return yyleng; }
14 %}
15
16 //=============================================================
17 //=============================================================
18
19 number ([0-9])+
20 chars [A-Za-z]
21 cstring [A-Za-z_]
22 blank " "
23 delim [ \t\n]
24 word {chars}+
25 singleLineComment "//"[^\n]*
26 multiLineComment "/*"[^*]*\*(\*|([^*/]([^*])*\*))*\/
27
28 comment {multiLineComment}|{singleLineComment}
29 class bool|int|float|bvec|ivec|vec|vec2|vec3|vec4|mat2|mat3|mat4|sampler1D|sampler2D|sampler3D|samplerCube|sampler1DShadow|sampler2DShadow
30 keyword return|if|else|while|do|for|foreach|break|continue|switch|case|default|goto|class|struct|enum|extern|interface|namespace|public|static|this|volatile|using|in|out|true|false
31 qualifier const|attribute|uniform|varying
32 systemVariable gl_BackColor|gl_BackLightModelProduct|gl_BackLightProduct|gl_BackMaterial|gl_BackSecondaryColor|gl_ClipPlane|gl_ClipVertex|gl_Color|gl_DepthRange|gl_DepthRangeParameters|gl_EyePlaneQ|gl_EyePlaneR|gl_EyePlaneS|gl_EyePlaneT|gl_Fog|gl_FogCoord|gl_FogFragCoord|gl_FogParameters|gl_FragColor|gl_FragCoord|gl_FragData|gl_FragDepth|gl_FrontColor|gl_FrontFacing|gl_FrontLightModelProduct|gl_FrontLightProduct|gl_FrontMaterial|gl_FrontSecondaryColor|gl_LightModel|gl_LightModelParameters|gl_LightModelProducts|gl_LightProducts|gl_LightSource|gl_LightSourceParameters|gl_MaterialParameters|gl_MaxClipPlanes|gl_MaxCombinedTextureImageUnits|gl_MaxDrawBuffers|gl_MaxFragmentUniformComponents|gl_MaxLights|gl_MaxTextureCoords|gl_MaxTextureImageUnits|gl_MaxTextureUnits|gl_MaxVaryingFloats|gl_MaxVertexAttribs|gl_MaxVertexTextureImageUnits|gl_MaxVertexUniformComponents|gl_ModelViewMatrix|gl_ModelViewMatrixInverse|gl_ModelViewMatrixInverseTranspose|gl_ModelViewMatrixTranspose|gl_ModelViewProjectionMatrix|gl_ModelViewProjectionMatrixInverse|gl_ModelViewProjectionMatrixInverseTranspose|gl_ModelViewProjectionMatrixTranspose|gl_MultiTexCoord0|gl_MultiTexCoord1|gl_MultiTexCoord10|gl_MultiTexCoord11|gl_MultiTexCoord2|gl_MultiTexCoord3|gl_MultiTexCoord4|gl_MultiTexCoord5|gl_MultiTexCoord6|gl_MultiTexCoord7|gl_MultiTexCoord8|gl_MultiTexCoord9|gl_Normal|gl_NormalMatrix|gl_NormalScale|gl_ObjectPlaneQ|gl_ObjectPlaneR|gl_ObjectPlaneS|gl_ObjectPlaneT|gl_Point|gl_PointParameters|gl_PointSize|gl_Position|gl_ProjectionMatrix|gl_ProjectionMatrixInverse|gl_ProjectionMatrixInverseTranspose|gl_ProjectionMatrixTranspose|gl_SecondaryColor|gl_TexCoord|gl_TextureEnvColor|gl_TextureMatrix|gl_TextureMatrixInverse|gl_TextureMatrixInverseTranspose|gl_TextureMatrixTranspose|gl_Vertex
33 systemFunction radians|degress|sin|cos|tan|asin|acos|atan|pow|exp|log|exp2|log2|sqrt|inversesqrt|abs|sign|floor|ceil|fract|mod|min|max|clamp|mix|step|smoothstep|length|distance|dot|cross|normalize|faceforward|reflect|matrixCompMult|lessThan|lessThanEqual|greaterThan|greaterThanEqual|equal|notEqual|any|all|not|texture2D|texture2DProj|texture2DLod|texture2DProjLod|textureCube|textureCubeLod
34 identifier {cstring}+{number}*[{cstring}@]*{number}*
35
36 %%
37
38 {keyword}            return (int)GLSLTokenType.Keyword;
39 {class}                return (int)GLSLTokenType.Class;
40 {qualifier}            return (int)GLSLTokenType.Qualifier;
41 {systemVariable}    return (int)GLSLTokenType.SystemVariable;
42 {systemFunction}    return (int)GLSLTokenType.SystemFunction;
43 {identifier}        return (int)GLSLTokenType.Identifier;
44 {comment}            return (int)GLSLTokenType.Comment;
45
46 %%

保存并关闭,这时生成一下项目,我们会看到在GLSL目录下生成了GLSLLexer.cs文件,同样把这个文件添加到proj中。

7. 处理扫描结果



接下来我们要在ShaderEditor.cs中处理我们的扫描结果,并最终对匹配的代码行进行着色。

首先删除默认创建的ShaderEditor类。

添加一个GLSLToken枚举,这个枚举就是GLSLLexer.cs返回的枚举类型,它用来通知我们当前的语句块是哪个类型。

代码如下:

 1     public enum GLSLTokenType {
 2         Text = 1,
 3         Keyword,
 4         Comment,
 5         Identifier,
 6         Class,
 7         Qualifier,
 8         SystemVariable,
 9         SystemFunction
10     }

创建我们自己的ShaderEditor类,代码如下:

 1 internal sealed class GLSLClassifier : ITagger<ClassificationTag> {
 2         ITextBuffer textBuffer;
 3         IDictionary<GLSLTokenType, IClassificationType> typeDic;
 4         internal GLSLClassifier(ITextBuffer buffer, IClassificationTypeRegistryService typeService) {
 5             textBuffer = buffer;
 6             typeDic = new Dictionary<GLSLTokenType, IClassificationType>();
 7             typeDic[GLSLTokenType.Text] = typeService.GetClassificationType("GLSLText");
 8             typeDic[GLSLTokenType.Identifier] = typeService.GetClassificationType("GLSLIdentifier");
 9             typeDic[GLSLTokenType.Keyword] = typeService.GetClassificationType("GLSLKeyword");
10             typeDic[GLSLTokenType.Class] = typeService.GetClassificationType("GLSLClass");
11             typeDic[GLSLTokenType.Comment] = typeService.GetClassificationType("GLSLComment");
12             typeDic[GLSLTokenType.Qualifier] = typeService.GetClassificationType("GLSLQualifier");
13             typeDic[GLSLTokenType.SystemVariable] = typeService.GetClassificationType("GLSLVariable");
14             typeDic[GLSLTokenType.SystemFunction] = typeService.GetClassificationType("GLSLFunction");
15         }
16
17         public event EventHandler<SnapshotSpanEventArgs> TagsChanged {
18             add { }
19             remove { }
20         }
21
22         public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
23             IClassificationType classification = typeDic[GLSLTokenType.Text];
24             string text = spans[0].Snapshot.GetText();
25             yield return new TagSpan<ClassificationTag>(
26                 new SnapshotSpan(spans[0].Snapshot, new Span(0, text.Length)),
27                 new ClassificationTag(classification));
28             scanner.SetSource(text, 0);
29             int tok;
30             do {
31                 tok = scanner.nextToken();
32                 int pos = scanner.getPos();
33                 int len = scanner.getLength();
34                 int total = text.Length;
35                 if (pos < 0 || len < 0 || pos > total) {
36                     continue;
37                 }
38                 if (pos + len > total) {
39                     len = total - pos;
40                 }
41                 if (typeDic.TryGetValue((GLSLTokenType)tok, out classification)) {
42                     yield return new TagSpan<ClassificationTag>(
43                         new SnapshotSpan(spans[0].Snapshot, new Span(pos, len)),
44                         new ClassificationTag(classification));
45                 }
46             } while (tok > (int)Tokens.EOF);
47         }
48
49         Scanner scanner = new Scanner();
50     }
TagsChanged事件保证在代码发生改变时实时刷新编辑器。

构造方法中,通过typeService.GetClassificationType("GLSLIdentifier")取得我们定义的实例,并把它们和枚举关联起来,

GetClassificationType的参数传入着色类型的Name。

GetTags方法是由系统调用的迭代方法,yield return new TagSpan<ClassificationTag>()返回我们的着色对象,即可实现着色效果。

编译并运行,可以看到vsh和fsh已经有了语法着色了。

本文由哈萨雅琪原创,转载请注明出处。

使用Visual Studio SDK制作GLSL词法着色插件,布布扣,bubuko.com

时间: 2024-10-10 22:04:20

使用Visual Studio SDK制作GLSL词法着色插件的相关文章

[转]使用Visual Studio SDK制作GLSL词法着色插件

本文转自(http://www.cnblogs.com/aanbpsd/p/3920067.html) 原作者故意写错了一点东西,这就让那些一点脑筋也不想动的小伙伴得不到想要的结果.我在这里把那些地方纠正过来了. [转]使用Visual Studio SDK制作GLSL词法着色插件 我们在Visual Studio上开发OpenGL ES项目时,避免不了写Shader.这时在vs里直接编辑shader就会显得很方便.但是vs默认是不支持GLSL的语法着色的,我们只好自己动手创造.最简单的实现自定

艾伦 Visual Studio 批量自动化代码操作工具-VS插件发布

艾伦 Visual Studio 批量自动化代码操作工具 以下简称--艾伦工具箱. 艾伦工具箱是一个多文件批量处理插件,目的是为了广大开发者提高开发效率,减少项目代码规范化审计,缩短开发者的项目开发周期. 名称:艾伦 Visual Studio 批量自动化代码操作工具. 亮点:批量操作. 功能:1,代码格式化:2,移除与重排Using. 可选:1,针对整个解决方案进行自动化操作:2,针对选中的当前项目进行自动化操作:3,针对指定的后缀名代码文件进行操作: 注意事项:默认支持*.cs.*.vb,可

Visual Studio 2012 比较好用的插件推荐

为了高效率的开发,下面笔者推荐几款非常不错的插件,方便大家.   以上控件的安装方式是: 然后通过联网的方式下载,安装后,需要重启一下Visual Studio方可使用.

如何在Visual Studio中开发自己的代码生成器插件

 Visual Studio是美国微软公司开发的一个基本完整的开发工具集,它包括了整个软件生命周期中所需要的大部分工具,如UML工具.代码管控工具.集成开发环境(IDE)等等,且所写的目标代码适用于微软支持的所有平台.可以说.NET开发人员离不开它,它可以极大的提高编写软件的效率. Visual Studio作为一个世界级开发工具,当然支持通过插件方式对其功能进行扩展,开发人员可以定制自己的插件来进一步提升Visual Studio的功能. 1 什么是Add In? 所谓的add-in就是一些被

提高Visual Studio开发性能的几款插件

通过打开Visual Studio,单机TOOLS—Extensions and Updates-Online-Visual Studio Gallery(工具-扩展和更新-联网-Visual Studio库)进行搜索和安装 1.Indent Guides 一款显示格式化块虚线的插件,安装后不管css.html.javascript.c#都能使用 2.JSLint 验证你的JS代码,JSLint可以为你做这件事.使用这个插件可能刚开始会让你感觉不爽,因为他使用了很多类似C#的规则(例如,某些变量

Visual Studio 有哪些好用的插件?

推荐一批绝大部分都是免费的能较好增强写代码舒适度的. .NET Demon -- (非免费)安装后可以连续编译, 如果不买License的话过期后也还有代码改动后自动保存的上好功能, 安装它之后再也不用费心保存了. AllMargins -- 在增强的滚动条, 可以以各种彩色呈现代码的结构和错误之处, 直接点击定位. C# Outline -- 给C#所有{}代码块加折叠功能. Code Alignment --提供代码对齐功能, 比如按 =, (, ) 等符号对齐, 也可以自定义对齐基准字符(

安装 visual studio code 前端所需的插件 beautify, ESLint

以安装以下插件.. 打开“用户设置”,在右边的窗口输入以下配置, 保存后,会自动与左边的默认配置合并.....然后,,编辑器页面就好看多了..

Visual Studio 项目模板制作(三)

前面,我们已经制作好了模板,然后放到相应的Template目录就可以在Visual Studio中使用 本篇,我们采用安装VSIX扩展的方式来安装模板,这种方式需要安装Visual Studio SDK 安装了SDK之后,可以在新建项目里面看到VSIX Project 选择VSIX Project 然后设置一下名称,点击确定,项目就新建完成了 项目结构: 现在我们开始 首先,双击打开source.extension.vsixmanifest 设置扩展的各种属性 然后,添加我们前面两篇制作的模板

有感 Visual Studio 2015 RTM 简介 - 八年后回归 Dot Net,终于迎来了 Mvc 时代,盼走了 Web 窗体时代

有感 Visual Studio 2015 RTM 简介 - 八年后回归 Dot Net,终于迎来了 Mvc 时代,盼走了 Web 窗体时代 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 关于