ModelBatch(LibGDX)都做了什么

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的实现了。(睡觉去先……)

时间: 2024-10-30 07:56:51

ModelBatch(LibGDX)都做了什么的相关文章

scikit-learn:CountVectorizer提取tf都做了什么

http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html#sklearn.feature_extraction.text.CountVectorizer class sklearn.feature_extraction.text.CountVectorizer(input=u'content', encoding=u'utf-8', decode_er

对象初始化都做了什么

Java中对象的初始化都做了些什么,以Person p = new Person("张三",20)为例 一.Person p 1.首先会把编译后的Person.class文件加载内存中 2.在栈内存中为类Person的引用p开辟空间 3.如果Person中有静态的成员,则会先把静态的变量和方法加载到方法区中的静态方法区 二.new Person ("张三",20) 1.new关键字,在堆内存中为Person创建对象 2.把Person中的非静态成员加载到方法区,并为

一张图告诉你广电总局这些年都做了些什么~

广电总局,那些年的禁令!国家新闻出版广电总局是国务院直属机构,我们最熟悉的莫过于电影开场时必然会出现的金色龙形长城标志.那么,一张图,让你看清广电总局这些年做了什么事…… 一张图告诉你广电总局这些年都做了些什么-,布布扣,bubuko.com

你连自律都做不到,还奢谈什么自由?

本文由作者 菜刀少爷 授权罗辑思维发布,选自微信公众号“菜刀少爷”. 1 牛人都是狠角色 据说史蒂夫·乔布斯年轻时每天凌晨四点起床,九点前把一天工作做完.乔帮主说:自由从何而来?从自信来,而自信则是从自律来. 自律是对自我的控制,自信是对事情的控制.先学会克制自己,用严格的日程表控制生活,才能在这种自律中不断磨练出自信. 都是成年人,连最基本的行为控制都做不到,还谈什么自信?又奢谈什么自由? 前华人首富李嘉诚以勤奋自律著称.他的作息时间非常有名: 不论几点睡觉,在清晨5点59分闹铃响后起床:随后

js new都做了什么 prototype __proto__

现在对自己之前理解的东西,总结下,整理到这,看我那会画的一张图,我都不知道在说什么,那会的瞬间都疏通的感觉,现在基本都模糊了,我这脑子真是... 言归正传 开始: 一:思考new都做了什么,下面一个例子 function A (){ var a1=111; this.a2=222; function fa(){ console.log(a1); } fa(); } A.prototype.a3="333"; var b = new A(); new一个构造函数, 1:会先新建一个对象,

【dotnet跨平台】&amp;quot;dotnet restore&amp;quot;和&amp;quot;dotnet run&amp;quot;都做了些什么?

[dotnet跨平台]"dotnet restore"和"dotnet run"都做了些什么? 前言: 关于dotnet跨平台的相关内容.能够參考:跨平台.NET Core--微软开源方向 当中..net core基础库叫CoreFX:https://github.com/dotnet/corefx,.net core执行时叫CoreCLR:https://github.com/dotnet/coreCLR, asp.net core各组件库:https://git

如果在之前阶段都做的很好,是否到探索性测试阶段,就不会发现Bug了

首先提出一个问题: 如果在探索性测试阶段发现很多bug,是否是之前卡中AC写的不够详尽?或者是开卡时候QA.开发.BA等人一起讨论的不够深入? 好吧,我换一个问题,如果在之前阶段都做的很好,是否到探索性测试阶段,就不会发现Bug了? 对于这个问题,我的想法是: 在一个开发团队里有十多个人,大家都使用同样的研发流程,在每张卡中的AC也尽量写的详尽,但是你可以看到,大约10年开发经验的人和2,3年开发经验的人最后测出的Bug数差别很大.我的问题是,为什么同样的流程,类似的卡的AC描述详细程度,对于开

Java对象的创建 —— new之后JVM都做了什么?

Java对象创建过程 1. 类加载检查 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载.解析和初始化过.如果没有则进行相应的类加载过程.(我之后会写一篇关于类加载顺序和过程的博客,并在此补充连接地址) 2. 分配内存空间 类加载检查通过之后,JVM将为新生对象在堆中分配内存.对象所需内存的大小在类加载完成后已经完全确定了(关于怎样计算对象所需内存大小我稍后会写一篇博客并补充链接).为对象分配空间就相当于在J

电脑在开机的时候都做了些什么

首先嘛,开机对于大家来说也就是捅一下开机按钮,然后电脑开始嗡嗡叫,各种风扇转起来,显示器上显示了一个LOGO,最后就看到了Win7的启动动画.这个过程中,电脑详细都做了些什么呢?我在这里详细的说一说我的了解.(注意这只是我的了解,我还是很严谨的哦) 1.开机之后,主板的芯片组会给CPU发送一个RESET信号,CPU会挂起,并不马上执行指令(CS:EIP=0xFFFF:0x0000) 这个时候CPU完成初始化,内部恢复到初始状态. 等芯片组检测到电源已经稳定供电了,芯片组就撤去RESET信号,CP