Celeron's picture

[SL Multitexturing] - Only one texture with gluSphere

Hi again,
after my first newbie post ( http://www.opentk.com/node/719 , sorry again... ) I've done my homeworks using RenderMonkey to check the vertex/fragment shader syntax.
Well, the result is acceptable as you can see here:
http://img136.imageshack.us/img136/8042/rmonkey.jpg

The seguent poor result, instead, is produced by my code:
http://img183.imageshack.us/img183/5339/myearthmultitex.jpg

It seems like only one texture (day) is available and for sure something in my code is going wrong ;-)
A question: does gluSphere support multitexturing? If so, how can I accomplish this task? Reading some examples with Google, I didn't found any reference to this.

For all those wishing help me to understand, I've posted the VB.Net 2005 project that implements - better say "tries to implement" :-) - SL sphere multitexturing.
In the project is included vertex/fragment code (as it appears in the Orange Book - ch. #10), but - obviously - not the textures. These ones are 7200x3600 px jpegs; after all, to compile and run the project, it is possible to use any image you have; the problem - I think - isn't in the textures because they went well in RenderMonkey :-(

Thanks in advance for any help :-)

AttachmentSize
SphereMultitexturing.zip48.85 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

The textures could very well be the problem. Most hardware supports only 409x4096 - anything more and it won't work.

I'd also advise against using gluSphere. It's too inflexible and creating a sphere manually is easy:

        [StructLayout(LayoutKind.Sequential, Pack=1)]
        struct VertexP3N3T2
        {
             public Vector3 Position, Normal;
              public Vector2 TexCoord;
        }
 
        static VertexP3N3T2[] CalculateVertices(float radius, float height, int segments, int rings)
        {
           // Load data into a vertex buffer or a display list afterwards.
           var data = new VertexP3N3T2[rings * segments];
 
           for (double y = 0; y < rings; y++)
            {
                double phi = (y / (rings - 1)) * Math.PI;
                for (double x = 0; x < segments; x++)  
                {
                    double theta = (x / (segments - 1)) * 2 * Math.PI;
 
                    Vector3 v = new Vector3()
                    {
                        X = (float)(radius * Math.Sin(phi) * Math.Cos(theta)),
                        Y = (float)(height * Math.Cos(phi)),
                        Z = (float)(radius * Math.Sin(phi) * Math.Sin(theta)),
                    };
                    Vector3 n = Vector3.Normalize(v);
                    // Horizontal texture projection
                    Vector2 uv = new Vector2()
                    {
                        X = (float)(x / (segments - 1)),
                        Y = (float)(y / (rings - 1))
                    };
                    // Using data[i++] causes i to be incremented multiple times in Mono 2.2 (bug #479506).
                    data[i] = new VertexP3N3T2() { Position = v, Normal = n, TexCoord = uv };
                    i++;
 
                    // Top - down texture projection.
                    //Vector2 uv = new Vector2()
                    //{
                    //    X = (float)(Math.Atan2(n.X, n.Z) / Math.PI / 2 + 0.5),
                    //    Y = (float)(Math.Asin(n.Y) / Math.PI / 2 + 0.5)
                    //};
                }
            }  
            return data;
        }
 
        static ushort[] CalculateElements(float radius, float height, int segments, int rings)
        {
            // Load data into an element buffer or use them as offsets into the vertex array above.
            var data = new ushort[segments * rings * 6];
 
            for (int y = 0; y < rings - 1; y++)
            {
                for (int x = 0; x < segments - 1; x++)
                {
                    data[i++] = (ushort)((y + 0) * segments + x);
                    data[i++] = (ushort)((y + 1) * segments + x);
                    data[i++] = (ushort)((y + 1) * segments + x + 1);
 
                    data[i++] = (ushort)((y + 1) * segments + x + 1);
                    data[i++] = (ushort)((y + 0) * segments + x + 1);
                    data[i++] = (ushort)((y + 0) * segments + x);
                }
            }
            return data;
        }
Celeron's picture

Thank you very much for the reply.
As soon as possible I'll apply the code posted and will report here the feedback.
Maybe RenderMonkey automatically scales the textures before applying them?

the Fiddler's picture

Yeah, it probably scales and compresses textures when you add them to the project.

Celeron's picture

Well, after a couple of weeks... here I am :-)
I've appreciated your code, Fiddler, so I've translated the vb example project in C# to use it as best as possible.
Reading this forum and the pletora of OpenGL code found on the web, I've tried to use both VBO and SL. The result is the C# project attached to this post and - most of all - a big, big video driver crash when I run it :-((

Obviously my poor knowledge is The Problem, but I think that is an obliged path.

Well, the crash occurs when I try to draw the VBO using this method:

            /// <summary>
            /// Draws a VBO
            /// </summary>
            /// <param name="vboPtr">Vbo structure info</param>
            /// <param name="indices">array of INDICES</param>
            /// <param name="vertices">array of VERTICES</param>
            public static void DrawVbo(Vbo vboPtr, ushort[] indices, VertexP3N3T2[] vertices)
            {
 
                // bind VBOs for vertex array and index array
                GL.BindBuffer(BufferTarget.ArrayBuffer, vboPtr.VboId);           // for vertex coordinates
                GL.BindBuffer(BufferTarget.ElementArrayBuffer, vboPtr.Eboid);    // for indices
 
                GL.EnableClientState(EnableCap.VertexArray);            // activate vertex, normal and texture coords array
                GL.EnableClientState(EnableCap.NormalArray);
                GL.EnableClientState(EnableCap.TextureCoordArray);
 
                // tells OpenGL how the data are passed
                GL.InterleavedArrays(InterleavedArrayFormat.T2fN3fV3f, 0, (IntPtr) null);
 
                // obtains the safe pointer to the indices array
                GCHandle pinnedArray = GCHandle.Alloc(indices, GCHandleType.Pinned);
                IntPtr ptrIndices = pinnedArray.AddrOfPinnedObject();
 
                // draws the elements
                GL.DrawElements(BeginMode.Triangles, vboPtr.Numelements, DrawElementsType.UnsignedShort, ptrIndices);
 
                // frees resources
                pinnedArray.Free();
 
                GL.DisableClientState(EnableCap.VertexArray);                // deactivates used Client States
                GL.DisableClientState(EnableCap.NormalArray);
                GL.DisableClientState(EnableCap.TextureCoordArray);
 
                // bind with 0, so, switch back to normal pointer operation
                GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
                GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
 
            }
        }

I'm quite sure that something is missing here. But I didn't understand what.

I've scaled down the textures to 4096x4096 and I've updated them here: http://www.box.net/shared/ntb7akfir5
To use them in the project, simply put in the folder "textures" under the project root.

Thanks in advance.

AttachmentSize
VboAndShaders.zip37.88 KB
the Fiddler's picture

Which is the crashing line?

As far as I can tell, the issue lies in the element array. Since you are using an element array, make sure to:

  1. upload not only the vertex data, but also the element data (simply bind the element array and use GL.BufferData at the place where you create the VBO), and
  2. enable the element array prior to drawing (as you do for the vertices, normals, etc).

Once you do that, you can draw the VBO like this:

GL.DrawElements(BeginMode.Triangles, indices.Length, DrawElementsType.UnsignedShort, IntPtr.Zero);

The last parameter is now an offset from the beginning of the element array - use IntPtr.Zero to draw the elements from the beginning. Numelements should be equal or less than the number of items in the element array (not the vertex array!), i.e. indices.Length.

This way you don't need to pin anything either. Everything is uploaded to GL memory via the BufferData calls - you simply need to bind the buffers and issue the drawing command (in other words, you don't need to keep the ushort[] and VertexP3N3T2[] arrays around).

Celeron's picture

Thank you very much :-)
Now the crash is fixed, even if the result is not still impressive as you can see here :-))
http://img231.imageshack.us/img231/4227/vbosl.jpg

The method I use to load the buffers is:

            /// <summary>
            /// Load into a Vertex Buffer Object both VERTICES and INDICES and returns a vbo structure info
            /// </summary>
            /// <param name="vertices">vertex array</param>
            /// <param name="indices">elements array</param>
            /// <returns></returns>
            public static Vbo LoadVbo(VertexP3N3T2[] vertices, ushort[] indices)
            {
                Vbo handle=new Vbo();
                int size;
 
                //vertices
                GL.GenBuffers(1, out handle.VboId);
                if (GL.GetError() != ErrorCode.NoError)
                {
                    throw new ApplicationException("Error while creating VERTICES Buffer Object.\n\nERROR: " + Enum.GetName(typeof(ErrorCode), GL.GetError()));
                }
                GL.BindBuffer(BufferTarget.ArrayBuffer, handle.VboId);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertices.Length * Vector3.SizeInBytes),vertices, BufferUsageHint.StaticDraw);
                GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, out size);
 
                if (GL.GetError() != ErrorCode.NoError)
                {
                    throw new ApplicationException("Error while creating VERTICES Buffer Object.\n\nERROR: " + Enum.GetName(typeof(ErrorCode), GL.GetError()));
                }
 
                if (vertices.Length*Vector3.SizeInBytes!=size)
                {
                    throw new ApplicationException("Error while uploading VERTICES data.");
                }
 
                //indices
                GL.GenBuffers(1, out handle.Eboid);
                if (GL.GetError() != ErrorCode.NoError)
                {
                    throw new ApplicationException("Error while creating INDICES Buffer Object.\n\nERROR: " + Enum.GetName(typeof(ErrorCode), GL.GetError()));
                }
                GL.BindBuffer(BufferTarget.ElementArrayBuffer, handle.Eboid);
                GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(indices.Length * sizeof(ushort)), indices, BufferUsageHint.StaticDraw);
                GL.GetBufferParameter(BufferTarget.ElementArrayBuffer,BufferParameterName.BufferSize, out size);
                if (indices.Length * sizeof(ushort) != size)
                {
                    throw new ApplicationException("Error while uploading INDICES data.");
                }
 
                if (GL.GetError() != ErrorCode.NoError)
                {
                    throw new ApplicationException("Error while creating INDICES Buffer Object.\n\nERROR: " + Enum.GetName(typeof(ErrorCode), GL.GetError()));
                }
 
                handle.Numelements = indices.Length;
                return handle;
            }

Really don't know what's happening now... Do you have any idea about what to check out?
Thanks for the patience.

Inertia's picture

You're specifying InterleavedArrayFormat.T2fN3fV3f, but your vertex struct is VertexP3N3T2. If you use GL.InterleaveArray(), TexCoord must be the first 2 floats, Normal the following and Position the last 3 floats. If you want to keep the struct as-is, check http://www.opentk.com/node/278

Celeron's picture

Aaarrghh :-)) .. thanks, Inertia.. yeah.. now works (I think). I really must check the viewport, but "something" (not too close to a sphere after all) is painted.
I've changed the "VertexP3N3T2" structure in this way, according to the Interleaved Array Format that I am using:

            /// <summary>
            /// Vertex Structure for vec3 position, vec3 normals, vec2 textcoord
            /// </summary>
            [StructLayout(LayoutKind.Sequential, Pack = 1)]
            public struct VertexP3N3T2
            {
                public Vector2 TexCoord;
                public Vector3 Normal;
                public Vector3 Position;                
            }

This is what I've got: http://img155.imageshack.us/img155/4452/vbsl3.jpg
Well, I think that now I have to work on the "Reshape" method; this is what I'm using at the moment:

        /// <summary>
        /// Set-up the viewport; always called when the glControl is resized
        /// </summary>
        /// <param name="w">width</param>
        /// <param name="h">height</param>
        private void OnReshape(int w, int h)
        {
            if (w == 0 | h == 0)
            {
                w = 1;
                h = 1;
            }
            GL.Viewport(0, 0, w, h);
            Double aspect = w / h;
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            Glu.Perspective(45.0, aspect, 1.0, 90.0); 
        }

What I have to change to frame completly the "Earth"? When I tried the "single texture experiment" I easily applied this "SetupViewport" method:

        ''' <summary>
        ''' Sets up the the viewport
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub SetupViewport()
 
            Dim cx As Single = 0.0, halfWidth As Single = _viewportW * 0.5F, halfHeight = _viewportH * 0.5F
            Dim aspect As Single = 4.0F / 3.0F '_viewportW / _viewportH
 
            GL.Viewport(0, 0, _viewportW, _viewportW)
            GL.MatrixMode(MatrixMode.Modelview)
            GL.LoadIdentity()
            'cx is the eye space center of the zNear plane in X */
            GL.Ortho(cx - halfWidth * aspect, cx + halfWidth * aspect, _viewportH * -1, _viewportH, 0.0F, _viewportW * 4)
            Glu.LookAt( _
                0.0F, 300.0F, 0.0F, _
                0.0F, 0.0F, -200.0F, _
                0.0F, 1.0F, 0.0F)
 
        End Sub

... but now it doesn't seem work any more...

Inertia's picture

Double aspect = w / h; should be Double aspect = w / (double)h; otherwise the division is done with integers and the result is cast to double.

Glu.Perspective(45.0, aspect, 1.0, 90.0); could be a troublemaker aswell, try set the nearplane to 0.1 if the front of the sphere appears clipped.

GL.Viewport(0, 0, _viewportW, _viewportW) contains a typo

I don't quite get what you're doing with GL.Ortho and then multiplying Glu.LookAt with it. GL.Ortho should only be used if you want a orthogonal projection matrix (for 2D) but never with the modelview matrix. If this is some cool hack I'd love to hear about it :)

Celeron's picture

Yeah.. a big cool hack :-)) Really, I'm trying to understand how to play with viewport and projection; it's so hard to study only few hours during the week-end, happily with my two sons.. have mercy on a poor father :-))
I'll check these concepts as soon as possible and I'll report my progress.

Thank you very much for your attention and specially for your patience ;-)