#version 150 #extension GL_ARB_gpu_shader5: enable #define MAX_LIGHTS 16 #define INVOCATIONS 1 #ifdef INVOCATIONS #define MAX_VERTICES 18 layout(triangles, invocations = MAX_LIGHTS) in; layout(triangle_strip, max_vertices = MAX_VERTICES) out; #else #define MAX_VERTICES 240 // 16*5*3 - no triangle can make it into all 6 faces, right? layout(triangles) in; layout(triangle_strip, max_vertices = MAX_VERTICES) out; #endif in VertexData { vec4 position; vec2 texCoord; float culled[MAX_LIGHTS*6]; } vert[]; out FragmentData { vec2 texCoord; } frag; uniform int u_lightCount; uniform float u_shadowTexelStep; uniform float u_lightRadius[MAX_LIGHTS]; uniform vec3 u_lightOrigin[MAX_LIGHTS]; uniform vec4 u_shadowRect[MAX_LIGHTS]; const mat3 cubicTransformations[6] = mat3[6] ( mat3( 0, 0, -1, 0, -1, 0, -1, 0, 0 ), mat3( 0, 0, 1, 0, -1, 0, 1, 0, 0 ), mat3( 1, 0, 0, 0, 0, -1, 0, 1, 0 ), mat3( 1, 0, 0, 0, 0, 1, 0, -1, 0 ), mat3( 1, 0, 0, 0, -1, 0, 0, 0, -1 ), mat3( -1, 0, 0, 0, -1, 0, 0, 0, 1 ) ); const float clipEps = 0e-2; const vec4 ClipPlanes[4] = vec4[4] ( vec4(1, 0, -1, clipEps), vec4(-1, 0, -1, clipEps), vec4(0, 1, -1, clipEps), vec4(0, -1, -1, clipEps) ); // I suspect that the right way is to cook the projection matrices for each page on CPU and pass as uniforms to avoid this mess vec2 ShadowAtlasForVector(vec3 v, int lightNo, int targetFace) { vec2 shadow2d = v.xy*.5 + vec2(-v.z)*.5; float pageSize = u_shadowRect[lightNo].w + u_shadowTexelStep; vec2 pageOrigin = u_shadowRect[lightNo].xy - vec2(u_shadowTexelStep / 2); pageOrigin.x += pageSize * targetFace; pageOrigin = pageOrigin * 2 - vec2(1); shadow2d *= pageSize * 2;// + pageOrigin * -v.z; shadow2d.xy += -v.z * pageOrigin; return vec2(shadow2d); } #define CULL_BACK_FACES 1 //#define CULL_TRIANGLES_IN 1 #define CULL_TRIANGLES_OUT 1 void OneLight(int lightNo) { #ifdef CULL_TRIANGLES_IN for(int j = 6*lightNo; j < 6*lightNo+6; j++) if(vert[0].culled[j] + vert[1].culled[j] + vert[2].culled[j] == 3) return; #endif vec3 vertsInLightSpace[3]; for(int i = 0; i < 3; i++) vertsInLightSpace[i] = vert[i].position.xyz - u_lightOrigin[lightNo]; // cull back faces #ifdef CULL_BACK_FACES vec3 edge1 = vertsInLightSpace[1].xyz - vertsInLightSpace[0].xyz; vec3 edge2 = vertsInLightSpace[2].xyz - vertsInLightSpace[0].xyz; if(dot(cross(edge1, edge2), vertsInLightSpace[0].xyz) < 0) return; #endif for(int j = 0; j < 6; j++) { // if(j!=2) continue; // debug - single face vec4 clip[3], vertsInCubeFaceSpace[3]; vec4 clipCount = vec4(0); for(int i = 0; i < 3; i++) { vec3 vertInLightSpace = vertsInLightSpace[i]; vec4 vertInCubeFaceSpace = vec4(cubicTransformations[j] * vertInLightSpace, 1); //vertInCubeFaceSpace = vec4(vertInLightSpace, 1); vertsInCubeFaceSpace[i] = vertInCubeFaceSpace; for(int k=0; k<4; k++) { clip[i][k] = dot(vertInCubeFaceSpace, ClipPlanes[k]); clipCount[k] += float(clip[i][k] < 0); } } #ifdef CULL_TRIANGLES_OUT if(max(max(clipCount[0], clipCount[1]), max(clipCount[2], clipCount[3])) == 3) continue; #endif for(int i = 0; i < 3; i++) { vec4 vertInCubeFaceSpace = vertsInCubeFaceSpace[i]; gl_Position.xy = ShadowAtlasForVector(vertInCubeFaceSpace.xyz, lightNo, j); gl_Position.z = (-vertInCubeFaceSpace.z - 2*u_lightRadius[lightNo]); gl_Position.w = -vertInCubeFaceSpace.z; frag.texCoord = vert[i].texCoord; // clip the triangle to the atlas page gl_ClipDistance[0] = clip[i].x; gl_ClipDistance[1] = clip[i].y; gl_ClipDistance[2] = clip[i].z; gl_ClipDistance[3] = clip[i].w; //gl_ClipDistance[0] = gl_ClipDistance[1] = gl_ClipDistance[2] = gl_ClipDistance[3] = 1; // debug - don't clip to atlas pages EmitVertex(); } EndPrimitive(); } } void main() { #ifdef INVOCATIONS int lightNo = gl_InvocationID; if(lightNo >= u_lightCount) return; // if(lightNo!=0) return; // debug - single light OneLight(lightNo); #else for (int lightNo = 0; lightNo < u_lightCount; lightNo++) { // if(lightNo!=0) return; // debug - single light OneLight(lightNo); } #endif }