问题描述
我最近一直在学习如何在 libgdx 中实现我自己的着色器.到目前为止,我使用自定义着色器提供程序完成了这项工作,它根据对象的 userdata 值在几个着色器之间进行选择;
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());
}
}
这似乎奏效了.我有一个应用了噪声着色器的对象,动画效果很好.
我有一个带有倒置纹理着色器的对象,看起来又很好.
我有一大堆其他对象正在使用正常的默认着色器进行渲染.似乎我设置的提供程序正在根据 userData 使用不同的着色器正确渲染不同的对象.
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.
但是,我最近发现我使用新着色器类型 (ConceptBeamShader) 创建的新对象仅使用默认着色器进行渲染.
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;
但是,conceptbeamshader 绝不会被创建或使用.
However, at no point does the conceptbeamshader get created or used.
事实上 createShader() 似乎根本没有运行它...暗示着色器数组中的现有着色器已经足够好了.
In fact createShader() doesn't seem to run for it at all...implying that an existing shader in the shaders array is good enough.
使用上面的 testListShader() 函数,我看到DefaultShader"在着色器"列表中,它可以渲染任何东西,因此它永远不会创建我希望该对象使用的新着色器:-/
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 :-/
我假设其他着色器只是在之前被选中,因为这些对象是在将 DefaultShader 添加到该内部着色器列表之前创建的.
I assume the other shaders only got picked before because those objects were created before DefaultShader got added to that internal shader list.
当然,一旦使用 DefaultShader,它就会存储在该提供程序列表中,并将吞噬"任何其他着色器.MyShaderProvider 扩展类中的 getShader 函数是;
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;
}
正如您所见,着色器被循环播放,并且使用了第一个为canRender"返回 true 的着色器.
As you can see the shaders are looped over and the first one which returns true for "canRender" is used.
那么...嗯...您应该如何准确地说使用此着色器渲染此 ModelInstance"?
我在网上阅读的教程似乎都没有涵盖这一点 - 事实上,官方网站上的教程似乎准确地推荐了我正在做的事情,所以很明显我缺少一些东西.
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.
谢谢,
编辑它被实例化的地方被要求.不知道这有什么帮助,但在这里;
editThe 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);
如前所述,我的其他着色器正在工作并且在我分配了匹配用户数据的对象上也是可见的,所以我 99.9% 确定正在调用提供程序,并且至少在某些情况下,为正确选择正确的着色器目的.我的直觉是,一旦DefaultShader"被添加到内部着色器列表中,它就会出错.
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.
推荐答案
有几种方法可以指定 Shader 用于 ModelInstance.其中之一是在ModelBatch上调用render方法时指定Shader:
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);
这将提示 ModelBatch 使用此着色器,它几乎总是这样做,除非指定的着色器不适合渲染.着色器是否适合(并且应该使用)渲染 ModelInstance 取决于对 Shader#canRender(Renderable)
.
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)
.
注意 Renderable 之间的区别 和模型实例.这是因为单个 ModelInstance 可以包含多个部分(节点),每个部分可能需要另一个 Shader.例如,当您有汽车模型时,它可能由不透明的底盘和透明的窗户组成.这将需要为窗户和底盘使用不同的着色器.
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.
因此,为整个 ModelInstance 指定着色器并不总是很有用.相反,您可能需要更好地控制模型的每个特定部分使用哪个着色器(每个 渲染调用).为此,您可以实现 ShaderProvider 接口.这允许您为每个 Renderable 使用您喜欢的任何着色器.当然,您应该确保您使用的 Shader 的 Shader#canRender(Renderable)
方法为指定的 Renderable
返回 true
.
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
.
扩展 DefaultShaderProvider
会很有用,因此当您不需要自定义着色器时,您可以使用 DefaultShader
.在这种情况下,您必须确保在何时应使用默认着色器和何时应使用自定义着色器之间存在明确且一致的区别.也就是说,当应使用自定义着色器时,DefaultShader#canRender 方法不应返回 true,而当应使用 DefaultShader 时,您的 customshader#canRender 方法不应返回 true.(这本身并不特定于自定义或默认着色器,您始终需要知道要使用哪个着色器)
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)
您正在尝试使用 ModelInstance#userData
区分自定义着色器和默认着色器.这有两个问题:
You are trying to use ModelInstance#userData
to distinct between a custom and default shader. There are two issues with this:
- ModelInstance 的每个 Renderable 的 userData 都是相同的.因此,实际上,您使设计过于复杂是徒劳的.您不妨使用
modelBatch.render(modelInstance, shader)
. - DefaultShader 知道并且不能知道任何用户特定的数据.它只是查看它知道的信息(材质、网格、环境等)并在
canRender
中返回true
如果它应该用于基于该信息进行渲染信息.
- 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
true
incanRender
if it should be used to render based on that info.
为了解决第二点,libGDX 3D API 自带了属性(用于环境和材料).通过设计,这些允许您仅将 Shader 和 Renderable 与两个数字进行比较,这两个数字是属性的按位掩码.因此,首选、最简单和最快的方法是使用自定义属性.这不仅可以让您明确地确定要使用哪个着色器,还可以让您指定使用着色器所需的信息(您想使用不同的着色器是有原因的).
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).
这里和这里.
这篇关于LibGDX 将特定着色器分配给 ModelInstance的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!