LibGDX为ModelInstance分配特定的着色器

3

我最近在学习并实现自己的libgdx着色器技术。目前,我使用自定义的着色器提供者,根据对象的userdata值,在几个着色器之间进行选择。

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()似乎根本没有运行它...这意味着shaders数组中的现有着色器已经足够好了。
使用上面的testListShader()函数,我看到“DefaultShader”在“shader”列表中,可以渲染任何东西,因此它永远不会创建我想让该对象使用的新着色器 :-/
我认为其他着色器之前只是因为那些对象在DefaultShader添加到内部着色器列表之前就被创建了。
当然,一旦使用了DefaultShader,它就会存储在该提供程序列表中,并且将“吞噬”任何其他着色器。MyShaderProvider类中的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;
}

正如您所看到的,着色器会循环运行,并使用第一个返回“canRender”为true的着色器。那么...嗯...您应该如何说“使用此着色器渲染此ModelInstance”?我在网上阅读的所有教程似乎都没有涵盖这个问题 - 实际上,官方网站上的教程似乎建议我正在做的事情,因此显然有些东西我没弄清楚。谢谢。编辑被要求实例化的位置。不确定这有多大帮助,但是在此处;
public static MyShaderProvider myshaderprovider = new MyShaderProvider();

然后它会在游戏设置中被分配给模型批处理。

modelBatch = new ModelBatch(myshaderprovider);

如上所述,我的其他着色器可以工作并显示在我分配匹配的用户数据的对象上,因此我有99.9%的把握提供程序正在被调用,并且至少在某些情况下为正确的对象选择了正确的着色器。 我的猜测是,当“DefaultShader”添加到内部着色器列表时就会出现问题。


好的,你从未实例化你的提供者,请查看http://stackoverflow.com/help/how-to-ask。你可能还想考虑使用材料而不是userData,请参见:https://github.com/libgdx/libgdx/wiki/ModelBatch#shaderprovider - Xoppa
也许我应该提到,我已经阅读了许多教程,包括官方网站上的教程。然而我还没有找到任何一个涵盖这个的。很多教程根本不涉及ModelInstances,而是纯粹处理基于sprite的用途。如我所述,我的着色器系统在许多对象上都能正常工作。提供者是从主类中实例化并分配的。你给出的第二个链接明确说明了我已经在做的事情。我设置了一个自定义着色器提供程序,并使用userData来选择要使用的着色器。我不明白为什么使用不同的材料字段会改变结果。 - darkflame
说你已经读过教程并不能帮助澄清问题。正如所说,你从未实例化提供者。也许可以从一个工作示例(https://github.com/libgdx/libgdx/blob/master/tests/gdx-tests/src/com/badlogic/gdx/tests/g3d/ShaderTest.java)开始,并修改以适应你的需求。至少它将帮助你创建一个MCVE(http://stackoverflow.com/help/mcve),并包含在你的问题中。 - Xoppa
我有一个可行的例子。我提到的其他着色器也能工作。我们可能在这里误解了彼此 :-/ 我有1个提供者。它目前正在按照上述规定分配一组着色器。如果没有设置,那么它们中的任何一个都不会起作用,难道不是吗?我的动画噪声着色器?我的反转着色器?如果没有工作提供者,它们中的任何一个怎么能工作呢 :? 我确实感到困惑,但我的问题很简单; 如何将特定的着色器分配给特定的ModelInstance - darkflame
顺便说一下,那个工作示例只使用了一个BaseShaderProvider,甚至没有展示使用自定义的。仅展示一个默认提供器和一个着色器并不能回答某人有多个可用的着色器但某个特定的未被选择的问题。 - darkflame
抱歉,我误解了。BaseShaderProvider和DefaultShaderProvider依赖于Shader#canRender方法来决定使用哪个着色器进行(重新)渲染。DefaultShader不知道您的userData,但会检查材质(和其他内容)以决定是否可以渲染。首选(也是最简单的)方法是使用自定义材质属性。如果您坚持使用userData,则还必须覆盖getShader方法。 - Xoppa
1个回答

10

有多种方法可以指定Shader用于ModelInstance。其中之一是在调用ModelBatch的render方法时指定Shader:

modelBatch.render(modelInstance, shader);

这将提示ModelBatch使用此着色器,除非指定的着色器不适合渲染。是否适合(并且应该使用)用于渲染ModelInstance的着色器是通过调用Shader#canRender(Renderable)来确定的。
请注意Renderable和ModelInstance之间的区别。这是因为单个ModelInstance可以由多个部分(节点)组成,每个部分可能需要另一个着色器。例如,当您拥有汽车模型时,它可能由不透明的底盘和透明的窗户组成。这将需要不同的着色器来处理窗户和底盘。
因此,为整个模型实例指定一个着色器并不总是非常有用。相反,您可能需要更多地控制每个模型部分(每个render call)使用哪个着色器。为此,您可以实现ShaderProvider接口。这允许您为每个可渲染实体使用任何您喜欢的着色器。当然,您应该确保所使用的着色器的Shader#canRender(Renderable)方法对于指定的Renderable返回true

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

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

  1. 对于ModelInstance的每个Renderable来说,userData是相同的。因此,在没有任何收益的情况下,您会使设计变得过于复杂。您可以使用modelBatch.render(modelInstance, shader)
  2. DefaultShader不能意识到也不可能意识到任何用户特定的数据。它只查看它所知道的信息(例如材料、网格、环境等),并在canRender中基于这些信息返回true,如果应该使用它来渲染,则渲染。

要解决第二个问题,libGDX 3D API带有attributes(用于环境和材质)。按设计,这些属性允许您只使用两个数字(作为位掩码)比较着色器和Renderable。因此,首选、最简单和最快的方法是使用一个自定义属性。这不仅能让您明确地标识要使用的着色器,还能让您指定使用着色器所需的信息(您想要使用不同的着色器是有原因的)。

一个关于如何做到这一点的示例可以在这里这里找到。

很难在没有查看代码的情况下说什么。我稍微修改了测试以扩展DefaultShaderProvider等,也许这有所帮助:https://github.com/libgdx/libgdx/blob/master/tests/gdx-tests/src/com/badlogic/gdx/tests/g3d/ShaderTest.java - Xoppa
非常好的解释,虽然解决了很多问题,我确实会将我的着色器选择切换到自定义属性。希望我不会遇到snapfractualpop所遇到的相同问题,因为该链接中的解决方案可能会出现与userdata相同的问题 - 如果已经存在通过canuse条件的DefaultShader,则"createShader"甚至不会触发。DefaultShaderProvider在创建新着色器之前检查所有以前创建的着色器。 - darkflame
我不确定为什么你认为它可能会有问题,但我可以向你保证,当Renderable包含一个不打算渲染的材质属性时,默认着色器DefaultShadercanRender中不会返回true。参考链接:https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g3d/shaders/DefaultShader.java#L656 - Xoppa
这很令人放心。只是确认一下,“materialMask = renderable.material.getMask()”是检查请求的可渲染对象是否具有与首先传递给DefaultShader的可渲染对象相同的材质ID吗?我看到第一个“materialMask”被设置的唯一位置是在创建着色器时。https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g3d/shaders/DefaultShader.java#L501,因此如果首次传递给着色器的可渲染对象具有自定义属性,它也将接受稍后相同的属性。也许这就是@snapfractalpop遇到的问题。 - darkflame
太好了,对我也有效。干得好Xoppa! - darkflame
显示剩余2条评论

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