15

I want to draw a sphere, I know how to do it in OpenGL using calls such as glBegin() & glEnd().

But there is nothing in ES.

Suggestions/Tutorial links?

user1118321
  • 25,567
  • 4
  • 55
  • 86
HungryCoder
  • 1,029
  • 2
  • 10
  • 16
  • check this question out - http://stackoverflow.com/questions/6072308/problem-drawing-a-sphere-in-opengl-es –  May 07 '12 at 19:48
  • As the URL already says: http://www.learnopengles.com/android-lesson-one-getting-started/ –  May 08 '12 at 12:03

1 Answers1

47

Since you've tagged this with OpenGL ES 2.0, let me suggest an alternative approach for creating smooth spheres, and that's to draw them as raytraced impostors. Rather than calculate the many vertices you'll need to replicate a smooth sphere, you can take advantage of the fact that a sphere looks pretty much the same from any angle.

To do this, you employ a process like the following:

Sphere impostor generation

You send four vertices that represent two triangles to a vertex shader, which then displaces them to create a square that always faces the user. Within that square, you use a fragment shader to raster over each pixel and provide the color that a sphere would have at that point if you were viewing it through this square window.

The advantage of this approach is that the sphere is as smooth as the resolution of your display supports, and the sphere will easily scale from small to large without requiring any recalculation of your geometry. It does shift the burden for rendering from the vertex processor to the fragment processor, but for a single sphere that's not much of a problem on the OpenGL ES 2.0 devices I've worked with.

I use this technique in this iOS application, for which the source code is available on that page, and talk about it a little more here. A simplified version of the vertex shader I use looks something like this:

attribute vec4 position;
attribute vec4 inputImpostorSpaceCoordinate;

varying mediump vec2 impostorSpaceCoordinate;
varying mediump vec3 normalizedViewCoordinate;

uniform mat4 modelViewProjMatrix;
uniform mediump mat4 orthographicMatrix;
uniform mediump float sphereRadius;

void main()
{
    vec4 transformedPosition;
    transformedPosition = modelViewProjMatrix * position;
    impostorSpaceCoordinate = inputImpostorSpaceCoordinate.xy;

    transformedPosition.xy = transformedPosition.xy + inputImpostorSpaceCoordinate.xy * vec2(sphereRadius);
    transformedPosition = transformedPosition * orthographicMatrix;

    normalizedViewCoordinate = (transformedPosition.xyz + 1.0) / 2.0;
    gl_Position = transformedPosition;
}

and the simplified fragment shader is this:

precision mediump float;

uniform vec3 lightPosition;
uniform vec3 sphereColor;
uniform mediump float sphereRadius;

uniform sampler2D depthTexture;

varying mediump vec2 impostorSpaceCoordinate;
varying mediump vec3 normalizedViewCoordinate;

const mediump vec3 oneVector = vec3(1.0, 1.0, 1.0);

void main()
{
    float distanceFromCenter = length(impostorSpaceCoordinate);

    // Establish the visual bounds of the sphere
    if (distanceFromCenter > 1.0)
    {
        discard;
    }

    float normalizedDepth = sqrt(1.0 - distanceFromCenter * distanceFromCenter);

    // Current depth
    float depthOfFragment = sphereRadius * 0.5 * normalizedDepth;
    //        float currentDepthValue = normalizedViewCoordinate.z - depthOfFragment - 0.0025;
    float currentDepthValue = (normalizedViewCoordinate.z - depthOfFragment - 0.0025);

    // Calculate the lighting normal for the sphere
    vec3 normal = vec3(impostorSpaceCoordinate, normalizedDepth);

    vec3 finalSphereColor = sphereColor;

    // ambient
    float lightingIntensity = 0.3 + 0.7 * clamp(dot(lightPosition, normal), 0.0, 1.0);
    finalSphereColor *= lightingIntensity;

    // Per fragment specular lighting
    lightingIntensity  = clamp(dot(lightPosition, normal), 0.0, 1.0);
    lightingIntensity  = pow(lightingIntensity, 60.0);
    finalSphereColor += vec3(0.4, 0.4, 0.4) * lightingIntensity;

    gl_FragColor = vec4(finalSphereColor, 1.0);
}

The current optimized versions of these shaders are a little harder to follow, and I also use ambient occlusion lighting, which is not present with these. Also not shown is texturing of this sphere, which can be done with a proper mapping function to translate between sphere surface coordinates and a rectangular texture. This is how I provide precalculated ambient occlusion values for the surfaces of my spheres.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • I used billboarding too http://williamedwardscoder.tumblr.com/post/13270747573/when-playing-with-game-engine-ideas-i-struggled ; my key input is that for best results you have to draw the edges in a second pass. I abandoned the technique when moving to OpenGLES 2 / webGL as you can't set the depth per fragment any more :( – Will Sep 23 '12 at 11:44
  • What is the `orthographicMatrix` in Your code? Is it a matrix that turns a square to always face user? – Szał Pał Mar 18 '15 at 09:41
  • @SzałPał - It's intended to correct for the non-square shape of the rendering view. – Brad Larson Mar 18 '15 at 14:38
  • can you explain what is `inputImpostorSpaceCoordinate` and how `distanceFromCentre` of a fragment is equal to `length(inputImpostorSpaceCoordinate.xy)` ; – Nishant Jun 23 '15 at 14:07
  • 1
    @Nishant - `inputImpostorSpaceCoordinate` are the values in the four edges of the box in my diagram above. Four vertices are fed into this, each with the same X, Y, Z coordinate, but with four different impostor space coordinates ((-1,1), (1, -1), (-1, -1), (1, 1)). The impostor space coordinates are used to transform the vertices to the appropriate position in the billboard. By taking the length of an interpolated version of those coordinates, you get a radius from the center of the billboard. If that is less than a certain value, the pixel is within the circle you're trying to draw. – Brad Larson Jun 23 '15 at 14:23