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

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

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

由来

本项目的目的:使开发者可以直接用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有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

+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<InAttribute>() != null)
 7                 {
 8                     this.fields.Add(new FieldTemplate(FieldQualifier.In, field.FieldType, field.Name));
 9                 }
10                 else if (field.GetCustomAttribute<OutAttribute>() != null)
11                 {
12                     this.fields.Add(new FieldTemplate(FieldQualifier.Out, field.FieldType, field.Name));
13                 }
14                 else if (field.GetCustomAttribute<UniformAttribute>() != 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祝威+悄悄在此留下版了个权的信息说:

总结

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

时间: 2024-10-18 02:35:31

CSharpGL(11)用C#直接编写GLSL程序的相关文章

已知如下数组: var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

已知如下数组: var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组 var dt= arr.toString().split(",").sort(function(a,b){return a-b}).map(Number);Array.from(new Set(dt)) 代码如下 var d

编写一个程序找出100~999之间所有的水仙花数

如果一个3位数等于其各位的立方和,称该数为水仙花数. 如,所以407是一个水仙花数,编写一个程序找出100~999之间所有的水仙花数. 1 #include<stdio.h> 2 #include<stdlib.h> 3 //判断水仙花数,是则返回1 4 int isNarcissus(int n); 5 6 int main() 7 { 8 int i; 9 for(i = 100; i < 1000; i++) 10 if(isNarcissus(i)) 11 print

编写语法分析程序

编写语法分析程序 Note: Mr.JY的编译原理! 文法改造 1.文法 1) <program>→{<declaration_list><statement_list>} 2) <declaration_list>→<declaration_list><declaration_stat> | ε 3) <declaration_stat>→int ID; 4) <statement_list>→<sta

编写一个程序实现strcpy函数的功能

1 #include <stdio.h> 2 #include <string.h> 3 #define N 5 4 5 6 char *mycpy(char *s1, char *s2) 7 { 8 //数组型 9 /* int i; 10 while(s2[i] != '\0') { 11 s1[i] = s2[i]; 12 i++; 13 } 14 s1[i] = '\0'; 15 return s1; */ 16 //指针型 17 char *p = s1; 18 whil

编写一个程序实现strcmp函数的功能

写自己的strcat函数------→mycmp 1 #include <stdio.h> 2 #include <string.h> 3 #define N 5 4 5 int mycmp(char *s1, char *s2) 6 { 7 //数组型 8 /* int i = 0; 9 while(s1[i] == s2[i] && s1[i] != '\0') { 10 i++; 11 } 12 13 return s1[i] - s2[i]; */ 14 /

编写一个程序实现strcat函数的功能

写自己的strcat函数------→mycat 1 #include <stdio.h> 2 #include <string.h> 3 #define N 5 4 5 char *mycat(char *s1, char *s2) 6 { 7 //数组型 8 /* int i = 0; 9 while(s1[i] != '\0') { 10 i++; 11 } 12 int j = 0; 13 while(s2[j] != '\0') { 14 s1[i] = s2[j]; 1

第 2 章 编写 C# 程序

2.1  Visual Studio 2005开发环境 2.2  控制台应用程序 试试看:创建一个简单的控制台应用程序 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { // Output text to the

《编写一个程序,从一个文件中读出字符串,并显示在屏幕上》

注意:在程序的第11行用fgets函数读入字符串时,指定一次读入10个字符,但按fgets函数的规定, 如果遇到“\n”就结束字符串输入,“\n”作为最后一个字符也读入到字符数组中 //编写一个程序,从f:\\FILE_1\\file_2.txt中读回字符串 #include<stdio.h>#include<string.h>#include<stdlib.h>int main(){ FILE *fp; char str[3][10]; int i=0; if((fp

用python + hadoop streaming 编写分布式程序(三) -- 自定义功能

又是期末又是实训TA的事耽搁了好久……先把写好的放上博客吧 前文: 用python + hadoop streaming 编写分布式程序(一) -- 原理介绍,样例程序与本地调试 用python + hadoop streaming 编写分布式程序(二) -- 在集群上运行与监控 使用额外的文件 假如你跑的job除了输入以外还需要一些额外的文件(side data),有两种选择: 大文件 所谓的大文件就是大小大于设置的local.cache.size的文件,默认是10GB.这个时候可以用-fil