Materials, textures, and shaders

In Chapter 3, Cardboard Box, we introduced the OpenGL ES 2.0 graphics pipeline and simple shaders. We will now extract that code into a separate Material class.

In computer graphics, materials refer to the visual surface characteristics of geometric models. When rendering an object in the scene, materials are used together with lighting and other scene information required by the shader code and the OpenGL graphics pipeline.

A solid colored material is the simplest; the entire surface of the object is a single color. Any color variation in the final rendering will be due to lighting, shadows, and other features in a different shader variant. It is quite possible to produce solid color materials with lighting and shadows, but the simplest possible example just fills raster segments with the same color, such as our very first shader.

A textured material may have surface details defined in an image file (such as a JPG). Textures are like wallpapers pasted on the surface of the object. They can be used to a great extent and are responsible for most of the details that the user perceives on an object. A solid colored sphere may look like a ping pong ball. A textured sphere may look like the Earth. More texture channels can be added to define variations in shading or even to emit light when the surface is in shadow. You will see this kind of effect at the end of Chapter 6, Solar System, when we add an artificial light to the dark side of the Earth.

More realistic physically-based shading goes beyond texture maps to include simulated height maps, metallic shininess, and other imperfections, such as rust or dirt. We won't be going into that in this book, but it's common in graphics engines such as Unity 3D and the Unreal Engine. Our RenderBox library could be extended to support it.

Presently, we'll build the infrastructure for a basic solid colored material and associated shaders. Later in the chapter, we'll expand it with lighting.

Abstract material

In the renderbox/materials/ folder, create a new Java class named Material and begin to write it as follows:

public abstract class Material {
    private static final String TAG = "RenderBox.Material";

    protected static final float[] modelView = new float[16];
    protected static final float[] modelViewProjection = new float[16];

    public static int createProgram(int vertexShaderResource, int fragmentShaderResource){
        int vertexShader = loadGLShader(GLES20.GL_VERTEX_SHADER, vertexShaderResource);
        int passthroughShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderResource);

        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, vertexShader);
        GLES20.glAttachShader(program, passthroughShader);
        GLES20.glLinkProgram(program);
        GLES20.glUseProgram(program);

        RenderBox.checkGLError("Material.createProgram");
        return program;
    }

    public abstract void draw(float[] view, float[] perspective);
}

This defines an abstract class that will be used to extend the various types of materials we define. The createProgram method loads the designated shader scripts and builds an OpenGL ES program with the shaders attached.

We also define an abstract draw() method that will be implemented in each shader separately. Among other things, it requires the modelView and modelViewProjection transformation matrices be declared at the top of the class. At this point, we will actually only use modelViewProjection, but a separate reference to the modelView matrix will be needed when we add lighting.

Next, add the following utility methods to the Material class to load the shaders:

    /**
     * Converts a raw text file, saved as a resource, into an OpenGL ES shader.
     *
     * @param type The type of shader we will be creating.
     * @param resId The resource ID of the raw text file about to be turned into a shader.
     * @return The shader object handler.
     */
    public static int loadGLShader(int type, int resId) {
        String code = readRawTextFile(resId);
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, code);
        GLES20.glCompileShader(shader);

        // Get the compilation status.
        final int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

        // If the compilation failed, delete the shader.
        if (compileStatus[0] == 0) {
            Log.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);
            shader = 0;
        }

        if (shader == 0) {
            throw new RuntimeException("Error creating shader.");
        }

        return shader;
    }

    /**
     * Converts a raw text file into a string.
     *
     * @param resId The resource ID of the raw text file about to be turned into a shader.
     * @return The context of the text file, or null in case of error.
     */
    private static String readRawTextFile(int resId) {
        InputStream inputStream = RenderBox.instance.mainActivity.getResources().openRawResource(resId);
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("
");
            }
            reader.close();
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

As discussed in Chapter 3, Cardboard Box, these methods will load a shader script and compile it.

Later on, we'll derive specific materials from this class, and define specific shaders that each one will use.

..................Content has been hidden....................

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