博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CSharpGL(11)用C#直接编写GLSL程序
阅读量:6087 次
发布时间:2019-06-20

本文共 11972 字,大约阅读时间需要 39 分钟。

CSharpGL(11)用C#直接编写GLSL程序

+BIT祝威+悄悄在此留下版了个权的信息说:

2016-08-13

由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了。CSharpGL源码中包含10多个独立的Demo,更适合入门参考。

为了尽可能提升渲染效率,CSharpGL是面向Shader的,因此稍有难度。

由来

本项目的目的:使开发者可以直接用C#书写GLSL代码。

现在(2016年1月30日)编写GLSL的shader程序时,并没有什么好的开发环境。智能提示、代码补全、自动排版都没有。基本上我是用notepad++之类的编辑器写的。

很苦恼,一度导致我对shader有偏见。

GLSL是类似C语言的。我发现几乎所有的GLSL里出现的语法形式都可以用C#以相同的方式写出来。那么用C#来写"GLSL代码",之后再自动转换为纯粹的GLSL代码,岂非一大快事?!

在本项目定义的类型基础上,你就可以直接用C#来写GLSL代码了。(只有很少的几点不同,到时候你会立即明白的。)

C#版的GLSL,以后就称为CSSL(C# Shader Language)。

下载

这个项目是CSharpGL的一部分,CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入()

+BIT祝威+悄悄在此留下版了个权的信息说:

示例

从一个简单的例子来抽象出整个项目的设计方案来。

Vertex shader(GLSL)

这是一个典型的vertex shader。

1 #version 150 core 2  3 in vec3 in_Position; 4 in vec2 in_UV;   5 out vec2 pass_UV; 6  7 uniform mat4 projectionMatrix; 8 uniform mat4 viewMatrix; 9 uniform mat4 modelMatrix;10 11 void main(void) 12 {13     gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0);14 15     pass_UV = in_UV;16 }

对应的C#写法(CSSL)

我用如下的C#代码与之对应,并期望将来能够将其自动转化为上文的vertex shader。

1     class DemoVert 2 { 3 vec4 gl_Position; 4  5         [In] 6         vec3 in_Position; 7         [In] 8         vec2 in_UV; 9         [Out]10         vec2 pass_UV;11 12         [Uniform]13         mat4 projectionMatrix;14         [Uniform]15         mat4 viewMatrix;16         [Uniform]17         mat4 modelMatrix;18 19         void main()20         {21             gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0);22             pass_UV = in_UV;23         }24     }

 

Fragment shader(GLSL)

这是一个典型的fragment shader。与上文的vertex shader可以组成一个shader program。

1 #version 150 core 2  3 in vec2 pass_UV; 4 out vec4 out_Color; 5 uniform sampler2D texture1; 6 uniform sampler2D texture2; 7 uniform float percent; 8  9 void main(void) 10 {11     vec4 color = texture(texture1, pass_UV) * percent + texture(texture2, pass_UV) * (1.0 - percent);12     out_Color = color;13     //out_Color = texture(texture2, pass_UV);14     //out_Color = texture(texture1, pass_UV);15 }

对应的C#写法(CSSL)

我用如下的C#代码与之对应,并期望将来能够将其自动转化为上文的fragment shader。

1     class DemoFrag 2     { 3         [In] 4         vec2 pass_UV; 5         [Out] 6         vec4 out_Color; 7  8         [Uniform] 9         sampler2D texture1;10         [Uniform]11         sampler2D texture2;12         [Uniform]13         float percent;14         void main()15         {16             vec4 color = texture(texture1, pass_UV) * percent + texture(texture2, pass_UV) * (1.0f - percent);17             out_Color = color;18             //out_Color = texture(texture2, pass_UV);19             //out_Color = texture(texture1, pass_UV);20         }21 22         private vec4 texture(sampler2D texture1, vec2 pass_UV)23         {24             throw new NotImplementedException();25         }26 27     }
+BIT祝威+悄悄在此留下版了个权的信息说:

设计

大体思路就如上面的例子。顶点属性、uniform变量都可以用C#字段表示。main函数、内置函数、内置变量都可以用C#相应的函数和类型表示。

稍微有所不同的是,'in','out','uniform'等这些qualifier只好用Attribute代表了。

子函数尚未涉及,到时候再说。

不同类型的shader(vertex、fragment、geometry、tessellation等)都有些相同的内置函数,也都有各自独特的内置变量,这就是本项目的类库设计要描述的对象。

对于用户来说,用户只需写出CSSL的代码,即可一键自动获取GLSL的代码。

CSSL写好了,当然应该自动地转换为GLSL。否则还有什么意义。

CSSL

将C#代码转换为另一种形式,无非是反射+字符串解析拼接之类的东西。

设计方案很简单。包含CSSL的*.cs文件作为输入,对应的GLSL文件(*.vert或*.frag)作为输出。用反射获取in、out、uniform这些变量,用正则表达式获取main函数代码。最后用字符串拼接起来就是。Shader有多种,所以要有一个抽象和继承关系。

上图是对CSSL代码的分析和设计图。注意,这里的CSSL代码对我这个开发者而言,只是一堆存储在*.cs文件里的字符串。虽然其内容是C#代码,但其本质仍然是字符串,只不过这个字符串的内容是一些C#代码。可不要绕晕了。

语义化的Shader

获取语义化的shader,就是从字符串形式的CSSL到内存中的数据结构这样一个过程。这实际上是一个极其简陋的编译器做的事。

导出GLSL

获取字段的过程用反射就可以实现。

1         private void Parse() 2         { 3             FieldInfo[] fields = this.shaderCode.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); 4             foreach (var field in fields) 5             { 6                 if (field.GetCustomAttribute
() != null) 7 { 8 this.fields.Add(new FieldTemplate(FieldQualifier.In, field.FieldType, field.Name)); 9 }10 else if (field.GetCustomAttribute
() != null)11 {12 this.fields.Add(new FieldTemplate(FieldQualifier.Out, field.FieldType, field.Name));13 }14 else if (field.GetCustomAttribute
() != null)15 {16 this.fields.Add(new FieldTemplate(FieldQualifier.Uniform, field.FieldType, field.Name));17 }18 }19 20 this.mainFunction = SearchMainFunction(this.fullname);21 }

找到主函数代码就得用正则表达式了。

1         protected override string SearchMainFunction(string fullname) 2         { 3             string content = File.ReadAllText(fullname); 4             // class XxxVertexShader : VertexShaderCode 5             Match match = Regex.Match(content, @"class\s+" + this.shaderCode.GetType().Name + @"\s*:"); 6             int classStart = match.Index + match.Length; 7             // public override void main() { ... } 8             match = Regex.Match(content.Substring(classStart), 9                 @"public\s+override\s+void\s+main\s*\(\s*\)\s*\{
");10 // 自行找到main(){}函数的‘}’11 int firstLeftBrace = classStart + match.Index + match.Length - 1;12 int left = 1;13 int lastRightBrace = -1;14 for (int i = firstLeftBrace + 1; i < content.Length; i++)15 {16 char c = content[i];17 if (c == '\"')18 {19 for (int j = i + 1; j < content.Length; j++)20 {21 char tmp = content[j];22 if (tmp == '\"')23 {24 i = j;25 break;26 }27 }28 }29 else if (c == '\'')30 {31 i = i + 2;32 }33 else if (c == '{
')34 {35 left++;36 }37 else if (c == '}')38 {39 left--;40 if (left == 0)41 {42 lastRightBrace = i;43 break;44 }45 }46 }47 48 StringBuilder mainBuilder = new StringBuilder();49 mainBuilder.AppendLine("void main(void)");50 mainBuilder.AppendLine("{
");51 string[] parts = content.Substring(firstLeftBrace + 1, lastRightBrace - (firstLeftBrace - 1))52 .Split(separator, StringSplitOptions.RemoveEmptyEntries);53 int preEmptyCount = 0;54 {55 string line = Regex.Replace(parts[parts.Length - 1], "\t", " ");56 preEmptyCount = Regex.Match(line, @" *").Length;57 }58 foreach (var item in parts)59 {60 string line = Regex.Replace(item, "\t", " ");61 62 if (Regex.Match(line, @"[\t ]*").Length >= preEmptyCount)63 {64 line = line.Substring(preEmptyCount);65 }66 mainBuilder.AppendLine(line);67 }68 return mainBuilder.ToString();69 }
SearchMainFunction
+BIT祝威+悄悄在此留下版了个权的信息说:

使用

学习上手

为了方便教学使用,我制作了一个GUI程序。

你可以在这里找到他。

工程实际

每次用GUI都手动加载一遍在长期的工程实践中也是很烦人的。所以我提供一个Console程序,可以用脚本、VS生成事件等方式自动调用。这样,每次编译整个项目时,就可以顺带更新GLSL代码了。

How to do

我以下面这个项目为例说明,如何借助VS自带的生成事件来使用这个Console。

首先如上图所示添加CSharpShaderLanguage.dll和CSharpGL.CSSL2GLSL.exe两个文件,并设置其属性为"如果较新则复制"。

 

然后,如下图所示,添加两个CSharp文件,并编写CSSL代码。这里就体现出了使用本项目的好处之一:编写CSSL的过程本质是在VS下编写C#代码,你可以尽情享用VS提供的便利!

然后设置项目属性如下。参数..\..\表示CSSL2GLSL.exe要向上查找2个层级的文件夹。没有参数时则表示此CSSL2GLSL.exe所在的文件夹。

一切就绪,只欠F6。按F6生成,VS会自动调用CSSL2GLSL.exe。

如果你修改了CSSL代码,那么就会收到这样的提示:

这说明CSSL2GLSL.exe被VS自动调用,更新了你的GLSL代码!

所以,你得再按一次F6,到不再出现上面的提示为止。

编译完成后CSSL2GLSL.exe会自动打开log文件和文件夹,方便你查看编译的结果。

这样一来,我们的GLSL代码也就有了编译时的语法检查了。这是应用本项目的另一个好处。

+BIT祝威+悄悄在此留下版了个权的信息说:

2016-02-16

手动执行CSSL2GLSL

经过一段时间的使用,我发现上面的自动调用CSSL2GLSL也很烦人。常常在没有更改cssl代码时,CSSL2GLSL.exe也会执行,而且执行速度也不够快。这导致F6编译项目时等待时间增长了几倍,无法忍受。所以现在我不再使用上面的自动执行的方式,改为将CSSL2GLSL.exe放到solution文件夹下,需要时自己手动执行。

独立的扩展名

现在我规定CSSL代码文件的扩展名必须是*.cssl.cs。这样方便System.IO.Directory.GetFiles()识别,也就避免了处理大量无关代码的情形。

精简版log

一般cssl.cs文件不会出什么问题,所以看那个冗长的完整版log也很浪费时间和心情。所以现在在生成完整版log时也会生成一个精简版log,例如。

1 Directory: C:\Users\威\Documents\GitHub\CSharpGL 2 Found 20 CSSL shaders, and 0 of them are dumped to GLSL as needed. 3     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Demos\CSharpGL.LightEffects\DiffuseReflectionDirectionalLight.cssl.cs 4     2 CSSL shaders: 5         Not need to dump [DiffuseReflectionDirectionalLightVert] to [DiffuseReflectionDirectionalLight.vert] OK! 6         Not need to dump [DiffuseReflectionDirectionalLightFrag] to [DiffuseReflectionDirectionalLight.frag] OK! 7     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Demos\CSharpGL.LightEffects\DiffuseReflectionPointLight.cssl.cs 8     2 CSSL shaders: 9         Not need to dump [DiffuseReflectionPointLightVert] to [DiffuseReflectionPointLight.vert] OK!10         Not need to dump [DiffuseReflectionPointLightFrag] to [DiffuseReflectionPointLight.frag] OK!11     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Demos\CSharpGL.LightEffects\PhongPointLight.cssl.cs12     2 CSSL shaders:13         Not need to dump [PhongPointLightVert] to [PhongPointLight.vert] OK!14         Not need to dump [PhongPointLightFrag] to [PhongPointLight.frag] OK!15     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Demos\CSharpGL.Objects.Demos\NormalLine.cssl.cs16     3 CSSL shaders:17         Not need to dump [NormalLineVert] to [NormalLine.vert] OK!18         Not need to dump [NormalLineFrag] to [NormalLine.frag] OK!19         Not need to dump [NormalLineGeom] to [NormalLine.geom] OK!20     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Demos\CSharpGL.Objects.Demos\Simple.cssl.cs21     3 CSSL shaders:22         Not need to dump [SimpleVert] to [Simple.vert] OK!23         Not need to dump [SimpleFrag] to [Simple.frag] OK!24         Not need to dump [SimpleGeom] to [Simple.geom] OK!25     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Functions\CSharpGL.Objects.Common\AxisElement.cssl.cs26     2 CSSL shaders:27         Not need to dump [AxisElementVert] to [AxisElement.vert] OK!28         Not need to dump [AxisElementFrag] to [AxisElement.frag] OK!29     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Tools\CSharpGL.ObjViewer\ObjFile.cssl.cs30     3 CSSL shaders:31         Not need to dump [ObjFileVert] to [ObjFile.vert] OK!32         Not need to dump [ObjFileGeom] to [ObjFile.geom] OK!33         Not need to dump [ObjFileFrag] to [ObjFile.frag] OK!34     --> Translating C:\Users\威\Documents\GitHub\CSharpGL\Tools\ShaderLab\Some.cssl.cs35     3 CSSL shaders:36         Not need to dump [SomeVert] to [Some.vert] OK!37         Not need to dump [SomeFrag] to [Some.frag] OK!38         Not need to dump [SomeGeom] to [Some.geom] OK!39 Translation all done!
CSSL2GLSLDump20160216-125347.simple.log

而在有了cssl.cs扩展名这个规定后,我发现完整版log常常和精简版log的内容完全相同。这也成了一种快速推测cssl是否有问题的方式。

支持geometry shader

现在的CSSL支持geometry shader的编写和代码生成。并且,代码生成过程中也会自动解析用户自定义的结构类型,例如下面这样的:

1 in VS_GS_VERTEX2 {3     vec3 normal;4 } vertex_in[];

还有下面这样的,都支持。

1 out GS_FS_VERTEX2 {3     vec3 color;4 } vertex_out;
对应上面的两个GLSL类型,C#中的CSSL写法是这样的:
1         class VS_GS_VERTEX2         {3             public vec3 normal;//必须是public的字段4         }5         [In]6         VS_GS_VERTEX[] vertex_in;
1         class GS_FS_VERTEX2         {3             public vec3 color;//必须是public的字段4         }5         [Out]6         GS_FS_VERTEX vertex_out;

初始值的自动转化

此外,像下面这样的C#中的初始值也支持自动转化到GLSL。
1         [Uniform]2         float normalLength = 0.5f;

这个会自动转换为GLSL中的:

1 uniform float normalLength = 0.5;

总结

目前的CSSL并未完全覆盖GLSL的功能。因为我原本就没有多少写GLSL的经历。等我慢慢用GLSL的情形多了,再逐步补充CSSL吧。

 

转载地址:http://qcvwa.baihongyu.com/

你可能感兴趣的文章
C#数据采集类
查看>>
quicksort
查看>>
【BZOJ2019】nim
查看>>
LINUX内核调试过程
查看>>
【HDOJ】3553 Just a String
查看>>
Java 集合深入理解(7):ArrayList
查看>>
2019年春季学期第四周作业
查看>>
linux环境配置
查看>>
tomcat指定配置文件路径方法
查看>>
linux下查看各硬件型号
查看>>
epoll的lt和et模式的实验
查看>>
Flux OOM实例
查看>>
07-k8s-dns
查看>>
Android 中 ListView 分页加载数据
查看>>
oracle启动报错:ORA-00845: MEMORY_TARGET not supported on this system
查看>>
Go方法
查看>>
Dapper丶DapperExtention,以及AbpDapper之间的关系,
查看>>
搞IT的同学们,你们在哪个等级__那些年发过的帖子
查看>>
且谈语音搜索
查看>>
MySQL数据库导入导出常用命令
查看>>