123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- /*
- * Copyright (C) 2018 Alyssa Rosenzweig <alyssa@rosenzweig.io>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sub license,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the
- * next paragraph) shall be included in all copies or substantial portions
- * of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
- #include <assert.h>
- #include "test-util-3d.h"
- /* Include image loading library */
- #define STB_IMAGE_IMPLEMENTATION
- #include "stb_image.h"
- static EGLint const config_attribute_list[] = {
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_DEPTH_SIZE, 8,
- EGL_NONE
- };
- static const EGLint context_attribute_list[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE
- };
- static EGLDisplay display;
- static EGLConfig config;
- static EGLint num_config;
- static EGLContext context;
- static GLuint program;
- const char *vertex_shader_source =
- "attribute vec4 aPosition; \n"
- "attribute vec4 aColor; \n"
- "uniform sampler2D depthMap; \n"
- "uniform float angle;\n"
- " \n"
- "varying vec4 vColor; \n"
- " \n"
- "void main() \n"
- "{ \n"
- " vColor = aColor * vec4(8.02, 1.18, 1, 1); \n"
- " float x = aPosition.x;\n"
- " float y = aPosition.y;\n"
- " float z = texture2D(depthMap, vColor.xy).r;"
- " gl_Position = vec4(x, cos(angle)*y - sin(angle)*z, 1.0, 1.0); \n"
- "} \n";
- const char *fragment_shader_source =
- "precision mediump float; \n"
- " \n"
- "varying vec4 vColor; \n"
- "uniform sampler2D uTexture; \n"
- "uniform sampler2D depthMap; \n"
- " \n"
- "void main() \n"
- "{ \n"
- // " float z = texture2D(depthMap, vColor.xy).r;"
- " gl_FragColor = texture2D(uTexture, vColor.xy); \n"
- "} \n";
- /* Forward mapping implementation:
- *
- * 1. Generate an nxn triangular grid
- * 2. Run mapping in vertex shader
- * 3. Direct texture mapping in fragment shader
- */
- struct forward_grid {
- int width, height;
- GLfloat *vVertices;
- GLfloat *vColors;
- unsigned short *indices;
- };
- static struct forward_grid
- generate_grid(int N)
- {
- struct forward_grid grid = {
- .width = N,
- .height = N
- };
- /* Allocate room for the vertices/attributes/indices */
- grid.vVertices = calloc(N * N * 3, sizeof(GLfloat));
- grid.vColors = calloc(N * N * 4, sizeof(GLfloat));
- grid.indices = calloc(N * N * 6, sizeof(*grid.indices));
- /* Max number of vertices according to ushort indices */
- assert(N < 128);
- /* We want to subdivide [-1, -1] to [1, 1] in nxn pieces. That is,
- * iterate in each direction with a step size of 2/(N - 1) to make sure
- * both 0 and 1 are included */
- float step_size = (2.0) / ((float) (N - 1));
- for (int step_y = 0; step_y < N; ++step_y) {
- float y = step_y * step_size - 0.7f;
- for (int step_x = 0; step_x < N; ++step_x) {
- float x = step_x * step_size - 0.7f;
- /* This grid section is a rect from (x, y) to
- * (x+step_size, y + step_size) */
- int vertex_number = step_y*N + step_x;
- grid.vVertices[vertex_number*3 + 0] = x;
- grid.vVertices[vertex_number*3 + 1] = y;
- /* Set some attributes */
- grid.vColors[vertex_number*4 + 0] = ((float) step_x) / (N - 1);
- grid.vColors[vertex_number*4 + 1] = ((float) step_y) / (N - 1);
- }
- }
- /* Form indexed buffer from the subdivied vertices. The scheme is to
- * draw quads connecting adjacent vertices, and split those quads into
- * triangles. */
- int index_start = 0;
- for (int y = 0; y < (N - 1); ++y) {
- for (int x = 0; x < (N - 1); ++x) {
- /* Connect (x, y) (x + 1, y) (x, y + 1) */
- grid.indices[index_start + 0] = (y + 0)*N + (x + 0);
- grid.indices[index_start + 1] = (y + 0)*N + (x + 1);
- grid.indices[index_start + 2] = (y + 1)*N + (x + 0);
- /* Connect (x + 1, y + 1) (x + 1, y) (x, y + 1).
- * XXX: Winding order? */
- grid.indices[index_start + 3] = (y + 1)*N + (x + 1);
- grid.indices[index_start + 4] = (y + 0)*N + (x + 1);
- grid.indices[index_start + 5] = (y + 1)*N + (x + 0);
- /* Advance index buffer */
- index_start += 6;
- }
- }
- /* Send the grid off to the GPU */
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, grid.vVertices);
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, grid.vColors);
- glEnableVertexAttribArray(1);
- return grid;
- }
- static void
- draw_grid(struct forward_grid *grid)
- {
- /* Triangle*2 * the size of the grid */
- glDrawElements(GL_TRIANGLES, 3 * 2 * grid->width * grid->height, GL_UNSIGNED_SHORT, grid->indices);
- }
- /* Texture related boilerplate */
- struct image {
- uint8_t *data;
- int width;
- int height;
- int n;
- };
- static void
- init_texture(struct image *img, int number)
- {
- GLuint textures[2], texture_handle;
- glGenTextures(1, &textures);
- glActiveTexture(GL_TEXTURE0 + number);
- glBindTexture(GL_TEXTURE_2D, textures[0]);
- int mode = (img->n == 4) ? GL_RGBA : GL_LUMINANCE;
- glTexImage2D(
- GL_TEXTURE_2D, 0, mode,
- img->width, img->height, 0,
- mode, GL_UNSIGNED_BYTE, img->data);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- texture_handle = glGetUniformLocation(program, number ? "depthMap" : "uTexture");
- glUniform1i(texture_handle, number);
- }
- /* Depth map generation:
- * The idea is to choose an axis running through the object,
- * and measure perpendicular distance away from that axis.
- */
- /* First, consider perpendicular distance "pixel counting".
- *
- * In particular, we limit to the special case where the axis is pointing
- * completely down. This is ideal to avoid roundoff error which would greatly
- * complicate the routine and later stages. TODO: Rotate the image
- * ahead-of-time.
- *
- * Thus, it is only necessary to know the starting point to count from.
- * Counting looks for pixels in the strictly perpendicular direction; that is,
- * directly to the left or right. At the moment, we assume considerable
- * symmetry and count to the right. TODO: Is this correct? */
- static int
- count_pixels(struct image *img, int start_x, int start_y)
- {
- int distance = 0;
- uint32_t *data32 = (uint32_t *) img->data;
- /* Iterate perpendicularly */
- for (int x = start_x; x < img->width; ++x) {
- uint32_t color = data32[start_y * img->width + x];
- uint8_t alpha = color >> 24;
- /* If there's nothing here, the edge has been found */
- if (alpha == 0) break;
- ++distance;
- }
- return distance;
- }
- #define MIN(a,b) ((a>b)?(b):(a))
- /* Next, we generate the 8-bit depth map.
- *
- * We first count pixels along the vertical axis for easy access.
- *
- * We then assign each pixel a depth value based on the depth function and this
- * count */
- static struct image
- generate_depth_map(struct image *src, int start_x, int start_y)
- {
- /* Allocate room for the image and perpendicular lengths */
- struct image dst = {
- .data = calloc(src->width * src->height, 4),
- .width = src->width,
- .height = src->height,
- .n = 4,
- };
- int *lengths = malloc(sizeof(int) * src->height);
- /* Count pixels */
- for (int y = 0; y < src->height; ++y) {
- lengths[y] = count_pixels(src, start_x, y);
- }
- /* Generate depth map */
- uint32_t *data32 = (uint32_t *) dst.data;
- for (int y = 0; y < dst.height; ++y) {
- for (int x = 0; x < dst.width; ++x) {
- float length = lengths[y];
- float coord = x - start_x;
- float depth_det = length*length - coord*coord;
- /* Assume zero depth => nonexistent */
- uint8_t udepth = 0;
- /* If determinant is negative, no depth. If positive,
- * there is something here; take the square root and
- * store that. */
- if (depth_det >= 0) {
- float depth = sqrtf(depth_det);
- int d = depth * 4;
- data32[(y * dst.width) + x] = MIN(d, 255);
- }
- }
- }
- return dst;
- }
- int main()
- {
- GLint width, height;
- EGLSurface surface;
- display = get_display();
- /* get an appropriate EGL frame buffer configuration */
- eglChooseConfig(display, config_attribute_list, &config, 1, &num_config);
- /* create an EGL rendering context */
- context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attribute_list);
- surface = make_window(display, config, 2048, 1280);
- eglQuerySurface(display, surface, EGL_WIDTH, &width);
- eglQuerySurface(display, surface, EGL_HEIGHT, &height);
- /* connect the context to the surface */
- eglMakeCurrent(display, surface, surface, context);
- program = get_program(vertex_shader_source, fragment_shader_source);
- glBindAttribLocation(program, 0, "aPosition");
- glBindAttribLocation(program, 1, "aColor");
- link_program(program);
- glViewport(0, 0, width, height);
- /* Load the image */
- struct image img;
- img.data = stbi_load("pencil.png", &img.width, &img.height, &img.n, 0);
- assert(img.data != NULL);
- assert(img.n == 4);
- /* XXX: We probably want a better way to input these */
- int start_x = img.width >> 1;
- int start_y = 0;
- struct image depth_map = generate_depth_map(&img, start_x, start_y);
- init_texture(&img, 0);
- init_texture(&depth_map, 1);
- /* Clean up */
- stbi_image_free(img.data);
- /* Generate the forward mapping grid */
- struct forward_grid grid = generate_grid(127);
- /* Setup the angle */
- int u_angle = glGetUniformLocation(program, "angle");
- float angle = 0.0f;
- for (;;) {
- glClearColor(0.5, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
- glUniform1f(u_angle, angle);
- draw_grid(&grid);
- eglSwapBuffers(display, surface);
- glFlush();
- angle += (3.14159f/180.0f);
- }
- eglDestroySurface(display, surface);
- eglTerminate(display);
- return 0;
- }
|