Drawing a sphere normal map in the fragment shader

Viewed 2782

I'm trying to draw a simple sphere with normal mapping in the fragment shader with GL_POINTS. At present, I simply draw one point on the screen and apply a fragment shader to "spherify" it.

However, I'm having trouble colouring the sphere correctly (or at least I think I am). It seems that I'm calculating the z correctly but when I apply the 'normal' colours to gl_FragColor it just doesn't look quite right (or is this what one would expect from a normal map?). I'm assuming there is some inconsistency between gl_PointCoord and the fragment coord, but I can't quite figure it out.

Vertex shader

precision mediump float;

attribute vec3 position;

void main() {
  gl_PointSize = 500.0;
  gl_Position = vec4(position.xyz, 1.0);
}

fragment shader

precision mediump float;

void main() {

  float x = gl_PointCoord.x * 2.0 - 1.0;
  float y = gl_PointCoord.y * 2.0 - 1.0;

  float z = sqrt(1.0 - (pow(x, 2.0) + pow(y, 2.0)));

  vec3 position = vec3(x, y, z);

  float mag = dot(position.xy, position.xy);
  if(mag > 1.0) discard;

  vec3 normal = normalize(position);

  gl_FragColor = vec4(normal, 1.0);
}

Actual output:

enter image description here

Expected output:

enter image description here

2 Answers

The color channels are clamped to the range [0, 1]. (0, 0, 0) is black and (1, 1, 1) is completely white.

Since the normal vector is normalized, its component are in the range [-1, 1]. To get the expected result you have to map the normal vector from the range [-1, 1] to [0, 1]:

vec3 normal_col = normalize(position) * 0.5 + 0.5;
gl_FragColor    = vec4(normal_col, 1.0);

If you use the abs value, then a positive and negative value with the same size have the same color representation. The intensity of the color increases with the grad of the value:

vec3 normal_col = abs(normalize(position));
gl_FragColor    = vec4(normal_col, 1.0);

First of all, the normal facing the camera [0,0,-1] should be rgb values: [0.5,0.5,1.0]. You have to rescale things to move those negative values to be between 0 and 1.

Second, the normals of a sphere would not change linearly, but in a sine wave. So you need some trigonometry here. It makes sense to me to to start with the perpendicular normal [0,0,-1] and then then rotate that normal by an angle, because that angle is what changing linearly.

As a result of playing around this I came up with this:

http://glslsandbox.com/e#50268.3

which uses some rotation function from here: https://github.com/yuichiroharai/glsl-y-rotate

Related