Libgdx如何在3D中使用着色器

7
我在我的WIP游戏中遇到了一个问题,希望能使其更具吸引力。目前,我向Environment添加了一些AmbientlightDirectionla-light,并使用它渲染场景。但现在我想为它添加自定义的Shader。因此,我一直在寻找一些教程,但几乎每个教程中他们都使用了另一种在游戏中使用Shader的"版本":
  • ModelBatch提供StringFileHandlevertex/fragment-shader
  • 创建一个带有顶点和片元ShaderShaderProgram
  • 使用这个顶点和片元Shader来创建一个新的DefaultShader
  • 创建一个实现Shader接口的类,并使用这个ShaderClass
我认为还有更多可能性,因为还有ShaderProvider和其他类。
由于所有这些可能性让我有点困惑,所以我正在寻找能够指引我正确方向的人。
为了让您更容易理解,我会告诉您我拥有什么和我需要什么:
我有:
  1. 一个应该绘制一切的ModelBatch
  2. 一个Array<ModelInstance>实例,我想要将Shader应用于其中。
  3. 一张纹理和它的法线贴图,都以Texture形式存储。
  4. 我的光源的positiondirectioncolor,以及它的强度float
  5. 环境光的color和强度,分别以Vector3float给出。
  6. 光的"衰减"以Vector3形式给出。
我需要:
  1. Texture和其Normal Map绑定到Shader上。
  2. 将光源的位置、方向、颜色和强度传递给Shader
  3. 将环境光的颜色和强度传递给Shader
  4. Falloff向量传递给Shader
  5. 使用这个Shader来渲染Array中的所有"instances",它们都是使用我的ModelBatch绘制的。
我希望能够使用不同的纹理为不同的模型进行渲染。我有所有的ModelInstance都使用了带纹理的Material,并且我对于这些不同的纹理都有法线贴图。现在我想要根据ModelInstance的Material中的纹理,在Shader中绑定正确的纹理和法线贴图。
如果可能的话,更加“高级”的是在我的游戏中为不同的ModelTypes使用不同的Shader(例如Stonewall使用没有“Specularity”和“Reflection”的Shader,而Metalwalls则使用Specularity)。
在Libgdx中,最好的方法是什么?我应该如何绑定我拥有的不同变量?(例如,使用ShaderProgram时我可以使用setUniformi)
非常感谢。如果需要更多信息或者难以理解,请告诉我,我会尝试提出更好的问题。
编辑:我认为在我的情况下,最好的方法是创建一个实现Shader接口的新Shader类。但我想听听关于所有其他可能性的意见,包括设计、性能和可能的限制。
1个回答

16

可以在这里找到关于3D API渲染管线的总体解释。这个教程指导您从零开始创建一个新的Shader。而这个教程展示了如何使用自定义属性将数据传递给着色器。这个维基页面还解释了如何使用Material Attributes以及DefaultShader支持哪些Attributes

在LibGDX中,ShaderShaderProgram之间有所不同。 ShaderProgram只是GPU实现(包括顶点和片段着色器程序),基本上只是编译后的GLSL文件。例如,您可以在ShaderProgram上设置uniform并使用它来渲染网格。但是,ShaderProgram本身并不“知道”它应该如何渲染模型。

Shader接口旨在弥合ShaderProgramRenderable(后者是模型的最小可渲染部分)之间的差距。因此,Shader通常封装了一个ShaderProgram,并确保设置正确的uniform等(请注意,严格来说,Shader不必封装一个ShaderProgram。例如,在移除GLES1支持之前,还有一个GLES1着色器,它管理固定的渲染管道而不是封装一个ShaderProgram)。

BaseShader类是一个abstract帮助类,实现了Shader接口,封装了一个ShaderProgram并添加了一些帮助方法以轻松设置uniform。如果您扩展这个类,您可以很容易地registerset uniform,例如:

public class MyShader extends BaseShader {
    public final int u_falloff = register("u_falloff");
    ...
    @Override
    public void render (final Renderable renderable) {
        set(u_falloff, 15f);
        ...
        super.render(renderable);
    }
}

这将设置名为"u_falloff"的uniform变量为指定值15f(它将调用setUniformX)。如果ShaderProgram(glsl文件)没有实现称为"u_falloff"的uniform变量,则简单地忽略该调用。您也可以使用以下代码进行检查:if (has(u_falloff)) { /* 计算falloff并设置它 */ }BaseShader还为每个uniform添加了一个ValidatorSetter的可能性。

DefaultShader扩展BaseShader,并为大多数Material Attributes添加了默认实现。如果您希望查看每个uniform变量的命名,请查看源代码。在实践中,如果你使用与DefaultShader相同的uniform变量命名,则可以仅指定glsl文件,让DefaultShader来设置uniform变量。当然,您可以扩展DefaultShader以添加其他uniform变量。

当您调用ModelBatch#render(...)时,ModelBatch会向ShaderProvider查询要使用的Shader。这是因为每种可能的顶点属性和材质属性都可能需要不同的Shader。例如,如果您有两个ModelInstance(或更准确地说是两个Renderable),其中一个具有TextureAttribute.Diffuse,而另一个没有纹理但具有ColorAttribute.Diffuse。那么最常见的情况是,ShaderProvider需要创建两个不同的Shader。注意它们可以是相同的类(例如DefaultShader),但底层的GLSL文件可能是不同的。

DefaultShader使用预处理器宏(ubershader)来处理这个问题。根据顶点属性和材质属性,它将#define多个标志,导致glsl程序专门为该组合的顶点和材质属性编译。如果您想了解如何完成此操作,请查看glsl文件

因此,在实践中,您可能需要自己的ShaderProvider和自己的Shader(通过从头开始实现、扩展BaseShader或扩展DefaultShader)。您可以扩展DefaultShaderProvider并在需要时回退到DefaultShader,例如:

public class MyShaderProvider extends DefaultShaderProvider {
    ... // implement constructor
    @Override
    protected Shader createShader (final Renderable renderable) {
        if (renderable.material.has(MyCustomAttribute.Type))
            return new MyShader(renderable);
        else
            return super.createShader(renderable);
    }
}

简而言之,如果您想使用自己的或修改后的ubershader,并使用与默认着色器相同的uniforms,那么只需提供glsl文件即可。如果您想使用相同的uniforms但添加一个或两个额外的uniforms,那么扩展DefaultShader可能很容易。否则(或者如果您正在学习着色器),我建议按照此教程从头开始创建着色器。


BaseShader 会为您绑定网格,包括顶点属性(例如 a_tangent),只需在渲染方法的末尾调用 super.render(renderable); 即可。是的,请查看 BaseShaderProvider(https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g3d/utils/BaseShaderProvider.java),这是 DefaultShaderProvider 扩展的内容。只有当它没有可重用的 Shader 时,才会调用 createShadercompareTo 与此无关,仅用于对 Renderable 进行排序(您可以安全地忽略它并返回 0)。 - Xoppa
好的,所以我不需要自己处理这些属性。 最后一个问题:我有纹理和它们相关的法线贴图纹理。我需要将它们都绑定到着色器上,但是每个模型都可能不同。有没有一种方法可以将这两个纹理都传递给ModelInstance,然后再传递给着色器,或者我必须使用BaseShader的Setter方法来绑定它们? - Robert P
你真的读过 http://blog.xoppa.com/using-materials-with-libgdx/ 吗?只需使用 TextureAttribute.Normal。另请参阅 https://github.com/libgdx/libgdx/wiki/Material-and-environment - Xoppa
是的,我已经阅读了它们。好的,所以我使用 Textureatrribute.diffuse 来设置我的纹理,并使用 TextureAttribute.Normal 来设置其法线贴图。现在我感觉有点傻,因为找到所有这些东西有点困难。有太多的可能性了。再次感谢。+1并接受! - Robert P
通过使用我的CustomShaderProvider,我能够使用正确的Renderable初始化CustomShader。但由于某种原因,它只显示黑屏。ShaderProgram本身是有效的,因为我首先使用默认的Shader进行了测试。当我扩展BaseShader时,是否有任何需要注意的事项?我复制了您的Shader教程代码,并用BaseShaderSetter替换了program.setUniformX。此外,我总是调用super.xxx()并让BaseShader渲染物体,但是出现了问题... - Robert P
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接