LibGDX 将特定着色器分配给模型实例

LibGDX assigning a specific shader to a ModelInstance

我最近一直在学习如何在 libgdx 中实现我自己的着色器。 到目前为止,我使用自定义着色器提供程序执行此操作,该提供程序根据对象的用户数据值在几个着色器之间进行选择;

public class MyShaderProvider extends DefaultShaderProvider {
    public final DefaultShader.Config config;
    final static String logstag = "ME.MyShaderProvider";
    //known shaders
    static public enum shadertypes {
        prettynoise,
        invert,
        standardlibgdx, 
        noise,
        distancefield,
        conceptbeam
    }

    public MyShaderProvider (final DefaultShader.Config config) {
        this.config = (config == null) ? new DefaultShader.Config() : config;
    }

    public MyShaderProvider (final String vertexShader, final String fragmentShader) {
        this(new DefaultShader.Config(vertexShader, fragmentShader));


    }

    public MyShaderProvider (final FileHandle vertexShader, final FileHandle fragmentShader) {
        this(vertexShader.readString(), fragmentShader.readString());
    }

    public MyShaderProvider () {
        this(null);
    }

    public void testListShader(Renderable instance){

        for (Shader shader : shaders) {

            Gdx.app.log(logstag, "shader="+shader.getClass().getName());

            Gdx.app.log(logstag, "can render="+shader.canRender(instance));

        }
    }

    @Override
    protected Shader createShader (final Renderable renderable) {

        //pick shader based on renderables userdata?
        shadertypes shaderenum = (shadertypes) renderable.userData;

        if (shaderenum==null){
                return super.createShader(renderable);
        }
        Gdx.app.log(logstag, "shaderenum="+shaderenum.toString());


        switch (shaderenum) {

        case prettynoise:
        {           
            return new PrettyNoiseShader();

        }
        case invert:
        {
              String vert = Gdx.files.internal("shaders/invert.vertex.glsl").readString();
              String frag = Gdx.files.internal("shaders/invert.fragment.glsl").readString();


            return new DefaultShader(renderable, new DefaultShader.Config(vert, frag)); 
        }
        case noise:
        {
            return new NoiseShader();
        }
        case conceptbeam:
        {
            Gdx.app.log(logstag, "creating concept gun beam ");
            return new ConceptBeamShader();
        }
        case distancefield:
        {
            return new DistanceFieldShader();
        }
        default:
            return super.createShader(renderable);

        }
        //return new DefaultShader(renderable, new DefaultShader.Config());

    }
}

这似乎有效。 我有一个应用了噪声着色器的对象,动画很好。
我有一个带有反转纹理着色器的对象,再次看起来很好。
我有一大堆其他对象正在使用普通默认着色器渲染。 看来我设置的提供程序可以根据 userData 使用不同的着色器正确渲染不同的对象。

但是,我最近发现我使用新着色器类型 (ConceptBeamShader) 创建的新对象仅使用默认着色器进行渲染。

对象用户数据设置与其他相同;

newlazer.userData = MyShaderProvider.shadertypes.conceptbeam;

但是,在任何时候都不会创建或使用 conceptbeamshader。

事实上,createShader() 似乎根本运行 没有...暗示着色器数组中的现有着色器就足够了。

使用上面的 testListShader() 函数我看到 "DefaultShader" 在 "shader" 列表中,它可以渲染任何东西,因此它永远不会创建我希望该对象使用的新着色器: -/

我假设之前只选择了其他着色器,因为这些对象是在 DefaultShader 添加到该内部着色器列表之前创建的。

当然,一旦使用 DefaultShader,它就会存储在该提供程序列表中,并将 "gobble up" 任何其他着色器。 class MyShaderProvider extends中的getShader函数是;

    public Shader getShader (Renderable renderable) {
    Shader suggestedShader = renderable.shader;
    if (suggestedShader != null && suggestedShader.canRender(renderable)) return suggestedShader;
    for (Shader shader : shaders) {
        if (shader.canRender(renderable)) return shader;
    }
    final Shader shader = createShader(renderable);
    shader.init();
    shaders.add(shader);
    return shader;
}

如您所见,着色器被循环使用,第一个 returns 对 "canRender" 为真。

所以...嗯...您应该怎么说 "render this ModelInstance with this shader"

None 我在网上阅读的教程似乎涵盖了这一点 - 事实上,官方网站上的教程似乎准确地推荐了我正在做的事情,所以很明显我遗漏了一些东西。

谢谢,


编辑 它被实例化的地方被要求。不知道这有什么帮助,但在这里;

public static MyShaderProvider myshaderprovider = new MyShaderProvider();

然后在游戏设置中分配给模型批次

modelBatch = new ModelBatch(myshaderprovider);

如前所述,我的其他着色器也在我分配匹配用户数据的对象上工作并可见,所以我 99.9% 确定正在调用提供程序,并且至少在某些情况下,为它选择了正确的着色器正确的对象。 我的直觉是一旦 "DefaultShader" 被添加到内部着色器列表中就会出错。

有几种方法可以指定 Shader to use for a ModelInstance。其中之一是在调用ModelBatch的render方法时指定Shader:

modelBatch.render(modelInstance, shader);

这将提示 ModelBatch 使用此着色器,它几乎总是这样做,除非指定的着色器不适合渲染。 Shader 是否适合(并且应该使用)渲染 ModelInstance 取决于对 Shader#canRender(Renderable).

的调用

请注意 Renderable 和 ModelInstance 之间的区别。这是因为单个 ModelInstance 可以由多个部分(节点)组成,每个部分可能需要另一个着色器。例如,当您有汽车模型时,它可能由不透明底盘和透明 windows 组成。 windows 和底盘需要不同的着色器。

因此,为整个 ModelInstance 指定一个着色器并不总是很有用。相反,您可能需要更多地控制模型的每个特定部分使用哪个着色器(每个 render call). For this you can implement the ShaderProvider 接口。这允许您为每个可渲染对象使用您喜欢的任何着色器。当然,您应该确保 Shader#canRender(Renderable) 你使用的Shader的方法 returns true 指定 Renderable.

扩展 DefaultShaderProvider 很有用,这样您就可以在不需要自定义着色器时回退到 DefaultShader。在这种情况下,您必须确保在何时应使用默认着色器和何时应使用自定义着色器之间存在明确且一致的区别。也就是说,当应该使用自定义着色器时,DefaultShader#canRender 方法不应 return true,而当应使用 DefaultShader 时,您的 customshader#canRender 方法不应 return true。 (这本身并不特定于自定义或默认着色器,您始终需要知道要使用哪个着色器)

您正在尝试使用 ModelInstance#userData 来区分自定义着色器和默认着色器。这有两个问题:

  1. 用户数据对于 ModelInstance 的每个 Renderable 都是相同的。所以实际上你过度复杂化你的设计是没有好处的。你不妨使用 modelBatch.render(modelInstance, shader).
  2. DefaultShader 知道也不能知道任何用户特定的数据。它只是查看它知道的信息(material、网格、环境等)和 canRender 中的 return true 是否应该用于渲染关于那个信息。

为了解决第二点,libGDX 3D API自带attributes(用于环境和material)。通过设计,这些允许您仅使用两个数字来比较着色器和可渲染对象,这两个数字是属性的按位掩码。因此,首选、最简单和最快的方法是使用自定义属性。这不仅可以让您明确地识别要使用的着色器,还可以让您指定使用着色器所需的信息(这是您想要使用不同着色器的原因)。

可以找到如何执行此操作的示例 here and here