The whole project is here: https://developer.apple.com/library/mac/#samplecode/GLEssentials/Introduction/Intro.html

The rendering flow is like this:

  1. Render the character upside down to a texture.
  2. Render the character normally.
  3. Render the flat reflection surface below the character using the texture from step 1.

I don't understand the texture coordinate calculation in the fragment shader, specifically the block marked with ??? below:

precision highp float;

// Color of tint to apply (blue)
const vec4 tintColor = vec4(0.0, 0.0, 1.0, 1.0);

// Amount of tint to apply
const float tintFactor = 0.2;

varying vec3  varNormal;
varying vec3  varEyeDir;

uniform sampler2D diffuseTexture;

void main (void)
    // Compute reflection vector
    vec3 reflectDir = reflect(varEyeDir, varNormal);

    // Compute altitude and azimuth angles
    vec2 texcoord;

    texcoord.t = normalize(reflectDir).y;
    // ???????????????????????????????????????????????
    reflectDir.y = 0.0; // Why clear reflectDir.y?
    texcoord.s = normalize(reflectDir).x * 0.5; // Why times 0.5?

    // Translate index values into proper range
    if (reflectDir.z >= 0.0) {
        texcoord = (texcoord + 1.0) * 0.5;
    } else {
        texcoord.t = (texcoord.t + 1.0) * 0.5;
        texcoord.s = (-texcoord.s) * 0.5 + 1.0; // Why translation of s is like this, different from t?
    // ???????????????????????????????????????????????

    // Do a lookup into the environment map.
    vec4 texColor = texture2D(diffuseTexture, texcoord);

    // Add some blue tint to the image so it looks more like a mirror or glass
    gl_FragColor = mix(texColor, tintColor, tintFactor);

Thanks in advance to anyone who can demystify this for me!


They seem to be using a kind of quasi-polar coordinates for the texture. The code you marked off is concerned with measuring the relationship of reflectDir to the coordinate axes and mapping that into texture coordinates.

First, it takes reflectDir.y to be the t component of the texture coordinate. In both cases of the if-statement, it adds 1 to this and multiplies by 0.5 in order to map it from the [-1, 1] interval to [0, 1].

It then zeroes out the reflectDir.y and renormalizes, in order to look only at the horizontal components of the vector in what follows. For the horizontal component there are two cases, based on whether z is positive or negative. In either case, it uses reflectDir.x for the s component of the texture coordinate, but mapped in a different way: for positive z, it maps [-1, 1] to [.25, .75], and for negative z it maps [-1, 1] to [1.25, .75]. After texture wrapping, the latter case will end up occupying the intervals [.75, 1.0] and [0, .25], so it fills the parts of the texture not already occupied by the positive-z case.

The net result is that you have something a kind of like a latitude-longitude map, but much cheaper to evaluate since it uses only normalizes, dot products, and linear math rather than inverse-trig functions. Like a lat-long map, it has the +Y axis at the top and the -Y axis at the bottom, with the entire 360 degrees of the horizon laid out along a horizontal line through the center of the texture. It will probably appear more distorted than a lat-long map, though; it'll be more pinched at the poles and it will have pinching also on the vertical lines at s = 0.25 and 0.75, where the positive/negative z parts come together.

| improve this answer | |
  • \$\begingroup\$ Thanks. I found I missed a very important detail before, i.e., the reflection surface's normal is set to point to +Z instead of +Y, and the latter is what I thought naturally as the case. After figuring this out, some confusions are easily solved. However, as to the different treatments of texcoord.s for positive and negative reflectDir.z, I think they are very arbitrary and only make sense for this specific scene and are not general math formulae, right? In fact, [1.25, .75] of texcoord.s does not make much sense because it uses GL_CLAMP_TO_EDGE and reflectDir.z is never < 0 here. \$\endgroup\$ – an0 Mar 23 '13 at 16:00
  • \$\begingroup\$ @an0 Well, it could be used as a general environment mapping - the math works out, although this mapping would seem to have serious issues with texture distortion. The different treatments for positive and negative z are just to get the whole 360 degrees of the environment into the texture. The use of GL_CLAMP_TO_EDGE may just be a mistake on the part of the coder of the sample. And you may be right that the z < 0 part is not necessary here, but the coder may have forgotten to optimize it or chosen not to, to leave the code more generalized. \$\endgroup\$ – Nathan Reed Mar 23 '13 at 16:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.