zoukankan      html  css  js  c++  java
  • 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对象的集合。VertexAttribute用来表示顶点属性,Attribute用来表示材质属性,实际上对应的可能是GLSL shader中的一个uniform字段。

    Attribute(注意没有s)是个抽象类,它有一个静态方法register()用来注册一个字段名称,如果没有重复的话这个字段名被放入一个静态全局数组,并且返回一个与数组索引相应二进制位为1的bit field值。它的实现有ColorAttribute,TextureAttribute,IntAttribute等,每个类里都注册了一些字段,有的可能是重复的,那说明它们就是同一个字段,但是可能采用不同属性。

    就是说,Attribute类提供了维护这些字段定义的功能,为不同的字段分配一个bit field。它的实现类则保存具体的相关的属性,比如TextureAttribute保存了Texture对象和纹理的相关信息。

    Attributes用来承载多个Attribute,它维护一个Attribute数组(按照的bit field的位置放置),和相应字段的bit field合成的掩码。

    然后再看上面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的实现了。

    它有几个内部类,一个是Config,保存了一些初始化的参数。

    一个Inputs类,其中创建了许多BaseShader.Uniform对象。

    public static class Inputs {
        public final static Uniform projTrans = new Uniform("u_projTrans");
        ....  //省略
    }

    一个Setters类,其中创建了许多实现Setter接口的匿名类:

    public static class Setters {
        public final static Setter projTrans = new Setter() {
            ....
        }
        public final static Setter viewTrans = new Setter() {
            ....
        }
        ....
    }

    之前BaseShader里用到了Setter现在可以看看它们的用途了,选取一个有代表性的:

    public final static Setter diffuseTexture = new Setter() {
        @Override
        public boolean isGlobal (BaseShader shader, int inputID) {
            return false;
        }
    
        @Override
        public void set (BaseShader shader, int inputID, Renderable renderable, Attributes combinedAttributes) {
            final int unit = shader.context.textureBinder.bind(((TextureAttribute)(combinedAttributes
                .get(TextureAttribute.Diffuse))).textureDescription);
            shader.set(inputID, unit);
        }
    };

    其实后面会看到这个Setters类里的一个Setter对象是对应Inputs类里的一个Uniform的。

    isGlobal()用于说明这个uniform字段是全局不变的还是每次都变化的,前面在BaseShader里可以看到,这两种uniform的字段名被记录在globalUniforms和localUniforms两个数组里,begin()的时候用Setter设置了所有globalUniforms里的字段,render()的时候设置了localUniforms里的字段。一个Shader的调用过程begin()是只调用一次的,render()可能被调用多次,这就是这两种uniform的区别,一个作用于所有应用此shader的渲染对象(比如摄像机位置等),一个只作用于当前渲染对象(比如当前渲染对象的变换矩阵)。

    set()用来设置相应uniform字段的值,至于为什么要用这个Setter来设置,是因为set()的第三个参数。前面看到过Attributes和Attribute两个类,Attribute提供一个属性,这里可以说是一个材质属性,Attributes是多个属性的集合。这个diffusesTexture被用来设置贴图纹理,Texture的相关信息就保存在combinedAttributes中保存的其中一个TextureAttribute里,它是这样获取到的:

    (TextureAttribute)(combinedAttributes.get(TextureAttribute.Diffuse))
    TextureAttribute.Diffuse的定义:
    public final static String DiffuseAlias = "diffuseTexture";
    public final static long Diffuse = register(DiffuseAlias);

    根据前面对Attribute的分析,Diffuse是"diffuseTexture"这个名称所代表的属性的一个唯一位置(一个bit field),combinedAttributes.get()就是根据这个位置从它保存的Attribute数组中取到的对应的Attribute对象,同理,Attributes创建的时候也是根据这个位置把每个Attribute放到数组相应位置的。

    取到了Attribute,转换成相应的TexutreAttribute类型,然后就可以得到贴图属性的相关信息了,就是textureDescription字段,它具体的实现就不管了,反正Shader类把其中的贴图数据加载到显存,得到一个贴图单元的id,然后调用BaseShader.set()把inputID对应的shader变量的值设为这个贴图单元的id就完成了。

    最后详细总结一下的话,每个Setter对应一个Uniform,Uniform有一个字符串是uniform变量的名字,BaseShader.register()把这个uniform变量的名字和这个Setter和Uniform对应的放进三个数组,init()再用这些uniform变量名获取到GLSL shader中每个uniform变量的位置放进location数组,并且把这些变量分为globalUniforms和localUniforms两类,然后在渲染过程中用相应的Setter和renderable对象的材质属性去设置相应的uniform变量的值,同时Setter也会完成一些其他操作,比如这里的上传和绑定贴图单元。

    其实到这里就结束了,剩下的都是些零零碎碎不影响流程的东西了。

  • 相关阅读:
    LeetCode "Jump Game"
    LeetCode "Pow(x,n)"
    LeetCode "Reverse Linked List II"
    LeetCode "Unique Binary Search Trees II"
    LeetCode "Combination Sum II"
    LeetCode "Divide Two Integers"
    LeetCode "First Missing Positive"
    LeetCode "Clone Graph"
    LeetCode "Decode Ways"
    LeetCode "Combinations"
  • 原文地址:https://www.cnblogs.com/fightingCat/p/3938551.html
Copyright © 2011-2022 走看看