From abc1eabecb6da1bf77b530132814a2427bfb44fb Mon Sep 17 00:00:00 2001 From: Oleksandr Shcherbina Date: Tue, 26 May 2015 18:40:03 +0200 Subject: [PATCH] evas: fix compatibility with GLES2.0. Shadow effect Summary: Use RGBA texture unit for generation shadow. Use separate framebuffer and renderbuffer for shadow map. Refactor shadow map shader to use position of shadow and pack depth value in RGBA texture. Refactor shader function for generation shadow factor to unpack depth value from RGBA shadow map. Reviewers: cedric, Hermet Subscribers: cedric Differential Revision: https://phab.enlightenment.org/D2578 Signed-off-by: Cedric BAIL --- .../evas/engines/gl_common/evas_gl_3d.c | 50 +++++++- .../engines/gl_common/evas_gl_3d_private.h | 4 + .../gl_common/shader_3d/evas_gl_3d_shaders.x | 120 ++++++++++++------ .../engines/gl_common/shader_3d/include.shd | 26 ++-- .../gl_common/shader_3d/shadow_map_frag.shd | 14 +- .../gl_common/shader_3d/shadow_map_vert.shd | 3 +- 6 files changed, 157 insertions(+), 60 deletions(-) diff --git a/src/modules/evas/engines/gl_common/evas_gl_3d.c b/src/modules/evas/engines/gl_common/evas_gl_3d.c index 1c393159e9..8caf9ae620 100644 --- a/src/modules/evas/engines/gl_common/evas_gl_3d.c +++ b/src/modules/evas/engines/gl_common/evas_gl_3d.c @@ -276,6 +276,9 @@ e3d_drawable_new(int w, int h, int alpha, GLenum depth_format, GLenum stencil_fo GLuint depth_stencil_buf = 0; GLuint depth_buf = 0; GLuint stencil_buf = 0; +#ifdef GL_GLES + GLuint shadow_fbo, depth_render_buf; +#endif Eina_Bool depth_stencil = EINA_FALSE; glGenTextures(1, &tex); @@ -298,8 +301,13 @@ e3d_drawable_new(int w, int h, int alpha, GLenum depth_format, GLenum stencil_fo glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); #ifndef GL_GLES glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, w, h, 0, GL_RED, GL_UNSIGNED_SHORT, 0); +#else + glGenFramebuffers(1, &shadow_fbo); + glGenFramebuffers(1, &depth_render_buf); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); #endif + glGenFramebuffers(1, &color_pick_fb_id); glGenTextures(1, &texcolorpick); glBindTexture(GL_TEXTURE_2D, texcolorpick); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); @@ -310,8 +318,6 @@ e3d_drawable_new(int w, int h, int alpha, GLenum depth_format, GLenum stencil_fo glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, w, h, 0, GL_RED, GL_UNSIGNED_SHORT, 0); #endif - glGenFramebuffers(1, &color_pick_fb_id); - glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); @@ -393,7 +399,10 @@ e3d_drawable_new(int w, int h, int alpha, GLenum depth_format, GLenum stencil_fo drawable->depth_buf = depth_buf; drawable->stencil_buf = stencil_buf; drawable->texDepth = texDepth; - +#ifdef GL_GLES + drawable->shadow_fbo = shadow_fbo; + drawable->depth_render_buf = depth_render_buf; +#endif return drawable; error: @@ -403,6 +412,8 @@ error: glDeleteTextures(1, &tex); if (texcolorpick) glDeleteTextures(1, &texcolorpick); + if (texDepth) + glDeleteTextures(1, &texDepth); if (fbo) glDeleteFramebuffers(1, &fbo); @@ -424,6 +435,13 @@ error: if (stencil_buf) glDeleteRenderbuffers(1, &stencil_buf); +#ifdef GL_GLES + if (shadow_fbo) + glDeleteFramebuffers(1, &shadow_fbo); + if (depth_render_buf) + glDeleteFramebuffers(1, &depth_render_buf); +#endif + return NULL; } @@ -1146,7 +1164,9 @@ _mesh_draw(E3D_Renderer *renderer, Evas_3D_Mesh *mesh, int frame, Evas_3D_Node * e3d_renderer_draw(renderer, &data); } -void _shadowmap_render(E3D_Drawable *drawable, E3D_Renderer *renderer, Evas_3D_Scene_Public_Data *data, Evas_Mat4 *matrix_light_eye, Evas_3D_Node *light) +void _shadowmap_render(E3D_Drawable *drawable, E3D_Renderer *renderer, + Evas_3D_Scene_Public_Data *data, Evas_Mat4 *matrix_light_eye, + Evas_3D_Node *light) { Eina_List *l; Evas_3D_Node *n; @@ -1157,12 +1177,24 @@ void _shadowmap_render(E3D_Drawable *drawable, E3D_Renderer *renderer, Evas_3D_S glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(4.0, 100.0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, drawable->texDepth, 0); +#ifdef GL_GLES + glBindFramebuffer(GL_FRAMEBUFFER, drawable->shadow_fbo); + glBindRenderbuffer(GL_RENDERBUFFER, drawable->depth_render_buf); +#endif + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + drawable->texDepth, 0); +#ifdef GL_GLES + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, drawable->w, + drawable->h); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, + drawable->depth_render_buf); +#endif e3d_renderer_target_set(renderer, drawable); e3d_renderer_clear(renderer, &c); Evas_3D_Node_Data *pd_light_node = eo_data_scope_get(light, EVAS_3D_NODE_CLASS); - Evas_3D_Light_Data *pd = eo_data_scope_get(pd_light_node->data.light.light, EVAS_3D_LIGHT_CLASS); + Evas_3D_Light_Data *pd = eo_data_scope_get(pd_light_node->data.light.light, + EVAS_3D_LIGHT_CLASS); Evas_Vec4 planes[6]; evas_mat4_multiply(&matrix_vp, &pd->projection, matrix_light_eye); @@ -1180,7 +1212,8 @@ void _shadowmap_render(E3D_Drawable *drawable, E3D_Renderer *renderer, Evas_3D_S blend_enabled = pdmesh->blending; pdmesh->blending = EINA_FALSE; pdmesh->shade_mode = EVAS_3D_SHADE_MODE_SHADOW_MAP_RENDER; - _mesh_draw(renderer, nm->mesh, nm->frame, light, matrix_light_eye, &matrix_mv, &matrix_mvp, &matrix_mvp); + _mesh_draw(renderer, nm->mesh, nm->frame, light, matrix_light_eye, + &matrix_mv, &matrix_mvp, &matrix_mvp); pdmesh->shade_mode = shade_mode; pdmesh->blending = blend_enabled; } @@ -1189,6 +1222,9 @@ void _shadowmap_render(E3D_Drawable *drawable, E3D_Renderer *renderer, Evas_3D_S } glDisable(GL_POLYGON_OFFSET_FILL); +#ifdef GL_GLES + glBindFramebuffer(GL_FRAMEBUFFER, drawable->fbo); +#endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, drawable->tex, 0); } diff --git a/src/modules/evas/engines/gl_common/evas_gl_3d_private.h b/src/modules/evas/engines/gl_common/evas_gl_3d_private.h index 87fbb8a31c..ad8d992c8a 100644 --- a/src/modules/evas/engines/gl_common/evas_gl_3d_private.h +++ b/src/modules/evas/engines/gl_common/evas_gl_3d_private.h @@ -147,6 +147,10 @@ struct _E3D_Drawable GLuint texDepth; GLuint texcolorpick; GLuint color_pick_fb_id; +#ifdef GL_GLES + GLuint shadow_fbo; + GLuint depth_render_buf; +#endif }; /* Texture internal functions. */ diff --git a/src/modules/evas/engines/gl_common/shader_3d/evas_gl_3d_shaders.x b/src/modules/evas/engines/gl_common/shader_3d/evas_gl_3d_shaders.x index d4bd7ebb4f..0505e412f9 100644 --- a/src/modules/evas/engines/gl_common/shader_3d/evas_gl_3d_shaders.x +++ b/src/modules/evas/engines/gl_common/shader_3d/evas_gl_3d_shaders.x @@ -403,19 +403,25 @@ static const char flat_frag_glsl[] = "uniform sampler2D uShadowMap;\n" "float shadow;\n" "float pcf(vec4 lpos, float size)\n" - " {\n" + "{\n" + " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" + " float i, j, shadow;\n" + " shadow = 0.0;\n" "#ifndef GL_ES\n" - " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" - " float i, j, randx, randy, shadow;\n" - " shadow = 0.0;\n" - " for (i = -4.0; i < 4.0; i++)\n" - " for (j = -4.0; j < 4.0; j++)\n" - " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" - " return shadow / 64.0;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" "#else\n" - " /*TODO Add algorithm generate shadow*/\n" - " return 1.0;\n" + " const vec4 unpack = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);\n" + " const float bias = 0.00015 /*TODO Optimizate set of offset*/;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " {\n" + " vec4 zvalue = texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size);\n" + " shadow += float(smcoord.z < dot(zvalue, unpack) + bias);\n" + " }\n" "#endif //GL_ES\n" + " return shadow / 64.0;\n" "}\n" "#endif //SHADOWED\n" "#ifdef DIFFUSE\n" @@ -734,19 +740,25 @@ static const char phong_frag_glsl[] = "uniform sampler2D uShadowMap;\n" "float shadow;\n" "float pcf(vec4 lpos, float size)\n" - " {\n" + "{\n" + " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" + " float i, j, shadow;\n" + " shadow = 0.0;\n" "#ifndef GL_ES\n" - " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" - " float i, j, randx, randy, shadow;\n" - " shadow = 0.0;\n" - " for (i = -4.0; i < 4.0; i++)\n" - " for (j = -4.0; j < 4.0; j++)\n" - " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" - " return shadow / 64.0;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" "#else\n" - " /*TODO Add algorithm generate shadow*/\n" - " return 1.0;\n" + " const vec4 unpack = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);\n" + " const float bias = 0.00015 /*TODO Optimizate set of offset*/;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " {\n" + " vec4 zvalue = texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size);\n" + " shadow += float(smcoord.z < dot(zvalue, unpack) + bias);\n" + " }\n" "#endif //GL_ES\n" + " return shadow / 64.0;\n" "}\n" "#endif //SHADOWED\n" "#ifdef DIFFUSE\n" @@ -1158,19 +1170,25 @@ static const char normal_map_frag_glsl[] = "uniform sampler2D uShadowMap;\n" "float shadow;\n" "float pcf(vec4 lpos, float size)\n" - " {\n" + "{\n" + " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" + " float i, j, shadow;\n" + " shadow = 0.0;\n" "#ifndef GL_ES\n" - " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" - " float i, j, randx, randy, shadow;\n" - " shadow = 0.0;\n" - " for (i = -4.0; i < 4.0; i++)\n" - " for (j = -4.0; j < 4.0; j++)\n" - " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" - " return shadow / 64.0;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" "#else\n" - " /*TODO Add algorithm generate shadow*/\n" - " return 1.0;\n" + " const vec4 unpack = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);\n" + " const float bias = 0.00015 /*TODO Optimizate set of offset*/;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " {\n" + " vec4 zvalue = texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size);\n" + " shadow += float(smcoord.z < dot(zvalue, unpack) + bias);\n" + " }\n" "#endif //GL_ES\n" + " return shadow / 64.0;\n" "}\n" "#endif //SHADOWED\n" "#ifdef NORMAL_TEXTURE\n" @@ -1461,6 +1479,7 @@ static const char shadow_map_vert_glsl[] = "precision lowp sampler2D;\n" "#endif\n" "uniform mat4 uMatrixMvp;\n" + "varying vec4 shadowmapposition;\n" "#ifdef VERTEX_POSITION\n" "attribute vec4 aPosition0;\n" "#endif //VERTEX_POSITION\n" @@ -1500,6 +1519,7 @@ static const char shadow_map_vert_glsl[] = "#endif //VERTEX_TEXCOORD_BLEND\n" "#endif //ALPHA_TEST_ENABLED\n" " gl_Position = uMatrixMvp * position;\n" + " shadowmapposition = gl_Position;\n" "}\n"; static const char shadow_map_frag_glsl[] = @@ -1508,6 +1528,7 @@ static const char shadow_map_frag_glsl[] = "precision mediump int;\n" "precision lowp sampler2D;\n" "#endif\n" + "varying vec4 shadowmapposition;\n" "#ifdef ALPHA_TEST_ENABLED\n" "#ifdef NEED_TEX_COORD\n" "varying vec2 vTexCoord;\n" @@ -1544,7 +1565,7 @@ static const char shadow_map_frag_glsl[] = " (1.0 - uTextureDiffuseWeight));\n" "#else\n" "#ifdef DIFFUSE_TEXTURE\n" - " color = texture2D(uTextureDiffuse0, vec2(Tex0CoordDiffuse)) ;\n" + " color = texture2D(uTextureDiffuse0, vec2(Tex0CoordDiffuse));\n" "#else\n" " color = vec4(1);\n" "#endif //DIFFUSE_TEXTURE\n" @@ -1590,7 +1611,18 @@ static const char shadow_map_frag_glsl[] = " gl_FragColor = color;\n" "#endif //GL_ES\n" "#endif //ALPHA_TEST_ENABLED\n" + "#ifndef GL_ES\n" " gl_FragColor.r = gl_FragCoord.z;\n" + "#else\n" + " const vec4 pack = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);\n" + " const vec4 mask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\n" + " vec4 depthcolor;\n" + " float normdist = shadowmapposition.z / shadowmapposition.w;\n" + " normdist = ((normdist + 1.0) / 2.0);\n" + " depthcolor = vec4(fract(pack * normdist));\n" + " depthcolor -= depthcolor.xxyz * mask;\n" + " gl_FragColor = depthcolor;\n" + "#endif\n" "}\n"; static const char color_pick_vert_glsl[] = @@ -1832,19 +1864,25 @@ static const char parallax_occlusion_frag_glsl[] = "uniform sampler2D uShadowMap;\n" "float shadow;\n" "float pcf(vec4 lpos, float size)\n" - " {\n" + "{\n" + " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" + " float i, j, shadow;\n" + " shadow = 0.0;\n" "#ifndef GL_ES\n" - " vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5;\n" - " float i, j, randx, randy, shadow;\n" - " shadow = 0.0;\n" - " for (i = -4.0; i < 4.0; i++)\n" - " for (j = -4.0; j < 4.0; j++)\n" - " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" - " return shadow / 64.0;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x);\n" "#else\n" - " /*TODO Add algorithm generate shadow*/\n" - " return 1.0;\n" + " const vec4 unpack = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);\n" + " const float bias = 0.00015 /*TODO Optimizate set of offset*/;\n" + " for (i = -4.0; i < 4.0; i++)\n" + " for (j = -4.0; j < 4.0; j++)\n" + " {\n" + " vec4 zvalue = texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size);\n" + " shadow += float(smcoord.z < dot(zvalue, unpack) + bias);\n" + " }\n" "#endif //GL_ES\n" + " return shadow / 64.0;\n" "}\n" "#endif //SHADOWED\n" "#ifdef DIFFUSE\n" diff --git a/src/modules/evas/engines/gl_common/shader_3d/include.shd b/src/modules/evas/engines/gl_common/shader_3d/include.shd index 706def92c4..3b92e827d0 100644 --- a/src/modules/evas/engines/gl_common/shader_3d/include.shd +++ b/src/modules/evas/engines/gl_common/shader_3d/include.shd @@ -42,19 +42,25 @@ varying vec4 vLightPosition; uniform sampler2D uShadowMap; float shadow; float pcf(vec4 lpos, float size) - { +{ + vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5; + float i, j, shadow; + shadow = 0.0; `#ifndef GL_ES' - vec3 smcoord = lpos.xyz / lpos.w * 0.5 + 0.5; - float i, j, randx, randy, shadow; - shadow = 0.0; - for (i = -4.0; i < 4.0; i++) - for (j = -4.0; j < 4.0; j++) - shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x); - return shadow / 64.0; + for (i = -4.0; i < 4.0; i++) + for (j = -4.0; j < 4.0; j++) + shadow += float(smcoord.z <= texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size).x); `#else' - /*TODO Add algorithm generate shadow*/ - return 1.0; + const vec4 unpack = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0); + const float bias = 0.00015 /*TODO Optimizate set of offset*/; + for (i = -4.0; i < 4.0; i++) + for (j = -4.0; j < 4.0; j++) + { + vec4 zvalue = texture2D(uShadowMap, smcoord.xy + vec2(i / 8.0, j / 8.0) * size); + shadow += float(smcoord.z < dot(zvalue, unpack) + bias); + } `#endif //GL_ES' + return shadow / 64.0; } #endif //SHADOWED') diff --git a/src/modules/evas/engines/gl_common/shader_3d/shadow_map_frag.shd b/src/modules/evas/engines/gl_common/shader_3d/shadow_map_frag.shd index cfdd298db4..bdb16e59bb 100644 --- a/src/modules/evas/engines/gl_common/shader_3d/shadow_map_frag.shd +++ b/src/modules/evas/engines/gl_common/shader_3d/shadow_map_frag.shd @@ -1,3 +1,4 @@ +varying vec4 shadowmapposition; #ifdef ALPHA_TEST_ENABLED FRAGMENT_SHADER_USE_TEX_COORD FRAGMENT_SHADER_USE_ALPHA_TEST_GLES @@ -39,7 +40,7 @@ void main() { #else #ifdef DIFFUSE_TEXTURE - color = texture2D(uTextureDiffuse0, vec2(Tex0CoordDiffuse)) ; + color = texture2D(uTextureDiffuse0, vec2(Tex0CoordDiffuse)); #else color = vec4(1); #endif //DIFFUSE_TEXTURE @@ -49,5 +50,16 @@ void main() { FRAGMENT_SHADER_ALPHA_TEST_GLES_APPLY(color) #endif //ALPHA_TEST_ENABLED +#ifndef GL_ES gl_FragColor.r = gl_FragCoord.z; +#else + const vec4 pack = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); + const vec4 mask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); + vec4 depthcolor; + float normdist = shadowmapposition.z / shadowmapposition.w; + normdist = ((normdist + 1.0) / 2.0); + depthcolor = vec4(fract(pack * normdist)); + depthcolor -= depthcolor.xxyz * mask; + gl_FragColor = depthcolor; +#endif } diff --git a/src/modules/evas/engines/gl_common/shader_3d/shadow_map_vert.shd b/src/modules/evas/engines/gl_common/shader_3d/shadow_map_vert.shd index 095fc9c9c9..66ce4f3d62 100644 --- a/src/modules/evas/engines/gl_common/shader_3d/shadow_map_vert.shd +++ b/src/modules/evas/engines/gl_common/shader_3d/shadow_map_vert.shd @@ -1,5 +1,5 @@ uniform mat4 uMatrixMvp; - +varying vec4 shadowmapposition; VERTEX_SHADER_USE_POSITION #ifdef ALPHA_TEST_ENABLED @@ -17,4 +17,5 @@ VERTEX_SHADER_POSITION #endif //ALPHA_TEST_ENABLED gl_Position = uMatrixMvp * position; + shadowmapposition = gl_Position; }