在Kotlin上开发GLSL着色器



大家好!

我们公司正在开发在线游戏,现在我们正在开发主要项目的移动版本。 在本文中,我们希望与示例和源代码共享为Android项目开发GLSL着色器的经验。

关于项目


最初,该游戏是基于Flash的浏览器,但是有关即将停止支持Flash的消息迫使我们将项目转移到HTML5。 Kotlin被用作开发语言,六个月后,我们能够在Android上启动该项目。 不幸的是,没有在移动设备上进行优化,游戏就缺乏性能。

为了提高FPS,决定重新设计图形引擎。 我们曾经使用多个通用着色器,但是现在对于每种效果,我们决定编写一个单独的着色器,针对特定任务进行锐化处理,以使它们的工作效率更高。

我们缺乏的


着色器可以存储在字符串中,但是此方法消除了语法检查和类型匹配,因此着色器通常存储在Assets或Raw文件中,因为这允许您通过安装Android Studio插件来启用验证。 但是这种方法也有一个缺点-缺乏重用性:要进行小的编辑,必须创建一个新的着色器文件。

这样:

-在Kotlin上开发着色器,
-在编译阶段进行语法检查,
-能够在着色器之间重用代码,
我需要为GLSL写一个“转换器” Kotlin。

所需的结果:着色器代码被描述为Kotlin类,其中属性,变量,统一是此类的属性。 该类的主要构造函数的参数用于静态分支,并允许您重用其余的着色器代码。 初始化块是着色器的主体。

解决方案


为了实现,使用了Kotlin 代表 。 它们允许运行时找出委托属性的名称,以捕获调用的获取和设置时间,并通知它们ShaderBuilder(所有着色器的基类)。

class ShaderBuilder { val uniforms = HashSet<String>() val attributes = HashSet<String>() val varyings = HashSet<String>() val instructions = ArrayList<Instruction>() ... fun getSource(): String = ... } 

委托实施
各种代表:

 class VaryingDelegate<T : Variable>(private val factory: (ShaderBuilder) -> T) { private lateinit var v: T operator fun provideDelegate(ref: ShaderBuilder, p: KProperty<*>): VaryingDelegate<T> { v = factory(ref) v.value = p.name return this } operator fun getValue(thisRef: ShaderBuilder, property: KProperty<*>): T { thisRef.varyings.add("${v.typeName} ${property.name}") return v } operator fun setValue(thisRef: ShaderBuilder, property: KProperty<*>, value: T) { thisRef.varyings.add("${v.typeName} ${property.name}") thisRef.instructions.add(Instruction.assign(property.name, value.value)) } } 

其余代表在GitHub上的实现

着色器示例:

 //    useAlphaTest     , //       , ,  , //   . class FragmentShader(useAlphaTest: Boolean) : ShaderBuilder() { private val alphaTestThreshold by uniform(::GLFloat) private val texture by uniform(::Sampler2D) private val uv by varying(::Vec2) init { var color by vec4() color = texture2D(texture, uv) // static branching if (useAlphaTest) { // dynamic branching If(color.w lt alphaTestThreshold) { discard() } } //     ShaderBuilder. gl_FragColor = color } } 

这是生成的GLSL源(FragmentShader的结果(useAlphaTest = true).getSource())。 代码的内容和结构得以保留:

 uniform sampler2D texture; uniform float alphaTestThreshold; varying vec2 uv; void main(void) { vec4 color; color = texture2D(texture, uv); if ((color.w < alphaTestThreshold)) { discard; } gl_FragColor = color; } 

通过在组装源时设置不同的参数来方便地重用着色器代码,但这不能完全解决重用问题。 如果需要在不同的着色器中编写相同的代码,则可以将这些指令放入单独的ShaderBuilderComponent中,并根据需要将它们添加到主要的ShaderBuilders中:

 class ShadowReceiveComponent : ShaderBuilderComponent() { … fun vertex(parent: ShaderBuilder, inp: Vec4) { vShadowCoord = shadowMVP * inp ... parent.appendComponent(this) } fun fragment(parent: ShaderBuilder, brightness: GLFloat) { var pixel by float() pixel = texture2D(shadowTexture, vShadowCoord.xy).x ... parent.appendComponent(this) } } 

欢呼,收到的功能使您可以在Kotlin上编写着色器,重用代码,检查语法!

现在,让我们回想一下 GLSL中的Swizzling,并看看它在Vec2,Vec3,Vec4中的实现。

 class Vec2 { var x by ComponentDelegate(::GLFloat) var y by ComponentDelegate(::GLFloat) } class Vec3 { var x by ComponentDelegate(::GLFloat) ... //  9 Vec2 var xx by ComponentDelegate(::Vec2) var xy by ComponentDelegate(::Vec2) ... } class Vec4 { var x by ComponentDelegate(::GLFloat) ... //  16 Vec2 var xy by ComponentDelegate(::Vec2) ... //  64 Vec3 var xxx by ComponentDelegate(::Vec3) ... } 

在我们的项目中,着色器编译可以按需在游戏周期中进行,并且此类对象分配会产生主要的GC调用,出现延迟。 因此,我们决定使用注释处理器将着色器源的组合转移到编译阶段。

我们用ShaderProgram批注标记该类:

 @ShaderProgram(VertexShader::class, FragmentShader::class) class ShaderProgramName(alphaTest: Boolean) 

注释处理器根据顶点和片段类构造函数的参数为​​我们收集各种着色器:

 class ShaderProgramNameSources { enum class Sources(vertex: String, fragment: String): ShaderProgramSources { Source0("<vertex code>", "<fragment code>") ... } fun get(alphaTest: Boolean) { if (alphaTest) return Source0 else return Source1 } } 

现在,您可以从生成的类中获取着色器文本:

 val sources = ShaderProgramNameSources.get(replaceAlpha = true) println(sources.vertex) println(sources.fragment) 

由于get函数-ShaderProgramSources的结果是枚举中的值,因此在程序注册表(ShaderProgramSources)-> CompiledShaderProgram中将其用作键很方便。

GitHub具有该项目的源代码,包括注释处理器以及着色器和组件的简单示例。

Source: https://habr.com/ru/post/zh-CN425027/


All Articles