Creating GLSL Shaders at Runtime in Unity3D

//

该概念涉及用户能够在程序运行时编写着色器,并在运行时将它们编译到场景中的对象上。通常这不是一个不合理的任务,但是这个项目是在 Unity 中构建的,这使事情变得非常复杂。

我之前看过一个在运行时将 shaderlab 代码传递给 Material 构造函数的示例,但我从未见过有人以同样的方式使用任何其他着色器语言。原来那是因为你做不到。我希望使用的Material 构造函数只接受 Shaderlab;Unity 不支持 GLSL、Cg 或 HLSL 的运行时编译,故事结束。

除了这不是全部。如果是的话,这将是一个非常短的帖子。事实证明,使用一些肘部油脂,您实际上可以编译其他语言(或至少 GLSL)。这篇文章的其余部分将向您展示如何。

设置您的项目

之前至少有几个人尝试过完成这项工作。在 Google 上快速搜索“运行时着色器编译统一”将带您进入此 Unity 论坛帖子。如果向下滚动,您会发现来自名为 Sirithang 的用户的帖子,他是该帖子真正的无名英雄。

他们的帖子谈到了一个名为 CgBatch 的工具,它包含在 Unity 中,根据这个 SIGGRAPH 演示文稿,它要么是 Unity 的整个着色器编译管道,要么至少是其中的一个步骤。siggraph 链接仅将其描述为生成 HLSL 的工具,但实际上它似乎将着色器完全转换为该材质构造函数从上面接受的格式。由于 CgBatch 不是供公众使用的,因此没有任何文档可以确定。

好的,所以我们知道我们需要使用 CgBatch,但是我们从哪里得到它。在 Mac 上,您可以在 Unity.app 中找到它(右键单击并选择“显示包内容”),在 Tools 文件夹中。在 Windows 上,您正在寻找位于 Unity/Editor/Data/Tools 中的 CgBatch.exe。感谢@izaleu在 Windows 上找到这个 :D)。在项目的 StreamingAssets 目录中创建一个文件夹并将 CgBatch 粘贴到其中(它必须在 StreamingAssets 的子目录中)。

CgBatch 还依赖于 Cg.framework,您可以在 Unity.app/Contents/Frameworks 文件夹中找到它。但是,如果您尝试运行 CgBatch,您会注意到它实际上依赖于位于“../Frameworks/Cg.framework”中的 Cg.framework,因此请将整个文件夹复制并粘贴到项目的 StreamingAssets 文件夹中。

最后,作为使用 CgBatch 的一部分,您需要提供 CGInclude 文件的路径,并且由于我们不希望我们的用户必须安装 Unity 才能使用我们的程序,因此您还需要将 CGIncludes 文件夹复制到您的StreamingAssets 目录。

另外:如果您以前从未使用过 StreamingAssets 文件夹,那么它只是您放置在项目资产文件夹中的一个文件夹,名称为“StreamingAssets”,该文件夹中的所有内容都将完全包含在您构建项目的 Application.streamingAssetsPath 中。

解密 CgBatch

那么如何使用 CgBatch。如果您尝试从命令行运行它,您可能会看到以下消息:

E -1:启动 CgBatch 失败(参数不正确)。用法:CgBatch 输入路径 includepath 输出 [-xbox360] [-ps3]


所以 CgBatch 至少需要 4 个参数。根据之前链接的论坛帖子,这些论点如下:

  • 输入:未编译着色器文件的路径
  • path:包含着色器的目录的路径
  • includepath : Unity 的 CGInclude 文件的路径
  • output:放置输出着色器文件的位置。

如果您使用适当的参数运行它,您应该能够获得材质着色器字符串构造函数可以接受的输出,这很棒!所以现在我们需要能够在一个正在运行的程序中做到这一点。

System.Diagnostics 简介

值得庆幸的是,Mono 覆盖了我们(即使在 Mac 上!)。Process 类(在 System.Diagnostics 中)专门设计用于运行命令行应用程序,并且可以配置为在 bash 和 windows 命令行中执行程序。

这样做的方法是创建一个新的 Process 对象,并使用该对象的 StartInfo 属性来准确指定您希望执行的命令和参数,然后调用 Process.Start();

在实践中,这如下所示:

using System.Diagnostics;
	
Process process = new Process();
process.StartInfo.FileName = "bash";
process.StartInfo.Arguments = "-c '" + [Command] [arg1] [arg2] ... +"'";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;

process.Start();

(以上是针对mac的,我没有windows机器可以试试这个东西)

如上图,需要执行的命令名称其实是bash,而不是CgBatch。为了从批处理中执行命令,您需要使用 -c 标志将其作为参数传递给 bash,并将命令及其所有参数括在单引号内。

将 RedirectStandardOutput 设置为 true 允许我们将命令的输出读入 Unity 控制台(对于调试非常方便),但为了使其工作,需要将 UseShellExecute 设置为 false,这意味着我们不会使用操作系统 shell 来启动程序(在本例中为 bash),我们将直接启动 bash。

实际上使这项工作

现在我们已经设置好了工具,现在我们如何执行 CgBatch,是时候将它们放在一起了。

为了概念验证,我只希望用户编写片段着色器,所以我需要为他们提供一个顶点着色器:

	
string prefix = "Shader \"Temp\"{\nProperties{\n}\nSubShader {" +
	"\nTags { \"Queue\" = \"Geometry\" }\nPass {\nGLSLPROGRAM\n#ifdef VERTEX\n" +
	"void main(){\n" +
	"gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" +
	"}\n" +
	"#endif\n" +
	"#ifdef FRAGMENT\n" +
	"uniform float _time;\n";
			

上面的示例用于在运行时编写 glsl 着色器。我还不能使用本文中介绍的方法进行 Cg 编译,但我确信它可以通过 CgBatch 的正确参数来完成。

你会注意到我还包括一个时间制服。这是因为我还没有弄清楚如何让 Unity 的特定常量在用户编写的着色器中被识别,并且 Time 非常有用,我将它传递给我自己(只需在 Update 中调用 Shader.SetGlobalFloat 参数来执行相同的)。

接下来,我们需要编写将在用户的片段着色器之后完成着色器文件的代码:

	
string suffix = "\n#endif\nENDGLSL}}}";

正如变量名所暗示的,在构建我们的输入文件时,用户的片段着色器将位于这两个字符串之间。

获取您认为合适的用户输入(如前面的图片所示,我现在使用 Unity.GUI),然后使用前缀+USERINPUT+后缀组合完整的文件字符串。

一旦你组装了完整的着色器字符串,你需要将它写入一个文件,因为 CgBatch 期望输入参数是一个文件路径。由于我们不希望此文件在运行之间持续存在,因此我将输入文件写入 Application.temporaryCachePath。

	
byte[] byteShader = System.Text.Encoding.UTF8.GetBytes(prefix+shader+suffix);

var tempShader = File.Create(Application.temporaryCachePath+"/tempshader.shader");
tempShader.Write(byteShader,0,(prefix+suffix+shader).Length);
tempShader.Close();

最后,我们需要读入输出并用它实际构建一个材料。总之,着色器编译过程如下所示:

	
byte[] byteShader = System.Text.Encoding.UTF8.GetBytes(prefix+shader+suffix);

var tempShader = File.Create(Application.temporaryCachePath+"/tempshader.shader");
tempShader.Write(byteShader,0,(prefix+suffix+shader).Length);
tempShader.Close();

Process compileProcess = new Process();
compileProcess.StartInfo.FileName = "bash";

compileProcess.StartInfo.Arguments = "-c '"
	+Application.streamingAssetsPath
	+"/Tools/CGBatch "
	+Application.temporaryCachePath
	+"/tempshader.shader ../CGIncludes/ ../CGIncludes/"
	+Application.temporaryCachePath
	+"/testOutput.shader'";
	
compileProcess.StartInfo.RedirectStandardOutput = true;
compileProcess.StartInfo.UseShellExecute = false;

compileProcess.Start();
var output = compileProcess.StandardOutput.ReadToEnd();
compileProcess.WaitForExit();

string compiled = File.ReadAllText(Application.temporaryCachePath
		+"/testOutput.shader");
									
Material m = new Material(compiled);
cube.renderer.material = m;

UnityEngine.Debug.Log(output);

以上仅在mac上测试过。在 Windows 上,您需要将“bash”替换为“cmd”,并将参数替换为适合您系统的任何内容。不幸的是,我没有一台 Windows 机器来测试它(再次,在 twitter 上给我发消息,我会更新这个)。

但是,如果您使用的是 Mac,或者已经了解了 Windows 的更改,您现在应该能够在运行时编译 GLSL!面对 Unity 不支持这个功能,你笑了!

您可能还会注意到您的构建产品比您预期的大 50MB。这是因为我们将所有 Cg.framework 包含在我们的项目中,以便 CgBatch 可以在编译期间使用它。我希望这个额外的文件大小是 Unity 默认选择不使用此功能的众多原因之一。

//

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值