Drawing a sphere in OpenGL ES
Asked Answered
B

1

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?

Benne answered 7/5, 2012 at 19:43 Comment(2)
check this question out - #6072808Protrusion
As the URL already says: learnopengles.com/android-lesson-one-getting-startedBreather
C
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.

Cyaneous answered 8/5, 2012 at 20:41 Comment(5)
I used billboarding too williamedwardscoder.tumblr.com/post/13270747573/… ; 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 :(Til
What is the orthographicMatrix in Your code? Is it a matrix that turns a square to always face user?Wildlife
@SzałPał - It's intended to correct for the non-square shape of the rendering view.Cyaneous
can you explain what is inputImpostorSpaceCoordinate and how distanceFromCentre of a fragment is equal to length(inputImpostorSpaceCoordinate.xy) ;Ferrel
@Ferrel - 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.Cyaneous

© 2022 - 2024 — McMap. All rights reserved.