[Mono-bugs] [Bug 679896] New: OpenGL lighting reversed on certain devices in a MonoTouch OpenGL ES 1.1 project if GL_CULL_FACE is disabled

bugzilla_noreply at novell.com bugzilla_noreply at novell.com
Tue Mar 15 15:20:11 EDT 2011


https://bugzilla.novell.com/show_bug.cgi?id=679896

https://bugzilla.novell.com/show_bug.cgi?id=679896#c0


           Summary: OpenGL lighting reversed on certain devices in a
                    MonoTouch OpenGL ES 1.1 project if GL_CULL_FACE is
                    disabled
    Classification: Mono
           Product: MonoTouch
           Version: unspecified
          Platform: Other
        OS/Version: Other
            Status: NEW
          Severity: Normal
          Priority: P5 - None
         Component: Class Libraries
        AssignedTo: mono-bugs at lists.ximian.com
        ReportedBy: mike.biz at dussault.org
         QAContact: mono-bugs at lists.ximian.com
          Found By: ---
           Blocker: ---


Description of Problem:

If GL_CULL_FACE is disabled in a MonoTouch project using OpenGL ES 1.1, then
the GL lighting direction will be negated on some of the devices or OS
versions, I'm not sure which. 

The same thing is not true for an xcode project. OpenGL ES 1.1 xcode projects
light consistently across all devices and OS versions that I've tested
regardless of your GL_CULL_FACE setting.


DEVICES THAT IT HAPPENS ON
--------------------------

As compared to the result of identical GL code in a C++ xcode project (which is
consistent across devices regardless of GL_CULL_FACE), here are the devices
that I see inverted lighting happening on in MonoTouch:

iPad running iOS 4.2.1
iOS simulator running iOS 4.2
iPhone4 running iOS 4.2.1

The lighting matches that of the xcode project on my iPhone 3G running iOS
3.1.2.


THE FIX
-------

The only way I was able to correct this inconsistency across devices was to
enable GL_CULL_FACE.





SAMPLE C# CODE
--------------

Here is some C# code that you can run each frame in a MonoTouch OpenGL ES 1.1
project to show the problem. The main function to call is GLRenderTest()

    public struct Vector3
    {
        public Vector3( float in_x, float in_y, float in_z )
        {
            X = in_x;
            Y = in_y;
            Z = in_z;
        }

        public static Vector3 operator*( Vector3 a, float scale )
        {
            return new Vector3( a.X * scale, a.Y * scale, a.Z * scale );
        }

        public static Vector3 operator+( Vector3 a, Vector3 b )
        {
            return new Vector3( a.X + b.X, a.Y + b.Y, a.Z + b.Z );
        }
    }

    public struct Matrix
    {
        public Matrix( 
            float in11, float in12, float in13, float in14,
            float in21, float in22, float in23, float in24,
            float in31, float in32, float in33, float in34,
            float in41, float in42, float in43, float in44 )
        {
            M11 = in11;  M12 = in12;  M13 = in13;  M14 = in14;
            M21 = in21;  M22 = in22;  M23 = in23;  M24 = in24;
            M31 = in31;  M32 = in32;  M33 = in33;  M34 = in34;
            M41 = in41;  M42 = in42;  M43 = in43;  M44 = in44;
        }

        public static Matrix CreatePerspectiveFieldOfView( float fieldOfViewY,
float aspectRatio, float nearPlaneDist, float farPlaneDist )
        {
            float yScale = 1.0f / (float)Math.Tan( fieldOfViewY * 0.5f );
            float xScale = yScale / aspectRatio;

            float zScale = farPlaneDist / ( nearPlaneDist - farPlaneDist );

            return new Matrix(
                xScale,            0,            0,            0,
                0,                yScale,        0,            0,
                0,                0,            zScale,        -1,
                0,                0,            nearPlaneDist*zScale,    0 );
        }

        public static float[] ToTemporaryGlMatrixFloatArray( Matrix mat )
        {
            m_TempGLMatrix[0]  = mat.M11;    m_TempGLMatrix[1]  = mat.M12;   
m_TempGLMatrix[2]  = mat.M13;    m_TempGLMatrix[3]  = mat.M14;
            m_TempGLMatrix[4]  = mat.M21;    m_TempGLMatrix[5]  = mat.M22;   
m_TempGLMatrix[6]  = mat.M23;    m_TempGLMatrix[7]  = mat.M24;
            m_TempGLMatrix[8]  = mat.M31;    m_TempGLMatrix[9]  = mat.M32;   
m_TempGLMatrix[10] = mat.M33;    m_TempGLMatrix[11] = mat.M34;
            m_TempGLMatrix[12] = mat.M41;    m_TempGLMatrix[13] = mat.M42;   
m_TempGLMatrix[14] = mat.M43;    m_TempGLMatrix[15] = mat.M44;
            return m_TempGLMatrix;
        }
    }

    void GLRenderTest()
    {
        float flElapsed = (float)( DateTime.Now - m_SplashScreenStartTime
).TotalSeconds;

        Matrix mProjection = Matrix.CreatePerspectiveFieldOfView(
(float)Math.PI / 2, 1, 10, 1000 );

        Vector3 vLightDir = new Vector3(1,0,0);//-( new Vector3( 9, 4, 1 ) );
        vLightDir.Normalize();

        RenderAllManual( flElapsed, mProjection, vLightDir );

        // Draw some stuff.
        Vector3 vSphereCenter = new Vector3( 0, 0, -20 );
        float flRadius = 10;
        int nSubdivsX = 10;
        int nSubdivsY = 10;
        float flXAngleOffset = flElapsed / 4;

        DrawSphere( vSphereCenter, flRadius, nSubdivsX, nSubdivsY,
flXAngleOffset );
        DrawSphere( new Vector3(12,12,-20), flRadius/4, nSubdivsX, nSubdivsY,
flXAngleOffset );
    }


    void RenderAllManual( float flElapsed, Matrix mProjection, Vector3
vLightDir )
    {
        int nResolutionX = 320;
        int nResolutionY = 480;

        // Clear.
        GL.ClearColor( 0, 0, 0, 1 );
        GL.ClearDepth( 1 );
        GL.Clear( (uint)( All.ColorBufferBit | All.DepthBufferBit ) );

        // Setup viewport and other states.
        GL.Viewport( 10, 10, nResolutionX - 20, nResolutionY - 20 );

        //*****************
        //*****************
        //*****************
        GL.Disable( All.CullFace ); //***************** THE CULPRIT HERE
        //*****************
        //*****************
        //*****************

        GL.BlendFunc( All.One, All.Zero );

        GL.Enable( All.DepthTest );
        GL.DepthFunc( All.Lequal );
        GL.DepthMask( true );

        GL.MatrixMode( All.Modelview );
        GL.LoadIdentity();

        GL.MatrixMode( All.Projection );
        GL.LoadMatrix( Matrix.ToTemporaryGlMatrixFloatArray( mProjection ) );

        GL.Disable( All.Texture2D );

        // (usually one-time lighting init)
        GL.Enable( All.ColorMaterial );

        GL.ShadeModel( All.Smooth );

        // GL's default ambient lighting is ( 0.2, 0.2, 0.2, 1.0 ). Turn it
off.
        float [] zeros = new float[] { 0, 0, 0, 0 };
        GL.LightModel( All.LightModelAmbient, zeros );
        GL.LightModel( All.LightModelTwoSide, (float)All.True );

        // Note: If we specify any specular color on the material, then we get
problematic 
        // effects on iOS where entire triangles pop from diffuse lighting to
specular.
        // Instead, we'll multiply this.SpecularColor against the directional
light specular colors
        // which doesn't produce those artifacts.
        GL.Material( All.FrontAndBack, All.Specular, zeros );

        // Setup lighting.
        GL.Enable( All.Lighting );
        GL.Material( All.FrontAndBack, All.Ambient, new float[] { 0, 0, 0, 1 }
);
        GL.Material( All.FrontAndBack, All.Diffuse, new float[] { 1, 1, 1, 1 }
);
        GL.Material( All.FrontAndBack, All.Emission, new float[] { 0.1f, 0.1f,
0.1f, 1 } );

        GL.Enable( All.Light0 );
        GL.Light( All.Light0, All.Ambient, new float[] { 0, 0, 0, 1 } );
        GL.Light( All.Light0, All.Diffuse, new float[] { 1, 1, 1, 1 } );
        GL.Light( All.Light0, All.Specular, new float[] { 0, 0, 0, 1 } );
        GL.Light( All.Light0, All.Position, new float[] { vLightDir.X,
vLightDir.Y, vLightDir.Z, 0 } );
    }

    Vector3 GetLatitudeLongitudeVector( float longitudeRadians, float
latitudeRadians )
    {
        Vector3 ret = new Vector3();
        ret.X = (float)Math.Cos( longitudeRadians );
        ret.Y = (float)Math.Sin( longitudeRadians );
        ret.Z = (float)Math.Sin( latitudeRadians );
        float scale = (float)Math.Cos( latitudeRadians );
        ret.X *= scale;
        ret.Y *= scale;

        ret.Normalize();
        return ret;
    }

    static public float RemapValueRange( float value, float inRangeMin, float
inRangeMax, float outRangeMin, float outRangeMax )
    {
        float t = ( value - inRangeMin ) / ( inRangeMax - inRangeMin );
        return outRangeMin + ( outRangeMax - outRangeMin ) * t;
    }

    void DrawSphere( Vector3 vSphereCenter, float flRadius, int nSubdivsX, int
nSubdivsY, float flXAngleOffset )
    {
        Vector3 [] verts = new Vector3[ nSubdivsX * nSubdivsY * 6 ];
        Vector3 [] normals = new Vector3[ nSubdivsX * nSubdivsY * 6 ];
        Color [] colors = new Color[ nSubdivsX * nSubdivsY * 6 ];
        int nVerts = 0;

        Color clrVerts = Color.White;
        for ( int y=0; y < nSubdivsY; y++ )
        {
            float flPrevYAngle = RemapValueRange( (float)y / nSubdivsY, 0, 1,
(float)Math.PI / 2, (float)-Math.PI / 2 );
            float flNextYAngle = RemapValueRange( (float)(y+1) / nSubdivsY, 0,
1, (float)Math.PI / 2, (float)-Math.PI / 2 );

            for ( int x=0; x < nSubdivsX; x++ )
            {
                float flPrevXAngle = RemapValueRange( (float)x / nSubdivsX, 0,
1, 0, (float)Math.PI * 2 ) + flXAngleOffset;
                float flNextXAngle = RemapValueRange( (float)(x+1) / nSubdivsX,
0, 1, 0, (float)Math.PI * 2 ) + flXAngleOffset;

                Vector3 vPrevXPosTop = GetLatitudeLongitudeVector(
flPrevXAngle, flPrevYAngle );
                Vector3 vNextXPosTop = GetLatitudeLongitudeVector(
flNextXAngle, flPrevYAngle );

                Vector3 vPrevXPosBottom = GetLatitudeLongitudeVector(
flPrevXAngle, flNextYAngle );
                Vector3 vNextXPosBottom = GetLatitudeLongitudeVector(
flNextXAngle, flNextYAngle );

                colors[nVerts] = clrVerts;
                normals[nVerts] = vPrevXPosTop;
                verts[nVerts++] = vPrevXPosTop * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vNextXPosTop;
                verts[nVerts++] = vNextXPosTop * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vPrevXPosBottom;
                verts[nVerts++] = vPrevXPosBottom * flRadius + vSphereCenter;


                colors[nVerts] = clrVerts;
                normals[nVerts] = vNextXPosTop;
                verts[nVerts++] = vNextXPosTop * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vNextXPosBottom;
                verts[nVerts++] = vNextXPosBottom * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vPrevXPosBottom;
                verts[nVerts++] = vPrevXPosBottom * flRadius + vSphereCenter;
            }
        }

        unsafe
        {
            fixed ( Vector3 *pTris = verts )
            {
                fixed ( Vector3 *pNormals = normals )
                {
                    fixed ( Color *pColors = colors )
                    {
                        GL.Disable( All.TextureCoordArray );

                        GL.EnableClientState( All.ColorArray );
                        GL.ColorPointer( 4, All.UnsignedByte, 0,
(IntPtr)pColors );

                        GL.EnableClientState( All.NormalArray );
                        GL.NormalPointer( All.Float, 0, (IntPtr)pNormals );

                        GL.EnableClientState( All.VertexArray );
                        GL.VertexPointer( 3, All.Float, 0, (IntPtr)pTris );

                        GL.DrawArrays( All.Triangles, 0, nVerts );
                    }
                }
            }
        }
    }





SAMPLE C++ CODE
---------------

Here is the same exact test in C++. This version does not have the problem.

    class Vector3
    {
    public:
        Vector3()
        {
            X = Y = Z = 0;
        }
        Vector3( float ix, float iy, float iz )
        {
            X = ix;
            Y = iy;
            Z = iz;
        }
        void Normalize()
        {
            float len = (float)sqrt( X*X + Y*Y + Z*Z );
            float mul = 1.0f / len;
            X *= mul;
            Y *= mul;
            Z *= mul;
        }

        Vector3 operator*( float scale )
        {
            return Vector3( X*scale, Y*scale, Z*scale );
        }

        Vector3 operator+( const Vector3& other )
        {
            return Vector3( X+other.X, Y+other.Y, Z+other.Z );
        }

        float X, Y, Z;
    };

    class Vector2
    {
    public:
        float X, Y;
    };

    class Matrix
    {
    public:
        Matrix( 
               float m11, float m12, float m13, float m14,
               float m21, float m22, float m23, float m24,
               float m31, float m32, float m33, float m34,
               float m41, float m42, float m43, float m44 )
        {
            m[0] = m11;
            m[1] = m12;
            m[2] = m13;
            m[3] = m14;

            m[4] = m21;
            m[5] = m22;
            m[6] = m23;
            m[7] = m24;

            m[8] = m31;
            m[9] = m32;
            m[10] = m33;
            m[11] = m34;

            m[12] = m41;
            m[13] = m42;
            m[14] = m43;
            m[15] = m44;
        }

        float m[16];
    };

    Matrix CreatePerspectiveFieldOfView( float fieldOfViewY, float aspectRatio,
float nearPlaneDist, float farPlaneDist )
    {
        float yScale = 1.0f / (float)tan( fieldOfViewY * 0.5f );
        float xScale = yScale / aspectRatio;

        float zScale = farPlaneDist / ( nearPlaneDist - farPlaneDist );

        return Matrix(
                          xScale,            0,            0,            0,
                          0,                yScale,        0,            0,
                          0,                0,            zScale,        -1,
                          0,                0,            nearPlaneDist*zScale,
   0 );
    }

    class Color
    {
    public:
        Color()
        {
            r=g=b=a=255;
        }
        Color( unsigned char ir, unsigned char ig, unsigned char ib, unsigned
char ia )
        {
            r=ir;
            g=ig;
            b=ib;
            a=ia;
        }

        unsigned char r,g,b,a;
    };

    float RemapValueRange( float val, float in_min, float in_max, float
out_min, float out_max )
    {
        float t = ( val - in_min ) / ( in_max - in_min );
        return out_min + ( out_max - out_min ) * t;
    }


    Vector3 GetLatitudeLongitudeVector( float longitudeRadians, float
latitudeRadians )
    {
        Vector3 ret;
        ret.X = (float)cos( longitudeRadians );
        ret.Y = (float)sin( longitudeRadians );
        ret.Z = (float)sin( latitudeRadians );
        float scale = (float)cos( latitudeRadians );
        ret.X *= scale;
        ret.Y *= scale;

        ret.Normalize();
        return ret;
    }


    void DrawSphere( Vector3 vSphereCenter, float flRadius, int nSubdivsX, int
nSubdivsY, float flXAngleOffset )
    {
        Vector3 *verts = new Vector3[ nSubdivsX * nSubdivsY * 6 ];
        Vector3 *normals = new Vector3[ nSubdivsX * nSubdivsY * 6 ];
        Color *colors = new Color[ nSubdivsX * nSubdivsY * 6 ];
        int nVerts = 0;

        Color clrVerts;
        clrVerts.r =     clrVerts.g =     clrVerts.b =     clrVerts.a = 255;
        for ( int y=0; y < nSubdivsY; y++ )
        {
            float flPrevYAngle = RemapValueRange( (float)y / nSubdivsY, 0, 1,
(float)M_PI / 2, (float)-M_PI / 2 );
            float flNextYAngle = RemapValueRange( (float)(y+1) / nSubdivsY, 0,
1, (float)M_PI / 2, (float)-M_PI / 2 );

            for ( int x=0; x < nSubdivsX; x++ )
            {
                float flPrevXAngle = RemapValueRange( (float)x / nSubdivsX, 0,
1, 0, (float)M_PI * 2 ) + flXAngleOffset;
                float flNextXAngle = RemapValueRange( (float)(x+1) / nSubdivsX,
0, 1, 0, (float)M_PI * 2 ) + flXAngleOffset;

                Vector3 vPrevXPosTop = GetLatitudeLongitudeVector(
flPrevXAngle, flPrevYAngle );
                Vector3 vNextXPosTop = GetLatitudeLongitudeVector(
flNextXAngle, flPrevYAngle );

                Vector3 vPrevXPosBottom = GetLatitudeLongitudeVector(
flPrevXAngle, flNextYAngle );
                Vector3 vNextXPosBottom = GetLatitudeLongitudeVector(
flNextXAngle, flNextYAngle );

                colors[nVerts] = clrVerts;
                normals[nVerts] = vPrevXPosTop;
                verts[nVerts++] = vPrevXPosTop * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vNextXPosTop;
                verts[nVerts++] = vNextXPosTop * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vPrevXPosBottom;
                verts[nVerts++] = vPrevXPosBottom * flRadius + vSphereCenter;


                colors[nVerts] = clrVerts;
                normals[nVerts] = vNextXPosTop;
                verts[nVerts++] = vNextXPosTop * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vNextXPosBottom;
                verts[nVerts++] = vNextXPosBottom * flRadius + vSphereCenter;

                colors[nVerts] = clrVerts;
                normals[nVerts] = vPrevXPosBottom;
                verts[nVerts++] = vPrevXPosBottom * flRadius + vSphereCenter;
            }
        }

        glDisableClientState( GL_TEXTURE_COORD_ARRAY );

        glEnableClientState( GL_COLOR_ARRAY );
        glColorPointer( 4, GL_UNSIGNED_BYTE, 0, colors );

        glEnableClientState( GL_NORMAL_ARRAY );
        glNormalPointer( GL_FLOAT, 0, normals );

        glEnableClientState( GL_VERTEX_ARRAY );
        glVertexPointer( 3, GL_FLOAT, 0, verts );

        glDrawArrays( GL_TRIANGLES, 0, nVerts );
    }


    void RenderAllManual( float flElapsed, Matrix mProjection, Vector3
vLightDir )
    {
        Vector2 vResolution;
        vResolution.X = 320;
        vResolution.Y = 480;

        // Clear.
        glClearColor( 0, 0, 0, 1 );
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        // Setup viewport and other states.
        glViewport( 10, 10, (int)vResolution.X - 20, (int)vResolution.Y - 20 );

        glDisable( GL_CULL_FACE );

        glBlendFunc( GL_ONE, GL_ZERO );

        glEnable( GL_DEPTH_TEST );
        glDepthFunc( GL_LEQUAL );
        glDepthMask( true );

        //glMatrixMode( GL_MODELVIEW );
        //glLoadIdentity();

        glMatrixMode( GL_PROJECTION );
        glLoadMatrixf( mProjection.m );

        glDisable( GL_TEXTURE_2D );

        // (usually one-time lighting init)
        glEnable( GL_COLOR_MATERIAL );

        // GL's default ambient lighting is ( 0.2, 0.2, 0.2, 1.0 ). Turn it
off.
        float zeros[] = { 0, 0, 0, 0 };
        glLightModelfv( GL_LIGHT_MODEL_AMBIENT, zeros );
        glLightModelf( GL_LIGHT_MODEL_TWO_SIDE, (float)1 );

        // Note: If we specify any specular color on the material, then we get
problematic 
        // effects on iOS where entire triangles pop from diffuse lighting to
specular.
        // Instead, we'll multiply this.SpecularColor against the directional
light specular colors
        // which doesn't produce those artifacts.
        glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, zeros );

        // Setup lighting.
        glEnable( GL_LIGHTING );

        float ambient[] = { 0, 0, 0, 1 };
        glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, ambient );

        float diffuse[] = { 1, 1, 1, 1 };
        glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse );

        float emission[] = { 0.1f, 0.1f, 0.1f, 1 };
        glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, emission );

        glEnable( GL_LIGHT0 );

        float ambient2[] = { 0, 0, 0, 1 };
        glLightfv( GL_LIGHT0, GL_AMBIENT, ambient2 );

        float diffuse2[] = { 1,1,1,1 };
        glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse2 );

        float specular2[] = { 0, 0, 0, 1 };
        glLightfv( GL_LIGHT0, GL_SPECULAR, specular2 );

        float position2[] = { vLightDir.X, vLightDir.Y, vLightDir.Z, 0 };
        glLightfv( GL_LIGHT0, GL_POSITION, position2 );
    }

    void GLRenderTest()
    {
        static float flStart = CACurrentMediaTime();
        float flElapsed = CACurrentMediaTime() - flStart;

        Matrix mProjection = CreatePerspectiveFieldOfView( (float)M_PI / 2, 1,
10, 1000 );

        Vector3 vLightDir = Vector3(1,0,0);// Vector3( -9, -4, -1 );
        vLightDir.Normalize();

        RenderAllManual( flElapsed, mProjection, vLightDir );

        // Draw some stuff.
        Vector3 vSphereCenter = Vector3( 0, 0, -20 );
        float flRadius = 10;
        int nSubdivsX = 10;
        int nSubdivsY = 10;
        float flXAngleOffset = flElapsed / 4;

        //Tao.OpenGl.Gl.glPolygonMode( Tao.OpenGl.Gl.GL_FRONT_AND_BACK,
Tao.OpenGl.Gl.GL_LINE );
        DrawSphere( vSphereCenter, flRadius, nSubdivsX, nSubdivsY,
flXAngleOffset );
        DrawSphere( Vector3(12,12,-20), flRadius/4, nSubdivsX, nSubdivsY,
flXAngleOffset );
    }

-- 
Configure bugmail: https://bugzilla.novell.com/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are the QA contact for the bug.
You are the assignee for the bug.


More information about the mono-bugs mailing list