Creating the Sun

The Sun will be rendered as a textured sphere. However, it's not shaded with front and back sides like our Earth. We need to render it unlit or rather unshaded. This means we need to create the UnlitTextureMaterial.

We have a texture file for the Sun, too (and all the planets as well).We won't show all of them in the chapter although they're included with the downloadable files for the book.

Drag a copy of the sun_tex.png file onto your res/drawable/ folder.

Unlit texture shaders

As we've seen earlier in this book, unlit shaders are much simpler than ones with lighting. In your res/raw/ folder, create files for unlit_tex_vertex.shader and unlit_tex_fragment.shader, and then define them, as follows.

File: unlit_tex_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;
}

File: unlit_tex_fragment.shader

precision mediump float;        // default medium precision
uniform sampler2D u_Texture;    // the input texture

varying vec3 v_Position;
varying vec2 v_TexCoordinate;

void main() {
    // Send the color from the texture straight out
    gl_FragColor = texture2D(u_Texture, v_TexCoordinate);
}

Yup, that's simpler than our earlier shaders.

Unlit texture material

Now, we can write the UnlitTexMaterial class. Here's the initial code:

public class UnlitTexMaterial extends Material {
    private static final String TAG = "unlittex";

    int textureId;
    
    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;

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

Here are the constructor, setupProgram, and setBuffers methods:

    public UnlitTexMaterial(int resourceId){
        super();
        setupProgram();
        this.textureId = MainActivity.loadTexture(resourceId);
    }
    
    public static void setupProgram(){
        if(program != -1) return;
        //Create shader program
        program = createProgram(R.raw.unlit_tex_vertex, R.raw.unlit_tex_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");

        RenderBox.checkGLError("Unlit Texture params");
    }
    
    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;
    }

It will be handy to have getter and setter methods for the texture ID (in later projects, not used here):

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

      public int getTexture(){
          return textureId;
      }

Lastly, here's the draw method:

    @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 in the shader.
        GLES20.glUniformMatrix4fv(MVPParam, 1, false, modelViewProjection, 0);

        // Set the 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("Unlit Texture draw");
    }
}

Rendering with an unlit texture

We're ready to integrate the new material into our Sphere class and see how it looks.

In Sphere.java, add a new constructor that takes a boolean parameter, indicating that the texture should be lighted, and the createUnlitTexMaterial helper method:

    public Sphere(int textureId, boolean lighting){
        super();
        allocateBuffers();
        if (lighting) {
            createDiffuseMaterial(textureId);
        } else {
            createUnlitTexMaterial(textureId);
        }
    }

    public Sphere createUnlitTexMaterial(int textureId){
        UnlitTexMaterial mat = new UnlitTexMaterial(textureId);
        mat.setBuffers(vertexBuffer, texCoordBuffer, indexBuffer, numIndices);
        material = mat;
        return this;
    }

Notice that the way in which we've defined constructors, you can call either new Sphere(texId) or Sphere(texId, true) to get lighted renders. But for unlit, you must use the second one as Sphere(texId, false). Also note that setting up the whole component in the constructor is not the only way to go. We only do it this way because it keeps our MainActivity code concise. In fact, as we start expanding our use of RenderBox and its shader library, it will become necessary to put most of this code into our MainActivity class. It would be impossible to create a constructor for every type of material. Ultimately, a materials system is necessary to allow you to create and set materials without having to create a new class for each one.

Adding the Sun

Now, all we need to do is add the Sun sphere to the setup method of MainActivity. Let's make it big, say, at a scale of 6.963 (remember that's in millions of kms). This value may seem arbitrary now, but you'll see where it comes from when we run the calculations on the Solar System geometry and scale the planets as well.

Add the following code to the setup method of MainActivity:

    public void setup() {
        Transform origin = new Transform();

        //Sun
        Transform sun = new Transform()
            .setParent(origin, false)
            .setLocalScale(6.963f, 6.963f, 6.963f)
            .addComponent(new Sphere(R.drawable.sun_tex, false));

        //"Sun" light
        RenderBox.instance.mainLight.transform.setPosition( origin.getPosition());
        RenderBox.instance.mainLight.color = new float[]{1, 1, 0.8f, 1};

            //Earth…

We start by defining an origin transform that will be the center of the Solar System. Then, we create the Sun, parented to the origin, with the given scale. Then, add a new sphere component with the Sun texture. We've also given our light a slightly yellowish color, which will blend with the Earth's texture colors.

Here's what the rendered Sun looks like, which seems to illuminate the Earth:

Adding the Sun

Now, let's move on to the rest of the Solar System.

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

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