ModelBatch的通常用法:
Batch batch = new ModelBatch(); batch.begin(camera); batch.render(renderable); batch.end();
先来看ModelBatch.begin()函数:
public void begin (final Camera cam) { if (camera != null) throw new GdxRuntimeException("Call end() first."); camera = cam; if (ownContext) context.begin(); }
只是更新一下camera的引用,还有一个RenderContext目前无需关心。
再看ModelBatch.render()函数,有好几个版本,只看最基本的这个:
public void render (final Renderable renderable) { renderable.shader = shaderProvider.getShader(renderable); renderable.mesh.setAutoBind(false); renderables.add(renderable); }
无参数构造ModelBatch的时候shaderProvider使用的是DefaultShaderProvider,它继承自BaseShaderProvider类,这里的getShader是父类的方法:
public Shader getShader (Renderable renderable) { //如果renderable设置shader并且可用的话,就直接返回这个shader Shader suggestedShader = renderable.shader; if (suggestedShader != null && suggestedShader.canRender(renderable)) return suggestedShader; //从已创建的shader中找一个可以渲染当前renderable对象的 for (Shader shader : shaders) { if (shader.canRender(renderable)) return shader; } //否则,创建一个 final Shader shader = createShader(renderable); shader.init(); shaders.add(shader); return shader; }
这里调用的createShader()应该是DefaultShaderProvider类的方法:
protected Shader createShader (final Renderable renderable) { return new DefaultShader(renderable, config); }
看来ModelBatch用来渲染模型的shader就在DefaultShader里了,不过先不管它,先看看ModelBatch.end():
public void end () { flush(); if (ownContext) context.end(); camera = null; }
非常简单,主要是调用了flush():
public void flush () { //先用一个Sorter接口类对渲染物体排序,怎么实作的就不管了。 sorter.sort(camera, renderables); Shader currentShader = null; //遍历先前添加的要渲染的对象 for (int i = 0; i < renderables.size; i++) { final Renderable renderable = renderables.get(i); //是否需要切换shader if (currentShader != renderable.shader) { if (currentShader != null) currentShader.end(); currentShader = renderable.shader; currentShader.begin(camera, context); } currentShader.render(renderable); } if (currentShader != null) currentShader.end(); renderablesPool.flush(); renderables.clear(); }
这里调用到了Shader的init(), begin(), end(), render()。前面说了DefaultShaderProvider创建了一个DefaultShader对象,它继承自一个抽象类BaseShader:
public abstract class BaseShader implements Shader { ... } public class DefaultShader extends BaseShader { ... }
先来仔细看看BaseShader类的实现,它有几个内部定义:
Setter接口:
public interface Setter { /** @return True if the uniform only has to be set once per render call, false if the uniform must be set for each renderable. */ boolean isGlobal (final BaseShader shader, final int inputID); void set (final BaseShader shader, final int inputID, final Renderable renderable, final Attributes combinedAttributes); }
Validator接口:
public interface Validator { /** @return True if the input is valid for the renderable, false otherwise. */ boolean validate (final BaseShader shader, final int inputID, final Renderable renderable); }
我觉得这个接口不是很有必要,目前只有内部类Uniform实作了它。
Uniform类:
public static class Uniform implements Validator { public final String alias; public final long materialMask; public final long environmentMask; public final long overallMask; .... }
Uniform类是对GLSL Shader中uniform变量的封装,它保存了相应uniform变量的字段名,还有三个掩码,它们的作用稍后看。
BaseShader提供了几个版本的Register()函数:
public int register (final String alias, final Validator validator, final Setter setter) { if (locations != null) throw new GdxRuntimeException("Cannot register an uniform after initialization"); final int existing = getUniformID(alias); if (existing >= 0) { validators.set(existing, validator); setters.set(existing, setter); return existing; } uniforms.add(alias); validators.add(validator); setters.add(setter); return uniforms.size - 1; }
uniforms, validators, setters 是对应三个参数的类型的数组,register()的主要作用就是把参数的对象加入到这三个数组中。alias是uniform字段的名称,getUniformID()返回的是alias在uniforms数组中的索引。它对应的还有一个Validator(目前就只有一个Uniform实现)和一个Setter,它们的作用要到后面才能看到。
在ModelBatch.flush()函数中,调用了Shader的init(), begin(), end(), render(),依次看看它们的实现:
BaseShader.init():
/** Initialize this shader, causing all registered uniforms/attributes to be fetched. */ public void init (final ShaderProgram program, final Renderable renderable) { if (locations != null) throw new GdxRuntimeException("Already initialized"); if (!program.isCompiled()) throw new GdxRuntimeException(program.getLog()); this.program = program; final int n = uniforms.size; locations = new int[n]; for (int i = 0; i < n; i++) { final String input = uniforms.get(i); final Validator validator = validators.get(i); final Setter setter = setters.get(i); if (validator != null && !validator.validate(this, i, renderable)) locations[i] = -1; else { locations[i] = program.fetchUniformLocation(input, false); if (locations[i] >= 0 && setter != null) { if (setter.isGlobal(this, i)) globalUniforms.add(i); else localUniforms.add(i); } } if (locations[i] < 0) { validators.set(i, null); setters.set(i, null); } } if (renderable != null) { final VertexAttributes attrs = renderable.mesh.getVertexAttributes(); final int c = attrs.size(); for (int i = 0; i < c; i++) { final VertexAttribute attr = attrs.get(i); final int location = program.getAttributeLocation(attr.alias); if (location >= 0) attributes.put(attr.getKey(), location); } } }
参数ShaderProgram是对真正的GLSL Shader程序的封装,它提供了与GLSL Shader交互的功能。init()首先遍历之前通过register()添加的uniform字段名和相应的Validator和Setter,通过ShaderProgram取得GLSL Shader程序中uniform变量的地址保存在locations数组中,Validator.validate()决定这个uniform字段可否被用于当前渲染对象,setter.isGlobal()决定这个uniform字段是全局不变的还是每次render都变化的。然后就是关于顶点属性,渲染对象的网格保存了顶点格式的信息,也就是VertexAttributes。
VertexAttributes实现了Iterable<VertexAttribute>接口,是一个VertexAttribute的集合,VertexAttribute和Uniform类似,它也保存一个attribute变量的字段名,另外它还有几个int字段表示用途、变量位置、大小、类型等。VertexAttributes中的静态类Usage定义了几个预定义的顶点属性的类型,ShaderProgram中定义了预定义的顶点属性的字段名。
再来看上面init()函数中,最后把获取到每个顶点属性的地址放进attributes这个Map里。
然后再看BaseShader.begin()函数:
public void begin (Camera camera, RenderContext context) { this.camera = camera; this.context = context; program.begin(); currentMesh = null; for (int u, i = 0; i < globalUniforms.size; ++i) if (setters.get(u = globalUniforms.get(i)) != null) setters.get(u).set(this, u, null, null); }
这里又用到了之前register()添加的数据,目前还不知道作用,不过可以肯定它们会设置uniform字段的值,而这里是设置全局不变的uniform字段的值。
BaseShader.render()函数,仍然有多个重载:
public void render (Renderable renderable) { if (renderable.worldTransform.det3x3() == 0) return; combinedAttributes.clear(); if (renderable.environment != null) combinedAttributes.set(renderable.environment); if (renderable.material != null) combinedAttributes.set(renderable.material); render(renderable, combinedAttributes); }
combinedAttributes是个用来反复使用的暂存对象:
private Attributes combinedAttributes = new Attributes();
类似VertexAttributes和VertexAttribute的关系,Attributes是Attribute对象的集合。
Attribute(注意没有s)是个抽象类,它有一个静态方法register()用来注册一个字段名称,如果没有重复的话这个字段名被放入一个静态全局数组,并且返回一个与数组索引相应二进制位为1的bit field值。它的实现有ColorAttribute,TextureAttribute,IntAttribute等,每个类里都注册了一些字段,有的可能是重复的,那说明它们就是同一个字段。所以,每个字段都有一个不同的位置,相应的也有一个不重复的bit field标志位。除了维护这些字段(实际上保存的是它们注册后的bit field构成的mask),Attribute类还用来保存一些相关的属性,比如TextureAttribute还保存了Texture对象和纹理的相关信息。
Attributes用来承载多个Attribute,它维护一个Attribute数组,和一个最终合成的掩码。
(这段为什么这么用还不是很明白,先这么记录下)
然后再看上面render()函数中,combinedAttributes被设置为渲染对象的材质属性,然后调用了另一个版本render():
public void render (Renderable renderable, final Attributes combinedAttributes) { for (int u, i = 0; i < localUniforms.size; ++i) if (setters.get(u = localUniforms.get(i)) != null) setters.get(u).set(this, u, renderable, combinedAttributes); if (currentMesh != renderable.mesh) { if (currentMesh != null) currentMesh.unbind(program, tempArray.items); currentMesh = renderable.mesh; currentMesh.bind(program, getAttributeLocations(renderable.mesh.getVertexAttributes())); } renderable.mesh.render(program, renderable.primitiveType, renderable.meshPartOffset, renderable.meshPartSize, false); }
先设置局部的变化的uniform变量,然后Mesh类的unbind(), bind()会切换顶点数据,这个过程又涉及到OpenGL。最后调用Mesh.render()绘制顶点,全是OpenGL的事了。不过还有点东西不是很清楚,接下来就看DefaultShader的实现了。(睡觉去先……)