OpenGL & GLSL 教程(三)

這個系列教程 是https://github.com/mattdesl/lwjgl-basics/wiki的系列翻譯。
精靈批處理(Sprite Batching)
若是咱們用上一節講到的debugTexture去渲染全部的圖形,那麼咱們立刻就會遇到性能問題。這是由於咱們每次只把一個精靈發送到GPU,因此咱們要在一次draw call中把精靈批量發送到GPU。這樣的話咱們就要用到SpriteBatchgit

介紹
咱們上一節講到,一個精靈只是肯定一個矩形的一些系列頂點。這裏的每一個定點有包括若干屬性:github

Position(x, y) - 在屏幕上的位置
TexCoord(s, t) - 紋理的取樣座標
Color(r, g, b, a) - 定點的顏色和透明度

大多數的SpriteBatch的代碼都是很易用的。可能在你遊戲的代碼中是這樣的:segmentfault

//called on game creation
public void create() {
    //create a single batcher we will use throughout our application
    spriteBatch = new SpriteBatch();
}

//called on frame render
public void render() {
    //prepare the batch for rendering
    spriteBatch.begin(); 

    //draw all of our sprites
    spriteBatch.draw(mySprite1, x, y);
    spriteBatch.draw(mySprite2, x, y);
    ...

    //end the batch, flushing the data to GPU
    spriteBatch.end();
}

//called when the display is resized
public void resize(int width, int height) {
    //notify the sprite batcher whenever the screen changes 
    spriteBatch.resize(width, height);
}

首先咱們調用begin(),而後調用 spriteBatch.draw(...)把精靈的頂點信息(座標,紋理座標,顏色)放入一個很是大的棧裏。
頂點在下列狀況發生前是不會被傳入GPU的:app

  • batch被強制調用end(),或者別的函數刷新batch 好比flush()
  • 當用戶繪製一個Sprite他的Texture和上一個Texture不是同一個的時候batch是被刷新的。
  • 存頂點的棧已經沒有空間,須要刷新從新開始。

這些是寫一個SpriteBatch以前必需要知道的。如上邊所說,用多個Texture會致使多個draw call。這就是爲何咱們老是推薦利用Texture atlas(紋理集),它可讓咱們渲染多個sprite 只用用一個draw call。ide

TextureRegion
像咱們上面討論的。在用Texture atlas的時候咱們能夠得到更好的性能。那麼當咱們用要畫這個紋理的一部分的時候咱們要用到TextureRegion這個工具類。它容許咱們在像素級別去指定圖片中咱們要渲染區域的位置,長和寬。讓咱們來舉個例子。下圖高亮區域就是咱們要渲染的sprite.
請輸入圖片描述
咱們能夠用下面的代碼去獲得一個TextureRegion:函數

//specify x, y, width, height of tile
region = new TextureRegion(64, 64, 64, 64);

如咱們看到的TextureRegion使咱們能夠得到一個子圖片而沒必要關心他的紋理座標。咱們用下面的代碼去渲染一個TextureRegion工具

... inside SpriteBatch begin / end ...
spriteBatch.draw(region, x, y);

頂點顏色(Vertex Color)
咱們經過調整batch的顏色去改變sprites的顏色(頂點顏色)。
咱們用RGBA賦值的方式來描述顏色,那麼(1, 1, 1, 1)表明白色,(1, 0, 0, 1)表明紅色,A值用來調整不透明度。性能

spriteBatch.begin();

//draw calls will now use 50% opacity
spriteBatch.setColor(1f, 1f, 1f, 0.5f);
spriteBatch.draw(...);
spriteBatch.draw(...);

//draw calls will now use 100% opacity (default)
spriteBatch.setColor(1f, 1f, 1f, 1f);
spriteBatch.draw(...);

spriteBatch.end();

是三角型不是四邊形(Triangles, not Quads!)
在以前的學習中咱們可能認爲紋理是四邊形的,其實在真實的環境當中大部分sprite batchers是用兩個三角形去表明一個四邊形。頂點的存儲順序是依賴具體引擎的,可是具體概念基本是以下圖的:
請輸入圖片描述
一個單獨的sprite包含兩個三角形或者說包含6個頂點。每一個頂點又包含8個屬性(X, Y, S, T, R, G, B, A),它們分別表明座標,紋理座標和顏色。這表示一個sprite就要把48個浮點數放入棧中。一些優化的sprite batching 可能會把48個值合併到一個浮點數當中,或者把表明顏色的四個浮點數合併到一個浮點數中。
Code學習

下面是一個SpriteBatch的實際代碼,其中有一些shader的代碼咱們之後會講到:優化

public class SpriteBatch {
        public static final String U_TEXTURE = "u_texture";
        public static final String U_PROJ_VIEW = "u_projView";

        public static final String ATTR_COLOR = "Color";
        public static final String ATTR_POSITION = "Position";
        public static final String ATTR_TEXCOORD = "TexCoord";

        public static final String DEFAULT_VERT_SHADER =
                "uniform mat4 "+U_PROJ_VIEW+";\n" +
                "attribute vec4 "+ATTR_COLOR+";\n" +
                "attribute vec2 "+ATTR_TEXCOORD+";\n" +
                "attribute vec2 "+ATTR_POSITION+";\n" +
                "varying vec4 vColor;\n" +
                "varying vec2 vTexCoord; \n" +
                "void main() {\n" +
                "   vColor = "+ATTR_COLOR+";\n" +
                "   vTexCoord = "+ATTR_TEXCOORD+";\n" +
                "   gl_Position = "+U_PROJ_VIEW+" * vec4("+ATTR_POSITION+".xy, 0, 1);\n" +
                "}";

        public static final String DEFAULT_FRAG_SHADER =
                "uniform sampler2D "+U_TEXTURE+";\n" +
                "varying vec4 vColor;\n" +
                "varying vec2 vTexCoord;\n" +
                "void main(void) {\n" +
                "   vec4 texColor = texture2D("+U_TEXTURE+", vTexCoord);\n" +
                "   gl_FragColor = vColor * texColor;\n" +
                "}";

        public static final List<VertexAttrib> ATTRIBUTES = Arrays.asList(
                new VertexAttrib(0, ATTR_POSITION, 2),
                new VertexAttrib(1, ATTR_COLOR, 4),
                new VertexAttrib(2, ATTR_TEXCOORD, 2));

        static ShaderProgram defaultShader;
        public static int renderCalls = 0;

        protected FloatBuffer buf16;
        protected Matrix4f projMatrix;
        protected Matrix4f viewMatrix;
        protected Matrix4f projViewMatrix;
        protected Matrix4f transpositionPool;

        protected Texture texture;
        protected ShaderProgram program;

        protected VertexData data;

        private int idx;
        private int maxIndex;

        private float r=1f, g=1f, b=1f, a=1f;
        private boolean drawing = false;

        static ShaderProgram getDefaultShader() {
            return defaultShader==null
                    ? new ShaderProgram(DEFAULT_VERT_SHADER, DEFAULT_FRAG_SHADER, ATTRIBUTES)
                    : defaultShader;
        }

        public SpriteBatch(ShaderProgram program, int size) {
            this.program = program;

            //later we can do some abstraction to replace this with VBOs...
            this.data = new VertexArray(size * 6, ATTRIBUTES);

            //max indices before we need to flush the renderer
            maxIndex = size * 6;

            updateMatrices();
        }

        public SpriteBatch(int size) {
            this(getDefaultShader(), size);
        }

        public SpriteBatch() {
            this(1000);
        }

        public Matrix4f getViewMatrix() {
            return viewMatrix;
        }

        public void setColor(float r, float g, float b, float a) {
            this.r = r;
            this.g = g;
            this.b = b;
            this.a = a;
        }

        /**
         * Call to multiply the the projection with the view matrix and save
         * the result in the uniform mat4 {@value #U_PROJ_VIEW}.
         */
        public void updateMatrices() {
            // Create projection matrix:
            projMatrix = MathUtil.toOrtho2D(projMatrix, 0, 0, Display.getWidth(), Display.getHeight());
            // Create view Matrix, if not present:
            if (viewMatrix == null) {
                viewMatrix = new Matrix4f();
            }
            // Multiply the transposed projection matrix with the view matrix:
            projViewMatrix = Matrix4f.mul(
                    Matrix4f.transpose(projMatrix, transpositionPool),
                    viewMatrix,
                    projViewMatrix);

            program.use();

            // Store the the multiplied matrix in the "projViewMatrix"-uniform:
            program.storeUniformMat4(U_PROJ_VIEW, projViewMatrix, false);

            //upload texcoord 0
            int tex0 = program.getUniformLocation(U_TEXTURE);
            if (tex0!=-1)
                glUniform1i(tex0, 0);
        }

        public void begin() {
            if (drawing) throw new IllegalStateException("must not be drawing before calling begin()");
            drawing = true;
            program.use();
            idx = 0;
            renderCalls = 0;
            texture = null;
        }

        public void end() {
            if (!drawing) throw new IllegalStateException("must be drawing before calling end()");
            drawing = false;
            flush();
        }

        public void flush() {
            if (idx>0) {
                data.flip();
                render();
                idx = 0;
                data.clear();
            }
        }

        public void drawRegion(Texture tex, float srcX, float srcY, float srcWidth,
                float srcHeight, float dstX, float dstY) {
            drawRegion(tex, srcX, srcY, srcWidth, srcHeight, dstX, dstY, srcWidth, srcHeight);
        }

        public void drawRegion(Texture tex, float srcX, float srcY, float srcWidth,
                float srcHeight, float dstX, float dstY, float dstWidth,
                float dstHeight) {
            float u = srcX / tex.width;
            float v = srcY / tex.height;
            float u2 = (srcX+srcWidth) / tex.width;
            float v2 = (srcY+srcHeight) / tex.height;
            draw(tex, dstX, dstY, dstWidth, dstHeight, u, v, u2, v2);
        }

        public void draw(Texture tex, float x, float y) {
            draw(tex, x, y, tex.width, tex.height);
        }

        public void draw(Texture tex, float x, float y, float width, float height) {
            draw(tex, x, y, width, height, 0, 0, 1, 1);
        }

        public void draw(Texture tex, float x, float y, float width, float height,
                float u, float v, float u2, float v2) {
            checkFlush(tex);

            //top left, top right, bottom left
            vertex(x, y, r, g, b, a, u, v);
            vertex(x+width, y, r, g, b, a, u2, v);
            vertex(x, y+height, r, g, b, a, u, v2);

            //top right, bottom right, bottom left
            vertex(x+width, y, r, g, b, a, u2, v);
            vertex(x+width, y+height, r, g, b, a, u2, v2);
            vertex(x, y+height, r, g, b, a, u, v2);
        }


        /**
         * Renders a texture using custom vertex attributes; e.g. for different vertex colours.
         * This will ignore the current batch color.
         *
         * @param tex the texture to use
         * @param vertices an array of 6 vertices, each holding 8 attributes (total = 48 elements)
         * @param offset the offset from the vertices array to start from
         */
        public void draw(Texture tex, float[] vertices, int offset) {
            checkFlush(tex);
            data.put(vertices, offset, data.getTotalNumComponents() * 6);
            idx += 6;
        }

        VertexData vertex(float x, float y, float r, float g, float b, float a,
                float u, float v) {
            data.put(x).put(y).put(r).put(g).put(b).put(a).put(u).put(v);
            idx++;
            return data;
        }

        protected void checkFlush(Texture texture) {
            if (texture==null)
                throw new NullPointerException("null texture");

            //we need to bind a different texture/type. this is
            //for convenience; ideally the user should order
            //their rendering wisely to minimize texture binds
            if (texture!=this.texture || idx >= maxIndex) {
                //apply the last texture
                flush();
                this.texture = texture;
            }
        }

        private void render() {
            if (texture!=null)
                texture.bind();
            data.bind();
            data.draw(GL_TRIANGLES, 0, idx);
            data.unbind();
            renderCalls++;
        }


    }
相關文章
相關標籤/搜索