From 203ee1e1af761bdcee3840f64e62b2e88a0f702f Mon Sep 17 00:00:00 2001 From: Simon Leistikow Date: Wed, 13 Sep 2017 01:11:40 +0200 Subject: [PATCH] Add first revision of sphere rendering --- app/build.gradle | 8 +- .../java/de/trac/spherical/MainActivity.java | 8 + .../de/trac/spherical/rendering/Renderer.java | 224 ++++++++++++++++++ .../de/trac/spherical/rendering/Sphere.java | 180 ++++++++++++++ app/src/main/res/layout/activity_main.xml | 8 +- 5 files changed, 420 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/de/trac/spherical/rendering/Renderer.java create mode 100644 app/src/main/java/de/trac/spherical/rendering/Sphere.java diff --git a/app/build.gradle b/app/build.gradle index a89edf2..f1d8f62 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 26 - buildToolsVersion "26.0.1" + compileSdkVersion 25 + buildToolsVersion "25.0.3" defaultConfig { applicationId "de.trac.spherical" minSdkVersion 15 - targetSdkVersion 26 + targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -24,7 +24,7 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:26.+' + compile 'com.android.support:appcompat-v7:25.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' } diff --git a/app/src/main/java/de/trac/spherical/MainActivity.java b/app/src/main/java/de/trac/spherical/MainActivity.java index f56afd3..751202f 100644 --- a/app/src/main/java/de/trac/spherical/MainActivity.java +++ b/app/src/main/java/de/trac/spherical/MainActivity.java @@ -1,13 +1,21 @@ package de.trac.spherical; +import android.opengl.GLSurfaceView; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import de.trac.spherical.rendering.Renderer; + public class MainActivity extends AppCompatActivity { + private GLSurfaceView surfaceView; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + surfaceView = (GLSurfaceView) findViewById(R.id.surface_view); + surfaceView.setEGLContextClientVersion(2); + surfaceView.setRenderer(new Renderer()); } } diff --git a/app/src/main/java/de/trac/spherical/rendering/Renderer.java b/app/src/main/java/de/trac/spherical/rendering/Renderer.java new file mode 100644 index 0000000..7df8801 --- /dev/null +++ b/app/src/main/java/de/trac/spherical/rendering/Renderer.java @@ -0,0 +1,224 @@ +package de.trac.spherical.rendering; + + +import android.database.MatrixCursor; +import android.opengl.GLSurfaceView; +import android.opengl.Matrix; +import android.util.Log; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import static android.opengl.GLES20.*; + +public class Renderer implements GLSurfaceView.Renderer { + + /** + * Default vertex shader. + * + * In: pos + * uvw + * Out: uv + */ + private static final String DEFAULT_VERTEX_SHADER = + "uniform mat4 mvpMatrix;\n" + + "attribute vec3 position;\n" + + "attribute vec2 textureCoordinates;\n" + + "varying vec2 uv;\n" + + "void main() {\n" + + " gl_Position = mvpMatrix * vec4(position, 1);\n" + + " uv = textureCoordinates;\n" + + "}\n"; + + /** + * Default fragment shader. + * + * In: uv + * Out: sets fragment color + */ + private static final String DEFAULT_FRAGMENT_SHADER = + "precision mediump float;\n" + + "varying vec2 uv;\n" + + "uniform sampler2D tex;\n" + + "void main() {\n" + + " gl_FragColor = vec4(0);//texture2D(tex, uv);\n" + + "}\n"; + + // Store a sphere geometry as framework for the photo texture. + private Sphere sphere = null; + + // Store projection matrix. + private float projMatrix [] = new float [16]; + + // Store modelview matrix. + private float modlMatrix [] = new float [16]; + + // Store view matrix. + private float viewMatrix [] = new float [16]; + + // Store the model view projection matrix. + private float mvpMatrix [] = new float [16]; + + // Store shader name. + private int programID; + + // Store shader locations. + private int positionLocation; + private int textureCoordinatesLocation; + private int mvpLocation; + + /** + * Draws the frame. + * @param unused unused + */ + public void onDrawFrame(GL10 unused) { + + // Update transformation matrix. + Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modlMatrix, 0); + Matrix.multiplyMM(mvpMatrix, 0, projMatrix, 0, mvpMatrix, 0); + + // Draw the frame. + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUseProgram(programID); + + glEnableVertexAttribArray(positionLocation); + glVertexAttribPointer(positionLocation, 3, GL_FLOAT, false, 3*4, sphere.getVertexBuffer()); + + glEnableVertexAttribArray(textureCoordinatesLocation); + glVertexAttribPointer(textureCoordinatesLocation, 2, GL_FLOAT, false, 2*4, sphere.getTextureCoordinatesBuffer()); + + glUniformMatrix4fv(mvpLocation, 1, false, mvpMatrix, 0); + glDrawElements(GL_TRIANGLES, sphere.getIndexBuffer().capacity(), GL_UNSIGNED_SHORT, sphere.getIndexBuffer()); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + + glUseProgram(0); + } + + /** + * Callback called if surface changed. + * + * @param unused unused + * @param width new width of the surface + * @param height new height of the surface + */ + public void onSurfaceChanged(GL10 unused, int width, int height) { + glViewport(0, 0, width, height); + float ratio = (float) width / height; + Matrix.perspectiveM(projMatrix, 0, 45.0f, ratio, 0.25f, 128.0f); + } + + /** + * Callback called if surface has been created. + * + * @param unused unused + * @param config surface configuration + */ + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + initialize(); + + //TODO: (re)move tmp code + Matrix.setIdentityM(modlMatrix, 0); + Matrix.translateM(modlMatrix, 0, 0, 0, 4.0f); + Matrix.setLookAtM(viewMatrix, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); + } + + /** + * Initialize OpenGL state and data. + */ + public void initialize() { + + // Initialize sphere. + sphere = new Sphere(1.0f, 32, 32); // TODO: choose useful aparameters. + + // Set OpenGL state. + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Build shader program. + programID = buildProgram(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER); + } + + /** + * Reset OpenGL state and delete data. + */ + public void deinitialize() { + sphere = null; + glDeleteProgram(programID); + } + + /** + * Builds a shader program given vertex and fragment shader soruce. + * @param vertexSource The vertex shader source + * @param fragmentSource The fragment shader source + * @return shader program + */ + private int buildProgram(String vertexSource, String fragmentSource) { + + int vertexShader = buildShader(GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + throw new RuntimeException("vertex shader could not be loaded"); + } + + int fragmentShader = buildShader(GL_FRAGMENT_SHADER, fragmentSource); + if (fragmentShader == 0) { + throw new RuntimeException("fragment shader could not be loaded"); + } + + int program = glCreateProgram(); + if (program != 0) { + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + int[] linkStatus = new int[1]; + glGetProgramiv(program, GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GL_TRUE) { + Log.e("Shader", "Could not link program: "); + Log.e("Shader", glGetProgramInfoLog(program)); + glDeleteProgram(program); + throw new RuntimeException("Could not link program"); + } + } + + positionLocation = glGetAttribLocation(program, "position"); + if (positionLocation == -1) { + throw new RuntimeException("Could not get attribute location for 'position'"); + } + textureCoordinatesLocation = glGetAttribLocation(program, "textureCoordinates"); + if (textureCoordinatesLocation == -1) { + throw new RuntimeException("Could not get attribute location for 'textureCoordinates'"); + } + mvpLocation = glGetUniformLocation(program, "mvpMatrix"); + if (mvpLocation == -1) { + throw new RuntimeException("Could not get uniform location for 'mvpMatrix'"); + } + + return program; + } + + /** + * Builds a shader of a specified type from a given source. + * @param type The shader type. + * @param source The shader source + * @return shader name + */ + private int buildShader(int type, String source) { + int shader = glCreateShader(type); + if (shader != 0) { + glShaderSource(shader, source); + glCompileShader(shader); + int[] compiled = new int[1]; + glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e("Shader", "Could not compile shader " + type + ":"); + Log.e("Shader", glGetShaderInfoLog(shader)); + glDeleteShader(shader); + shader = 0; + } + } + return shader; + } +} diff --git a/app/src/main/java/de/trac/spherical/rendering/Sphere.java b/app/src/main/java/de/trac/spherical/rendering/Sphere.java new file mode 100644 index 0000000..2d0f70a --- /dev/null +++ b/app/src/main/java/de/trac/spherical/rendering/Sphere.java @@ -0,0 +1,180 @@ +package de.trac.spherical.rendering; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * This class is used to create native buffers holding vertices and + * texture coordinates of a sphere with a given radius. + */ +public class Sphere { + + // The following attributes make up our sphere. + private FloatBuffer vertexBuffer; + private FloatBuffer textureCoordinatesBuffer; + private ShortBuffer indexBuffer; + + public Sphere(float radius, int polyCountX, int polyCountY) { + + // Setup vertex buffer. + ByteBuffer buffer = ByteBuffer.allocateDirect((polyCountX*polyCountY+2)*2*3*4); + buffer.order(ByteOrder.nativeOrder()); + vertexBuffer = buffer.asFloatBuffer(); + + // Setup texture coordinate buffer. + buffer = ByteBuffer.allocateDirect((polyCountX*polyCountY+2)*2*2*4); + buffer.order(ByteOrder.nativeOrder()); + textureCoordinatesBuffer = buffer.asFloatBuffer(); + + // Setup index buffer. + buffer = ByteBuffer.allocateDirect(polyCountX*polyCountY*6*2); + buffer.order(ByteOrder.nativeOrder()); + indexBuffer = buffer.asShortBuffer(); + + int polyCountXPitch = polyCountX+1; // get to same vertex on next level + + int level = 0; + + for (int p1 = 0; p1 < polyCountY-1; p1++) { + //main quads, top to bottom + for (int p2 = 0; p2 < polyCountX - 1; p2++) + { + final int curr = level + p2; + indexBuffer.put((short)(curr + polyCountXPitch)); + indexBuffer.put((short)(curr)); + indexBuffer.put((short)(curr + 1)); + indexBuffer.put((short)(curr + polyCountXPitch)); + indexBuffer.put((short)(curr+1)); + indexBuffer.put((short)(curr + 1 + polyCountXPitch)); + } + + // the connectors from front to end + indexBuffer.put((short)(level + polyCountX - 1 + polyCountXPitch)); + indexBuffer.put((short)(level + polyCountX - 1)); + indexBuffer.put((short)(level + polyCountX)); + + indexBuffer.put((short)(level + polyCountX - 1 + polyCountXPitch)); + indexBuffer.put((short)(level + polyCountX)); + indexBuffer.put((short)(level + polyCountX + polyCountXPitch)); + level += polyCountXPitch; + } + + final int polyCountSq = polyCountXPitch * polyCountY; // top point + final int polyCountSq1 = polyCountSq + 1; // bottom point + final int polyCountSqM1 = (polyCountY - 1) * polyCountXPitch; // last row's first vertex + + for (int p2 = 0; p2 < polyCountX - 1; p2++) { + // create triangles which are at the top of the sphere + + indexBuffer.put((short)(polyCountSq)); + indexBuffer.put((short)(p2 + 1)); + indexBuffer.put((short)(p2)); + + // create triangles which are at the bottom of the sphere + + indexBuffer.put((short)(polyCountSqM1 + p2)); + indexBuffer.put((short)(polyCountSqM1 + p2 + 1)); + indexBuffer.put((short)(polyCountSq1)); + } + + // create final triangle which is at the top of the sphere + + indexBuffer.put((short)(polyCountSq)); + indexBuffer.put((short)(polyCountX)); + indexBuffer.put((short)(polyCountX-1)); + + // create final triangle which is at the bottom of the sphere + + indexBuffer.put((short)(polyCountSqM1 + polyCountX - 1)); + indexBuffer.put((short)(polyCountSqM1)); + indexBuffer.put((short)(polyCountSq1)); + + // calculate the angle which separates all points in a circle + final double AngleX = 2.0 * Math.PI / polyCountX; + final double AngleY = Math.PI / polyCountY; + + int i=0; + double axz; + + // we don't start at 0. + double ay = 0;//AngleY / 2; + for (int y = 0; y < polyCountY; y++) { + ay += AngleY; + final double sinay = Math.sin(ay); + axz = 0; + + // calculate the necessary vertices without the doubled one + for (int xz = 0; xz < polyCountX; xz++) + { + float rx = (float) (radius * Math.cos(axz) * sinay); + float ry = (float) (radius * Math.cos(ay)); + float rz = (float) (radius * Math.sin(axz) * sinay); + + // calculate texture coordinates via sphere mapping + // tu is the same on each level, so only calculate once + float tu = 0.5f; + if (y==0) + { + if (ry != -1.0f && ry != 1.0f) { + float len = (float) Math.sqrt(rx*rx + ry*ry + rz*rz); + tu = (float) (Math.acos(Math.max(Math.min(rx / len / sinay, 1.0), -1.0)) * 0.5 / Math.PI); + } + if (rz < 0.0f) + tu=1-tu; + } + else + tu = textureCoordinatesBuffer.get((i-polyCountXPitch)*2); + + vertexBuffer.put(rx); + vertexBuffer.put(ry); + vertexBuffer.put(rz); + textureCoordinatesBuffer.put(tu); + textureCoordinatesBuffer.put((float)(ay/Math.PI)); + + i++; + axz += AngleX; + } + // This is the doubled vertex on the initial position + vertexBuffer.put(vertexBuffer.get((i-polyCountX)*3 + 0)); + vertexBuffer.put(vertexBuffer.get((i-polyCountX)*3 + 1)); + vertexBuffer.put(vertexBuffer.get((i-polyCountX)*3 + 2)); + textureCoordinatesBuffer.put(1.0f); + textureCoordinatesBuffer.put(0.0f); + i++; + } + + // Add the vertex at the top of the sphere. + vertexBuffer.put(0.0f); + vertexBuffer.put(radius); + vertexBuffer.put(0.0f); + textureCoordinatesBuffer.put(0.5f); + textureCoordinatesBuffer.put(0.0f); + + // Add the vertex at the bottom of the sphere. + vertexBuffer.put(0.0f); + vertexBuffer.put(-radius); + vertexBuffer.put(0.0f); + textureCoordinatesBuffer.put(0.5f); + textureCoordinatesBuffer.put(1.0f); + + // Rewind buffers. + vertexBuffer.position(0); + textureCoordinatesBuffer.position(0); + indexBuffer.position(0); + } + + public FloatBuffer getVertexBuffer() { + return vertexBuffer; + } + + public FloatBuffer getTextureCoordinatesBuffer() { + return textureCoordinatesBuffer; + } + + public ShortBuffer getIndexBuffer() { + return indexBuffer; + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6f3be17..ada8c30 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,10 +6,10 @@ android:layout_height="match_parent" tools:context="de.trac.spherical.MainActivity"> -