Putting a border frame on the image

Pictures look best in a frame. Let's add one now. There are a number of ways to accomplish this, but we are going to use shaders. The frame will also be used for the thumbnail images and will enable us to change colors to highlight when the user selects an image. Furthermore, it helps define a region of contrast, which ensures that you can see the edge of any image on any background.

Border shaders

We can start by writing the shader programs which, among other things, define the variables they will need from the Material object that uses it.

If necessary, create a resource directory for the shaders, res/raw/. Then, create the border_vertex.shader and border_fragment.shader files. Define them as follows.

The border_vertex shader is identical to the unlit_tex_vertex shader that we were using.

File: res/raw/border_vertex.shader

uniform mat4 u_MVP;

attribute vec4 a_Position;
attribute vec2 a_TexCoordinate;

varying vec3 v_Position;
varying vec2 v_TexCoordinate;

void main() {
   // pass through the texture coordinate
   v_TexCoordinate = a_TexCoordinate;

   // final point in normalized screen coordinates
   gl_Position = u_MVP * a_Position;
}

For the border_fragement shader, we add variables for a border color (u_Color) and width (u_Width). Then, add a bit of logic to decide whether the current coordinate being rendered is on the border or in the texture image:

File: res/raw/border_fragment.shader

precision mediump float;
uniform sampler2D u_Texture; 

varying vec3 v_Position; 
varying vec2 v_TexCoordinate;
uniform vec4 u_Color;
uniform float u_Width;

void main() {
    // send the color from the texture straight out unless in // border area
    if(
        v_TexCoordinate.x > u_Width
        && v_TexCoordinate.x < 1.0 - u_Width
        && v_TexCoordinate.y > u_Width
        && v_TexCoordinate.y < 1.0 - u_Width
    ){
        gl_FragColor = texture2D(u_Texture, v_TexCoordinate);
    } else {
        gl_FragColor = u_Color;
    }
}

Note that this technique cuts off the edges of the image. We found this to be acceptable, but if you really want to see the entire image, you can offset the UV coordinates within the texture2D sampler call. It would look something like this:

float scale = 1.0 / (1 - u_Width * 2);
Vec2 offset = vec(
    v_TexCoordinate.x * scale – u_Width,
    v_TexCoordinate.x * scale – u_Width);
gl_FragColor = texture2D(u_Texture, offset);

Finally, observant readers might notice that when the plane is scaled non-uniformly (to make it a rectangle), the border will be scaled so that the vertical borders might be thicker or thinner than the horizontal borders. There are a number of ways to fix this, but this is left as an exercise for the (over-achieving) reader.

The border material

Next, we define the material for the border shader. Create a new Java class in RenderBoxExt/materials/ named BorderMaterial and define it as follows:

public class BorderMaterial extends Material {
    private static final String TAG = "bordermaterial";

}

Add material variables for the texture ID, border width, and color. Then, add variables for the shader program references and buffers, as shown in the following code:

    int textureId;
    public float borderWidth = 0.1f;
    public float[] borderColor = new float[]{0, 0, 0, 1}; // black
    static int program = -1; //Initialize to a totally invalid value for setup state
    static int positionParam;
    static int texCoordParam;
    static int textureParam;
    static int MVPParam;
    static int colorParam;
    static int widthParam;

    FloatBuffer vertexBuffer;
    FloatBuffer texCoordBuffer;
    ShortBuffer indexBuffer;
    int numIndices;

Now we can add a constructor. As we've seen earlier, it calls a setupProgram helper method that creates the shader program and obtains references to its parameters:

    public BorderMaterial() {
        super();
        setupProgram();
    }

    public static void setupProgram() {
        //Already setup?
        if (program > -1) return;
        //Create shader program
        program = createProgram(R.raw.border_vertex, R.raw.border_fragment);

        //Get vertex attribute parameters
        positionParam = GLES20.glGetAttribLocation(program, "a_Position");
        texCoordParam = GLES20.glGetAttribLocation(program, "a_TexCoordinate");

        //Enable them (turns out this is kind of a big deal ;)
        GLES20.glEnableVertexAttribArray(positionParam);
        GLES20.glEnableVertexAttribArray(texCoordParam);

        //Shader-specific parameters
        textureParam = GLES20.glGetUniformLocation(program, "u_Texture");
        MVPParam = GLES20.glGetUniformLocation(program, "u_MVP");
        colorParam = GLES20.glGetUniformLocation(program, "u_Color");
        widthParam = GLES20.glGetUniformLocation(program, "u_Width");
        RenderBox.checkGLError("Border params");
    }

Likewise, we add a setBuffers method to be called by the RenderObject component (Plane):

public void setBuffers(FloatBuffer vertexBuffer, FloatBuffer texCoordBuffer, ShortBuffer indexBuffer, int numIndices){
        //Associate VBO data with this instance of the material
        this.vertexBuffer = vertexBuffer;
        this.texCoordBuffer = texCoordBuffer;
        this.indexBuffer = indexBuffer;
        this.numIndices = numIndices;
    }

Provide a setter method for the texture ID:

    public void setTexture(int textureHandle) {
        textureId = textureHandle;
    }

Add the draw code, which will be called from the Camera component, to render the geometry prepared in the buffers (via setBuffer). The draw method looks like this:

    @Override
    public void draw(float[] view, float[] perspective) {
        GLES20.glUseProgram(program);

        // Set the active texture unit to texture unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        // Bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);

        // Tell the texture uniform sampler to use this texture in // the shader by binding to texture unit 0.
        GLES20.glUniform1i(textureParam, 0);

        Matrix.multiplyMM(modelView, 0, view, 0, RenderObject.model, 0);
        Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
        // Set the ModelViewProjection matrix for eye position.
        GLES20.glUniformMatrix4fv(MVPParam, 1, false, modelViewProjection, 0);

        GLES20.glUniform4fv(colorParam, 1, borderColor, 0);
        GLES20.glUniform1f(widthParam, borderWidth);

        //Set vertex attributes
        GLES20.glVertexAttribPointer(positionParam, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
        GLES20.glVertexAttribPointer(texCoordParam, 2, GLES20.GL_FLOAT, false, 0, texCoordBuffer);

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, numIndices, GLES20.GL_UNSIGNED_SHORT, indexBuffer);

        RenderBox.checkGLError("Border material draw");
    }

One more thing; let's provide a method to destroy an existing material:

    public static void destroy(){
        program = -1;
    }

Using the border material

To use the BorderMaterial class instead of the default UnlitTexMaterial class, we wrote in the Plane class previously, we can add it to the Plane Java class, as follows. We plan to create the material outside the Plane class (in MainActivity), so we just need to set it up. In Plane.java, add the following code:

    public void setupBorderMaterial(BorderMaterial material){
        this.material = material;
        material.setBuffers(vertexBuffer, texCoordBuffer,         indexBuffer, numIndices);
    }

In MainActivity, modify the setupScreen method to use this material instead of the default one, as follows. We first create the material and set the texture to our sample image. We don’t need to set the color, which will default to black. Then we create the screen plane and set its material. And then create the transform and add the screen component:

    void setupScreen() {
        //...
        Screen = new Plane();
        BorderMaterial screenMaterial = new BorderMaterial();
        screenMaterial.setTexture(RenderBox.loadTexture( R.drawable.sample360));
        screen.setupBorderMaterial(screenMaterial);
        //...

}

When you run it now, it should look something like this:

Using the border material
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset