问题
I have recently been learning an implementing my own shaders in libgdx. So far I did this with a custom shader provider, which chooses between a few shaders based on the userdata value of the object;
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());
}
}
This seemed to work.
I have an object with a noise shader applied, animated fine.
I have an object with a inverted textured shader, again looking fine.
I have a whole bunch of other objects being rendered with the normal default shader.
It seems the provider as I have set it up is correctly rendering different objects with different shaders based on userData.
However,I recently found a new object I created with a new shader type (ConceptBeamShader) is only being rendered with the Default shader.
The objects user data is set the same as the others;
newlazer.userData = MyShaderProvider.shadertypes.conceptbeam;
However, at no point does the conceptbeamshader get created or used.
In fact createShader() doesn't seem to run for it at all...implying that an existing shader in the shaders array is good enough.
Using the testListShader() function above I see "DefaultShader" is in the "shader" list, which canRender anything, and thus it never gets to creating that new shader I want that object to use :-/
I assume the other shaders only got picked before because those objects were created before DefaultShader got added to that internal shader list.
Surely as soon as a DefaultShader is used, it gets stored in that provider list and will "gobble up" any other shaders. The getShader function in the class MyShaderProvider extends is;
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;
}
As you can see the shaders are looped over and the first one which returns true for "canRender" is used.
So...umm...how exactly are you supposed to say "render this ModelInstance with this shader" ?
None of the tutorials I have read online seemed to cover this - in fact the one on the official site seems to recommend exactly what I am doing so theres clearly something I am missing.
Thanks,
edit The place it was instanced was asked for. Not sure how this helps but here;
public static MyShaderProvider myshaderprovider = new MyShaderProvider();
Its then assigned to the modelbatch at the games setup
modelBatch = new ModelBatch(myshaderprovider);
As mentioned, my other shaders are working and visible on the objects I assigned the matching userdata too, so I am 99.9% sure the provider is being called and is, at least in some cases, picking the right shader for the right object. My hunch where its going wrong is as soon as "DefaultShader" gets added to the internal shader list.
回答1:
There are several ways to specify the Shader to use for a ModelInstance. One of which is to specify the Shader when calling the render method on the ModelBatch:
modelBatch.render(modelInstance, shader);
This will hint the ModelBatch to use this shader, which it will almost always do, unless the specified Shader isn't suitable to render. Whether a Shader is suitable (and should be used) to render the ModelInstance is determined by the call to Shader#canRender(Renderable).
Note the difference between the Renderable and ModelInstance. This is because a single ModelInstance can consist of multiple parts (nodes), each which might need another Shader. For example when you have car model, then it might consist of the opaque chassis and transparent windows. This will require a different shader for the windows and the chassis.
Therefore specifying a Shader for an entire ModelInstance isn't always very useful. Instead you might need to have more control over which Shader is used for each specific part of the model (each render call). For this you can implement the ShaderProvider interface. Which allows you to use whichever Shader you like for each Renderable. Ofcourse you should make sure that the Shader#canRender(Renderable) method of the Shader you use returns true for the specified Renderable.
It can be useful to extend the DefaultShaderProvider so you can fall back on the DefaultShader when you don't need a custom shader. In that case you must make sure that there's an unambiguous and consistent distinction between when the default shader should be used and when a custom shader should be used. That is, the DefaultShader#canRender method should not return true when a custom shader should be used and your customshader#canRender method should not return true when the DefaultShader should be used. (on itself this isn't specific to custom or default shader, you always need to know which shader to use)
You are trying to use ModelInstance#userData to distinct between a custom and default shader. There are two issues with this:
- The userData is the same for every Renderable of the ModelInstance. So practically you over complicating your design at no gain. You might as well use
modelBatch.render(modelInstance, shader). - The DefaultShader is and can't be aware of any user specific data. It simply looks at the information it is aware of (the material, mesh, environment, etc.) and return
trueincanRenderif it should be used to render based on that info.
To solve the second point, the libGDX 3D API comes with attributes (used for both environment and material). By design these allow you to compare a Shader and Renderable with just two numbers, which are bitwise masks of the attributes. Therefore the preferred, easiest and fastest method is to use a custom attribute. This not only let's you unambiguously identify which shader to use, but also let you specify the required information to use the shader (there's a reason you want to use a different shader).
An example of how to do that can be found here and here.
来源:https://stackoverflow.com/questions/28590802/libgdx-assigning-a-specific-shader-to-a-modelinstance