/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 20072011 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef __GNUC__
    #define MAYBE_UNUSED __attribute__((used))
#else
    #define MAYBE_UNUSED
#endif

static const char *sh_paraboloid_vert MAYBE_UNUSED =
    "void main() {\n"
    "   gl_Position = gl_ModelViewMatrix * gl_Vertex;\n"
    "}\n";

static const char *sh_paraboloid_geom MAYBE_UNUSED =
    "#extension GL_EXT_geometry_shader4 : enable\n"
    "\n"
    "/* -> Fragment shader (triangle data, projected position) */\n"
    "varying vec3 p0, edge1, edge2;\n"
    "varying vec2 pos;\n"
    "\n"
    "vec2 project(vec3 p) {\n"
    "   return p.xy / (length(p) + p.z);\n"
    "}\n"
    "\n"
    "float cosAlpha(vec3 v1, vec3 v2) {\n"
    "   vec3 v3 = cross(v1, v2);\n"
    "   return v3.z / length(v3);\n"
    "}\n"
    "\n"
    "void main() {\n"
    "   vec3 cur = gl_PositionIn[0].xyz;\n"
    "   cur.z = -cur.z;\n"
    "\n"
    "   bool curIsInside = cur.z > 0.0;\n"
    "   vec3 vertices[4];\n"
    "   int vCount = 0;\n"
    "\n"
    "   /* Clip the geometry against the forward hemisphere */\n"
    "   for (int i=0; i<3; ++i) {\n"
    "       vec3 next = gl_PositionIn[i < 2 ? i+1 : 0].xyz;\n"
    "       next.z = -next.z;\n"
    "       bool nextIsInside = next.z > 0.0;\n"
    "\n"
    "       if (curIsInside && nextIsInside) {\n"
    "           vertices[vCount++] = next;\n"
    "       } else if (curIsInside != nextIsInside) {\n"
    "           float t = -cur.z / (next.z-cur.z);\n"
    "\n"
    "           vertices[vCount++] = \n"
    "               vec3((1-t)*cur.xy + t*next.xy, 0.0);\n"
    "\n"
    "           if (nextIsInside)\n"
    "               vertices[vCount++] = next;\n"
    "       }\n"
    "\n"
    "       cur = next;\n"
    "       curIsInside = nextIsInside;\n"
    "   }\n"
    "\n"
    "   if (vCount < 3)\n"
    "       return;\n"
    "\n"
    "   /* Transform into projection space */\n"
    "   vec2 projected[4];\n"
    "   for (int i=0; i<vCount; ++i)\n"
    "       projected[i] = project(vertices[i]);\n"
    "\n"
    "   /* Find the longest edge and construct an OBB rotation matrix */\n"
    "   float largest = 0.0;\n"
    "   mat2 rot;\n"
    "   rot[0] = vec2(0.0);\n"
    "\n"
    "   for (int i=0; i<vCount; i++) {\n"
    "       int next = (i < vCount-1) ? i+1 : 0;\n"
    "       vec2 p0 = projected[i],\n"
    "            p1 = projected[next],\n"
    "            d  = p1-p0;\n"
    "\n"
    "       float l2 = dot(d, d);\n"
    "       if (l2 > largest) {\n"
    "           largest = l2;\n"
    "           rot[0] = d;\n"
    "       }\n"
    "   }\n"
    "   rot[0] /= sqrt(largest);\n"
    "   rot[1]  = vec2(rot[0].y, -rot[0].x);\n"
    "\n"
    "   /* Find a bounding rectangle for each edge and expand the OBB */\n"
    "   vec2 bmax = vec2(-1.0, -1.0), bmin = vec2(1.0, 1.0);\n"
    "   for (int i=0; i<vCount; i++) {\n"
    "       int next = (i < vCount-1) ? i+1 : 0;\n"
    "       vec2 a  = projected[i],\n"
    "            b  = projected[next],\n"
    "            p0 = a * rot,\n"
    "            p1 = b * rot,\n"
    "            p2 = p0, p3 = p1;\n"
    "\n"
    "       float ca = cosAlpha(vertices[i], vertices[next]);\n"
    "       if (ca != 0.0) {\n"
    "           vec2 d = vec2(b.y-a.y, a.x-b.x) * rot;\n"
    "           float l = length(d), ica = 1.0 / ca;\n"
    "           d *= (ica - sign(ica) * sqrt(ica*ica - 0.25 * l*l)) / l;\n"
    "           p2 += d; p3 += d;\n"
    "       }\n"
    "\n"
    "       bmax = max(bmax, max(max(p0, p1), max(p2, p3)));\n"
    "       bmin = min(bmin, min(min(p0, p1), min(p2, p3)));\n"
    "   }\n"
    "\n"
    "   /* Plumb triangle data to the fragment shader */\n"
    "   p0 = gl_PositionIn[0].xyz;\n"
    "   edge1 = gl_PositionIn[1].xyz - gl_PositionIn[0].xyz;\n"
    "   edge2 = gl_PositionIn[2].xyz - gl_PositionIn[0].xyz;\n"
    "   p0.z = -p0.z; edge1.z = -edge1.z; edge2.z = -edge2.z;\n"
    "\n"
    "   /* Emit a quad */\n"
    "   pos = rot[0] * bmin.x + rot[1] * bmin.y;\n"
    "   gl_Position = vec4(pos, 0.5, 1.0);\n"
    "   EmitVertex();\n"
    "   pos = rot[0] * bmin.x + rot[1] * bmax.y;\n"
    "   gl_Position = vec4(pos, 0.5, 1.0);\n"
    "   EmitVertex();\n"
    "   pos = rot[0] * bmax.x + rot[1] * bmin.y;\n"
    "   gl_Position = vec4(pos, 0.5, 1.0);\n"
    "   EmitVertex();\n"
    "   pos = rot[0] * bmax.x + rot[1] * bmax.y;\n"
    "   gl_Position = vec4(pos, 0.5, 1.0);\n"
    "   EmitVertex();\n"
    "   EndPrimitive();\n"
    "}\n";

static const char *sh_paraboloid_frag MAYBE_UNUSED =
    "/* Triangle data (from geometry shader) */\n"
    "varying vec3 p0, edge1, edge2;\n"
    "\n"
    "/* Projection space position */\n"
    "varying vec2 pos;\n"
    "\n"
    "/* Depth range */\n"
    "uniform float minDepth;\n"
    "uniform float invDepthRange;\n"
    "\n"
    "void main() {\n"
    "   float r2 = dot(pos, pos);\n"
    "   if (r2 > 1.0)\n"
    "       discard;\n"
    "\n"
    "   /* Turn into a direction */\n"
    "   float cosTheta = (1.0-r2) / (1.0+r2);\n"
    "   float factor = sqrt((1.0-cosTheta*cosTheta) / r2);\n"
    "   vec3 d = vec3(pos * factor, cosTheta);\n"
    "\n"
    "   vec3 pvec = cross(d, edge2);\n"
    "   float det = dot(edge1, pvec);\n"
    "   if (det == 0.0)\n"
    "       discard;\n"
    "\n"
    "   float inv_det = 1.0 / det;\n"
    "   float u = -dot(p0, pvec) * inv_det;\n"
    "\n"
    "   if (u < 0.0 || u > 1.0)\n"
    "       discard;\n"
    "\n"
    "   vec3 qvec = cross(p0, edge1);\n"
    "   float v = -dot(d, qvec) * inv_det;\n"
    "   if (v < 0.0 || u+v > 1.0)\n"
    "       discard;\n"
    "\n"
    "   float t = -dot(edge2, qvec) * inv_det - minDepth;\n"
    "   if (!(t > 0.0)) // catch NaNs as well \n"
    "       discard;\n"
    "   \n"
    "   float depth = t * invDepthRange;\n"
    "\n"
    "   float dx = dFdx(depth), dy = dFdy(depth);\n"
    "   gl_FragDepth = depth + sqrt(dx*dx+dy*dy);\n"
    "}\n";

static const char *sh_directional_vert MAYBE_UNUSED =
    "void main() {\n"
    "   gl_Position = ftransform();\n"
    "}\n";

static const char *sh_directional_frag MAYBE_UNUSED =
    "void main() {\n"
    "   float depth = gl_FragCoord.z,\n"
    "         dx = dFdx(depth), dy = dFdy(depth);\n"
    "   gl_FragDepth = depth + sqrt(dx*dx+dy*dy);\n"
    "}\n";

static const char *sh_cube_6pass_vert MAYBE_UNUSED =
    "/* Transformation matrix for the current cube map face */\n"
    "uniform mat4 transform;\n"
    "\n"
    "/* Depth projection axis */\n"
    "uniform vec4 projDir;\n"
    "\n"
    "/* -> Fragment shader */\n"
    "varying float depth;\n"
    "\n"
    "void main() {\n"
    "   vec4 pos = gl_ModelViewMatrix * gl_Vertex;\n"
    "   gl_Position = transform * pos;\n"
    "   depth = dot(projDir, pos);\n"
    "}\n";

static const char *sh_cube_6pass_frag MAYBE_UNUSED =
    "/* Linear depth from the vertex shader */\n"
    "varying float depth;\n"
    "\n"
    "void main() {\n"
    "   float dx = dFdx(depth), dy = dFdy(depth);\n"
    "\n"
    "   #ifndef DEPTH_CUBEMAPS_UNSUPPORTED\n"
    "       gl_FragDepth = depth + sqrt(dx*dx+dy*dy);\n"
    "   #else\n"
    "       gl_FragDepth = gl_FragColor.r = depth + sqrt(dx*dx+dy*dy);\n"
    "   #endif\n"
    "}\n";

static const char *sh_cube_1pass_vert MAYBE_UNUSED =
    "void main() {\n"
    "   gl_Position = gl_ModelViewMatrix * gl_Vertex;\n"
    "}\n";

static const char *sh_cube_1pass_geom MAYBE_UNUSED =
    "#extension GL_EXT_geometry_shader4 : enable\n"
    "\n"
    "/* Transformation matrix for each cube map face */\n"
    "uniform mat4 transform[6];\n"
    "\n"
    "/* Depth projection axis */\n"
    "uniform vec4 projDir[6];\n"
    "\n"
    "/* -> Fragment shader */\n"
    "varying float depth;\n"
    "\n"
    "void main() {\n"
    "   depth = 0.0; // avoid an (incorrect?) compiler warning\n"
    "\n"
    "   /* Replicate the geometry six times and rasterize to each cube map layer */\n"
    "   for (int side = 0; side < 6; side++) {\n"
    "       gl_Layer = side;\n"
    "       for (int i = 0; i < gl_VerticesIn; i++) {\n"
    "           gl_Position = transform[side] * gl_PositionIn[i];\n"
    "           depth = dot(projDir[side], gl_PositionIn[i]);\n"
    "           EmitVertex();\n"
    "       }\n"
    "       EndPrimitive();\n"
    "   }\n"
    "}\n";

static const char *sh_cube_1pass_frag MAYBE_UNUSED =
    "/* Linear depth from the vertex shader */\n"
    "varying float depth;\n"
    "\n"
    "void main() {\n"
    "   float dx = dFdx(depth), dy = dFdy(depth);\n"
    "   gl_FragDepth = depth + sqrt(dx*dx+dy*dy);\n"
    "}\n";

static const char *sh_hemicube_1pass_vert MAYBE_UNUSED =
    "void main() {\n"
    "   gl_Position = gl_ModelViewMatrix * gl_Vertex;\n"
    "}\n";

static const char *sh_hemicube_1pass_geom MAYBE_UNUSED =
    "#extension GL_EXT_geometry_shader4 : enable\n"
    "\n"
    "/* Transformation matrix for each cube map face */\n"
    "uniform mat4 transform[5];\n"
    "\n"
    "/* Depth projection axis */\n"
    "uniform vec4 projDir[5];\n"
    "\n"
    "/* -> Fragment shader */\n"
    "varying float depth;\n"
    "\n"
    "void main() {\n"
    "   depth = 0.0; // avoid an (incorrect?) compiler warning\n"
    "\n"
    "   /* Replicate the geometry six times and rasterize to each cube map layer */\n"
    "   for (int side = 0; side < 5; side++) {\n"
    "       gl_Layer = side != 4 ? side : 5;\n"
    "       for (int i = 0; i < gl_VerticesIn; i++) {\n"
    "           gl_Position = transform[side] * gl_PositionIn[i];\n"
    "           depth = dot(projDir[side], gl_PositionIn[i]);\n"
    "           EmitVertex();\n"
    "       }\n"
    "       EndPrimitive();\n"
    "   }\n"
    "}\n";

static const char *sh_hemicube_1pass_frag MAYBE_UNUSED =
    "/* Linear depth from the vertex shader */\n"
    "varying float depth;\n"
    "\n"
    "void main() {\n"
    "   float dx = dFdx(depth), dy = dFdy(depth);\n"
    "   gl_FragDepth = depth + sqrt(dx*dx+dy*dy);\n"
    "}\n";

static const char *sh_background_vert MAYBE_UNUSED =
    "uniform mat4 clipToWorld;\n"
    "varying vec3 position;\n"
    "\n"
    "void main() {\n"
    "   vec4 pos = ftransform();\n"
    "   gl_Position = pos;\n"
    "\n"
    "   vec4 tmp = clipToWorld * pos;\n"
    "   position = tmp.xyz / tmp.w;\n"
    "}\n"
    "\n";

static const char *sh_background_frag MAYBE_UNUSED =
    "#extension GL_EXT_gpu_shader4 : enable\n"
    "\n"
    "varying vec3 position;\n"
    "\n"
    "#if defined(DIRECTIONAL_CAMERA)\n"
    "   uniform vec3 camDirection;\n"
    "#else\n"
    "   uniform vec3 camPosition;\n"
    "#endif\n"
    "\n"
    "uniform float emitterScale;\n"
    "\n"
    "{{ SUPPLEMENTAL CODE }}\n"
    "\n"
    "void main() {\n"
    "   vec3 result;\n"
    "\n"
    "   #if !defined(DIRECTIONAL_CAMERA)\n"
    "       result = BACKGROUND_EVAL_NAME(normalize(position - camPosition));\n"
    "   #else\n"
    "       result = BACKGROUND_EVAL_NAME(camDirection);\n"
    "   #endif\n"
    "\n"
    "   gl_FragColor = vec4(result * emitterScale, 1.0);\n"
    "}\n";

static const char *sh_unsupported_vert MAYBE_UNUSED =
    "/* Uniform parameters */\n"
    "uniform mat4 instanceTransform;\n"
    "\n"
    "void main() {\n"
    "   vec4 pos = instanceTransform * gl_Vertex;\n"
    "   gl_Position = gl_ModelViewProjectionMatrix * pos;\n"
    "}\n";

static const char *sh_unsupported_frag MAYBE_UNUSED =
    "void main() {\n"
    "   gl_FragColor = vec4(0.0);\n"
    "}\n";

static const char *sh_render_vert MAYBE_UNUSED =
    "/* Uniform parameters */\n"
    "uniform mat4 vplTransform;\n"
    "uniform mat4 instanceTransform;\n"
    "\n"
    "#ifndef FACE_NORMALS\n"
    "   /* -> Fragment program */\n"
    "   varying vec3 posInVPLSpace;\n"
    "   varying vec3 posInWorldSpace;\n"
    "   varying vec2 uv;\n"
    "   varying vec3 normal;\n"
    "#else\n"
    "   /* -> Geometry program */\n"
    "   varying vec3 posInVPLSpace_vertex;\n"
    "   varying vec3 posInWorldSpace_vertex;\n"
    "   varying vec2 uv_vertex;\n"
    "#endif\n"
    "\n"
    "#ifdef ANISOTROPIC\n"
    "   varying vec3 tangent;\n"
    "#endif\n"
    "\n"
    "#ifdef VERTEX_COLORS\n"
    "   #ifndef FACE_NORMALS\n"
    "       varying vec3 vertexColor;\n"
    "   #else\n"
    "       varying vec3 vertexColor_vertex;\n"
    "   #endif\n"
    "#endif\n"
    "\n"
    "void main() {\n"
    "   vec4 pos = instanceTransform * gl_Vertex;\n"
    "   gl_Position = gl_ModelViewProjectionMatrix * pos;\n"
    "\n"
    "   #ifndef FACE_NORMALS\n"
    "       posInWorldSpace = pos.xyz;\n"
    "       posInVPLSpace   = (vplTransform * pos).xyz;\n"
    "       uv = gl_MultiTexCoord0.xy;\n"
    "\n"
    "       /* Multiply by instanceTransform (only rigid transformations allowed) */\n"
    "       normal = (instanceTransform * vec4(gl_Normal, 0.0)).xyz;\n"
    "\n"
    "       #ifdef VERTEX_COLORS\n"
    "           vertexColor = gl_Color.rgb;\n"
    "       #endif\n"
    "   #else\n"
    "       posInWorldSpace_vertex = pos.xyz;\n"
    "       posInVPLSpace_vertex   = (vplTransform * pos).xyz;\n"
    "       uv_vertex = gl_MultiTexCoord0.xy;\n"
    "\n"
    "       #ifdef VERTEX_COLORS\n"
    "           vertexColor_vertex = gl_Color.rgb;\n"
    "       #endif\n"
    "   #endif\n"
    "\n"
    "   #ifdef ANISOTROPIC\n"
    "       tangent = gl_MultiTexCoord1.xyz;\n"
    "   #endif\n"
    "}\n";

static const char *sh_render_geom MAYBE_UNUSED =
    "#extension GL_EXT_geometry_shader4 : enable\n"
    "\n"
    "/* From vertex program */\n"
    "varying in vec3 posInVPLSpace_vertex[3];\n"
    "varying in vec3 posInWorldSpace_vertex[3];\n"
    "varying in vec2 uv_vertex[3];\n"
    "\n"
    "/* To fragment program */\n"
    "varying out vec3 posInVPLSpace;\n"
    "varying out vec3 posInWorldSpace;\n"
    "varying out vec3 normal;\n"
    "varying out vec2 uv;\n"
    "\n"
    "#ifdef VERTEX_COLORS\n"
    "   varying in vec3 vertexColor_vertex[3];\n"
    "   varying out vec3 vertexColor;\n"
    "#endif\n"
    "\n"
    "void main() {\n"
    "   vec3 edge1 = posInWorldSpace_vertex[0]-posInWorldSpace_vertex[1];\n"
    "   vec3 edge2 = posInWorldSpace_vertex[0]-posInWorldSpace_vertex[2];\n"
    "\n"
    "   normal = cross(edge1, edge2);\n"
    "   for (int i=0; i<gl_VerticesIn; ++i) {\n"
    "       gl_Position = gl_PositionIn[i];\n"
    "       posInWorldSpace = posInWorldSpace_vertex[i];\n"
    "       posInVPLSpace = posInVPLSpace_vertex[i];\n"
    "       uv = uv_vertex[i];\n"
    "\n"
    "       #ifdef VERTEX_COLORS\n"
    "           vertexColor = vertexColor_vertex[i];\n"
    "       #endif\n"
    "\n"
    "       EmitVertex();\n"
    "   }\n"
    "   EndPrimitive();\n"
    "}\n";

static const char *sh_render_frag MAYBE_UNUSED =
    "#extension GL_EXT_gpu_shader4 : enable\n"
    "\n"
    "#define EPSILON 0.001\n"
    "\n"
    "/* Some helper functions for BSDF implementations */\n"
    "float cosTheta(vec3 v) { return v.z; }\n"
    "float sinTheta2(vec3 v) { return 1.0-v.z*v.z; }\n"
    "float sinTheta(vec3 v) { return sqrt(max(0.0, sinTheta2(v))); }\n"
    "float tanTheta(vec3 v) { return sinTheta(v)/cosTheta(v); }\n"
    "float sinPhi(vec3 v) { return v.y/sinTheta(v); }\n"
    "float cosPhi(vec3 v) { return v.x/sinTheta(v); }\n"
    "const float pi = 3.141592653589;\n"
    "const float inv_pi = 0.318309886183791;\n"
    "const float inv_twopi = 0.159154943091895;\n"
    "const float inv_fourpi = 0.0795774715459477;\n"
    "\n"
    "/* From the vertex program */\n"
    "varying vec3 posInVPLSpace;\n"
    "varying vec3 posInWorldSpace;\n"
    "varying vec3 normal;\n"
    "varying vec2 uv;\n"
    "\n"
    "#ifdef ANISOTROPIC\n"
    "   varying vec3 tangent;\n"
    "#endif\n"
    "\n"
    "#ifdef VERTEX_COLORS\n"
    "   varying vec3 vertexColor;\n"
    "#endif\n"
    "\n"
    "/* Uniform parameters */\n"
    "uniform mat3 vplFrame;\n"
    "uniform vec3 vplPower;\n"
    "uniform vec2 vplUV;\n"
    "uniform vec3 vplWi;\n"
    "uniform float minDistSqr;\n"
    "uniform float emitterScale;\n"
    "\n"
    "uniform mat4 vplTransform;\n"
    "\n"
    "#ifdef CUBEMAP_VPL\n"
    "   uniform samplerCube shadowMap;\n"
    "#else\n"
    "   uniform sampler2D shadowMap;\n"
    "#endif\n"
    "\n"
    "#ifdef DIRECTIONAL_VPL\n"
    "   uniform vec3 vplDirection;\n"
    "#else\n"
    "   uniform vec3 vplPosition;\n"
    "   uniform vec2 depthRange;\n"
    "#endif\n"
    "\n"
    "#ifdef DIRECTIONAL_CAMERA\n"
    "   uniform vec3 camDirection;\n"
    "#else\n"
    "   uniform vec3 camPosition;\n"
    "#endif\n"
    "\n"
    "#if defined(DIRECTIONAL_VPL)\n"
    "   bool isShadowed(vec3 p) {\n"
    "       p = p * 0.5 + 0.5;\n"
    "       return texture2D(shadowMap, p.xy).r * (1 + EPSILON) < p.z;\n"
    "   }\n"
    "#elif defined(PARABOLOIDAL_VPL)\n"
    "   bool isShadowed(vec3 p) {\n"
    "       float depth = texture2D(shadowMap, (p.xy / (-p.z + length(p))) * 0.5 + 0.5).r;\n"
    "       depth = (depth * (depthRange[1]-depthRange[0]) + depthRange[0]) * (1.0+EPSILON);\n"
    "       return dot(p, p) > depth * depth && p.z < 0.0;\n"
    "   }\n"
    "#elif defined(CUBEMAP_VPL)\n"
    "   bool isShadowed(vec3 d) {\n"
    "       float depth = textureCube(shadowMap, vec3(d.x, -d.y, d.z)).r;\n"
    "       float ref_depth = max(max(abs(d.x), abs(d.y)), abs(d.z));\n"
    "       depth = (depth * (depthRange[1]-depthRange[0]) + depthRange[0]) * (1.0+EPSILON);\n"
    "       return depth < ref_depth;\n"
    "   }\n"
    "#endif\n"
    "\n"
    "{{ SUPPLEMENTAL CODE }}\n"
    "\n"
    "void main() {\n"
    "   /* Set up an ONB */\n"
    "   vec3 N = normalize(normal);\n"
    "   mat3 frame;\n"
    "\n"
    "   #ifdef ANISOTROPIC\n"
    "       /* Use the per-vertex tangent information to construct a frame */\n"
    "\n"
    "       frame[0] = normalize(tangent - dot(tangent, N)*N);\n"
    "   #else\n"
    "       /* The material is isotropic -- any frame will do */\n"
    "\n"
    "       if (abs(N.x) > abs(N.y)) {\n"
    "           float invLen = 1.0 / sqrt(N.x*N.x + N.z*N.z);\n"
    "           frame[0] = vec3(-N.z * invLen, 0.0, N.x * invLen);\n"
    "       } else {\n"
    "           float invLen = 1.0 / sqrt(N.y*N.y + N.z*N.z);\n"
    "           frame[0] = vec3(0.0, -N.z * invLen, N.y * invLen);\n"
    "       }\n"
    "   #endif\n"
    "\n"
    "   frame[1] = cross(N, frame[0]);\n"
    "   frame[2] = N;\n"
    "\n"
    "   /* Compute the incident direction in local coordinates (at the point being rendered) */\n"
    "   vec3 wiWorld;\n"
    "   #ifdef DIRECTIONAL_CAMERA\n"
    "       wiWorld = -camDirection;\n"
    "   #else\n"
    "       wiWorld = normalize(camPosition - posInWorldSpace);\n"
    "   #endif\n"
    "   vec3 wi = wiWorld * frame;\n"
    "\n"
    "   vec3 emission;\n"
    "\n"
    "   #if defined(EMITTER_AREA_EVAL_NAME) && defined(EMITTER_DIR_EVAL_NAME)\n"
    "       emission = EMITTER_AREA_EVAL_NAME(uv) *\n"
    "                   EMITTER_DIR_EVAL_NAME(wi) * emitterScale;\n"
    "   #else\n"
    "       emission = vec3(0.0);\n"
    "   #endif\n"
    "\n"
    "   if (isShadowed(posInVPLSpace)) {\n"
    "       gl_FragColor = vec4(emission, 1.0);\n"
    "       return;\n"
    "   }\n"
    "\n"
    "   /* Compute the outgoing direction in local coordinates (at the point being rendered) */\n"
    "   vec3 woWorld;\n"
    "\n"
    "   #ifdef DIRECTIONAL_VPL\n"
    "       woWorld = -vplDirection;\n"
    "   #else\n"
    "       woWorld = vplPosition - posInWorldSpace;\n"
    "       float distSqr = dot(woWorld, woWorld);\n"
    "       woWorld /= sqrt(distSqr);\n"
    "   #endif\n"
    "   vec3 wo = woWorld * frame;\n"
    "\n"
    "   /* Compute the outgoing direction in local coordinates (at the VPL) */\n"
    "   vec3 vplWo = -(woWorld * vplFrame); /* The parentheses are required or incorrect code is generated on OSX .. */\n"
    "\n"
    "   vec3 result = vplPower;\n"
    "\n"
    "   #ifdef EMITTER_VPL\n"
    "       #ifdef VPL_ON_SURFACE\n"
    "           result *= VPL_EVAL_NAME(vplWo) * cosTheta(vplWo);\n"
    "       #else\n"
    "           result *= VPL_EVAL_NAME(vplWo);\n"
    "       #endif\n"
    "   #else\n"
    "       result *= VPL_EVAL_NAME(vplUV, vplWi, vplWo);\n"
    "   #endif\n"
    "\n"
    "   result *= BSDF_EVAL_NAME(uv, wi, wo);\n"
    "\n"
    "   #ifndef DIRECTIONAL_VPL\n"
    "       result *= (1.0 / max(distSqr, minDistSqr));\n"
    "   #endif\n"
    "\n"
    "   gl_FragColor = vec4(result + emission, 1.0);\n"
    "}\n";

