Follow by Email

Friday, March 27, 2015

Using shaders in AndEngine


Introduction

    I'd spent quite a lot time trying to  figure out how to use custom shaders in AndEngine. There are a couple of tutorials on the Internet but all of them are incomplete or have some strange solutions like engine onDraw overloading or something like this, so i decided to write this tutorial to collect useful info in one place.

First steps

Luckily  AndEngine has logical and straight architecture and we don't need to walk through the forest of code. It already has ShaderProgram class that can be used as a basis for our shader. In this example i will show how to write simple blink shader using which you can mark sprites in your game for example. So let me show you the whole BlinkShader class and then explain how it works:
package com.syouth.heli.main.shaders;

import android.opengl.GLES20;

import org.andengine.opengl.shader.ShaderProgram;
import org.andengine.opengl.shader.constants.ShaderProgramConstants;
import org.andengine.opengl.shader.exception.ShaderProgramException;
import org.andengine.opengl.shader.exception.ShaderProgramLinkException;
import org.andengine.opengl.util.GLState;
import org.andengine.opengl.vbo.attribute.VertexBufferObjectAttributes;

/**
 * Created by syouth on 25.03.15.
 */
public class BlinkShader extends ShaderProgram {
    public static final String UNIFORM_INTENCITY = "intencity";
    public static final double START_ANGLE = 60.0;

    private static BlinkShader INSTANCE = null;
    private static final float BLINKING_SPEED = 0.09f;
    private static final float MAX_ANGLE = 90;
    private static final float MIN_ANGLE = 0;
    private static final float MIN_INTENCITY = 0.5f;
    private static final float MAX_INTENCITY = 1.5f;
    private static final float mDiv = (float) ((MAX_INTENCITY - MIN_INTENCITY) /
            (Math.cos(Math.toRadians(MIN_ANGLE)) - Math.cos(Math.toRadians(MAX_ANGLE))));
    private static int mModelViewProjectionMatrixLocation = ShaderProgramConstants.LOCATION_INVALID;
    private static int mTexture0Location = ShaderProgramConstants.LOCATION_INVALID;
    private static int mIntencityLocation = ShaderProgramConstants.LOCATION_INVALID;

    private static boolean mBlink = false;
    private static double mCurAngle = 0.0f;
    private static long mPrevTime = 0;
    private static boolean mGrow = true;

    private static final String VERTEXT_SHADER =
            "uniform mat4 " + ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX + ";\n" +
                    "attribute vec4 " + ShaderProgramConstants.ATTRIBUTE_POSITION + ";\n" +
                    "attribute vec2 " + ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES + ";\n" +
                    "attribute vec4 " + ShaderProgramConstants.ATTRIBUTE_COLOR + ";\n" +
                    "varying vec2 " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ";\n" +
                    "varying vec4 " + ShaderProgramConstants.VARYING_COLOR + ";\n" +
                    "void main() {\n" +
                    "   " + ShaderProgramConstants.VARYING_COLOR +
                    " = " + ShaderProgramConstants.ATTRIBUTE_COLOR + ";\n" +
                    "   " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + " = " +
                    ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES + ";\n" +
                    "   gl_Position = " + ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX +
                    "*" + ShaderProgramConstants.ATTRIBUTE_POSITION + ";\n" +
                    "}";

    private static final String FRAGMENT_SHADER =
            "precision lowp float;\n" +
                    "uniform float " + UNIFORM_INTENCITY + ";\n" +
                    "uniform sampler2D " + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ";\n" +
                    "varying mediump vec2 " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ";\n" +
                    "varying lowp vec4 " + ShaderProgramConstants.VARYING_COLOR + ";\n" +
                    "void main() {\n" +
                    "   vec4 inten = vec4(" + UNIFORM_INTENCITY + ", " +
                    UNIFORM_INTENCITY + ", " +
                    UNIFORM_INTENCITY + ", 1.0);\n" +
                    "   gl_FragColor = inten * " + ShaderProgramConstants.VARYING_COLOR + " * " +
                    "texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ");\n" +
                    "}";
    private BlinkShader() {
        super(VERTEXT_SHADER, FRAGMENT_SHADER);
    }

    public static BlinkShader getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new BlinkShader();
        }

        return INSTANCE;
    }

    @Override
    protected void link(GLState pGLState) throws ShaderProgramLinkException {
        GLES20.glBindAttribLocation(mProgramID,
                ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION,
                ShaderProgramConstants.ATTRIBUTE_POSITION);
        GLES20.glBindAttribLocation(mProgramID,
                ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES_LOCATION,
                ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES);
        GLES20.glBindAttribLocation(mProgramID,
                ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION,
                ShaderProgramConstants.ATTRIBUTE_COLOR);
        super.link(pGLState);
        mModelViewProjectionMatrixLocation =
                getUniformLocation(ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX);
        mTexture0Location =
                getUniformLocation(ShaderProgramConstants.UNIFORM_TEXTURE_0);
        mIntencityLocation =
                getUniformLocation(UNIFORM_INTENCITY);
    }

    @Override
    public void bind(GLState pGLState, VertexBufferObjectAttributes pVertexBufferObjectAttributes) throws ShaderProgramException {
        super.bind(pGLState, pVertexBufferObjectAttributes);
        GLES20.glUniformMatrix4fv(
                mModelViewProjectionMatrixLocation,
                1,
                false,
                pGLState.getModelViewProjectionGLMatrix(),
                0
        );
        GLES20.glUniform1i(mTexture0Location, 0);
        if (mBlink) {
            long curTime = System.currentTimeMillis();
            long timeDelta = curTime - mPrevTime;
            mPrevTime = curTime;
            float angleDelta = timeDelta * BLINKING_SPEED;
            if (mGrow) {
                mCurAngle += angleDelta;
            } else {
                mCurAngle -= angleDelta;
            }
            if (mCurAngle > MAX_ANGLE) {
                mCurAngle -= 2 * angleDelta;
                mGrow = false;
            } else if (mCurAngle < 0) {
                mCurAngle += 2 * angleDelta;
                mGrow = true;
            }
        }
        GLES20.glUniform1f(mIntencityLocation, map(mCurAngle));
    }

    private static float map(double angle) {
        return (float) (MIN_INTENCITY + mDiv * Math.cos(Math.toRadians(angle)));
    }

    public static void setmBlink(boolean mBlink) {
        mPrevTime = System.currentTimeMillis();
        mGrow = false;
        mCurAngle = START_ANGLE;
        BlinkShader.mBlink = mBlink;
    }
}


It's a bit messy so sorry for this.
The shader itself is very easy. It just changes an intensity of the sprite according to passed variable.
ShaderProgramConstants.ATTRIBUTE_POSITION, ATTRIBUTE_TEXTURECOORDINATES, ATTRIBUTE_COLOR
are used for simplicity. They represent position, texture coordinates and color for triangle respectively. The main parts are link and bind methonds.
Link is called before the shader is linked and you have to bind your attributes here before the shader is linked.
GLES20.glBindAttribLocation()
is used for this.
ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION
is a constant that represents the location AndEngine uses for position and so on. After the linkage is completed in super.link we should get location of our unifor variables.
getUniformLocation()
is used for this. The nex inportant method is
public void bind(GLState pGLState, VertexBufferObjectAttributes pVertexBufferObjectAttributes) throws ShaderProgramExceptionpublic

It's called every time sprite is being drawn, so here we pass variables to shader.
GLES20.glUniform1i(mTexture0Location, 0)
This line means that 0's texture unit will be used for sampler.

How to attach shader to sprite

Attaching shader to sprite is really simple. You just have to write 
o.setShaderProgram(BlinkShader.getInstance());
BlinkShader.getInstance().setmBlink(true);
This will attach shader to sprite and start blinking. Also you have to add your shader to shader manager to correctly manage onResume and onPause events

mActivity.getShaderProgramManager().loadShaderProgram(BlinkShader.getInstance());

The end

Thats it. As you can see its very easy to use shaders in AndEngine if you know how to do this. Later i will show how to use multitexturing and maybe so other methods.

Wednesday, March 18, 2015

Inversion Of Control, Dependency injection and Java


What are Inversion of Control and Dependency Injection

    Inversion of control is a technique that allows to ease coupling in your programs. Dependency injection is a type of IoC that allows to get needed dependencies. 

Example

    Lets for example assume that you have the ICar and IEngine interfaces that represent abstract car and abstract engine. A car can start the engine and IEngine interface has ignite method which makes ignition. Than lets imagine that you are asked to implement that interfaces for really fast car with really powerful engine.
public interface IEngine {
    void ignite();
}

public interface ICar {
    void engineStart();
}

public class FastCar implements ICar {

    private IEngine mEngine;

    public FastCar(final IEngine engine) {
        mEngine = engine;
    }
    @Override
    public void engineStart() {
        mEngine.ignite();
    }
}

public class PowerfulEngine implements IEngine {
    @Override
    public void ignite() {
        System.out.println("Ignition");
    }
}
And to construct your car you shall do:
FastCar car = new FastCar(new PowerfulEngine());
Pretty easy. Yes. But what if you have a complex program where you have to pass your object through the hierarchy of classes. Not so pleasant. Or maybe you want to use the same engine in different cars. You will have to store and pass the same engine to every car.
That is what Inversion of Control is used.   
public class FastCar implements ICar {

    public FastCar() {
    }
    @Override
    public void engineStart() {
        try {
            IoCContainer.resolve(IEngine.class).ignite();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Much easier.

End

   Now a couple words about how to use IoCContainer that is attached to this article.
First of all it's basic realization and you can improve it a lot. For example add registration of created object. To use current realization mark constructor of your class with @ResolveAnnotation annotation to mark constructorthat must be used for object creation like this: 
public class TestClass {

    @ResolveAnnotation
    public TestClass() {}
}
Then register it and resolve when you need it:

IoCContainer.register(TestClass.class);
IoCContainer.resolve(TestClass.class);
To bind class on interface use:

IoCContatiner.register(IInterface.class, IImplementation.class);
IoCContatiner.resolve(IInterface.class);
Link to github with code for IoCContatiner