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也会完成一些其他操作,比如这里的上传和绑定贴图单元。

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

  • 相关阅读:
    IntelliJ IDEA 安装和破解教程
    IntelliJ IDEA 快捷键
    分布式事务框架-seata初识
    Spring超详细总结
    spring注解之@Import注解的三种使用方式
    双亲委托模型
    日志管理-log4j与slf4j的使用
    最详细的自定义Spring Boot Starter开发教程
    深入JVM(二)JVM概述
    一篇长文说 git 基础
  • 原文地址:https://www.cnblogs.com/fightingCat/p/3938551.html
Copyright © 2011-2022 走看看