Jump to content

  • Log In with Google      Sign In   
  • Create Account

spacerat

Member Since 26 Oct 2006
Offline Last Active Dec 13 2014 09:40 PM

#5192343 Procedural Map Generation

Posted by spacerat on 11 November 2014 - 07:47 PM

http://www.reddit.com/r/gamedev/comments/2lyqki/three_techniques_to_procedurally_generate/




#5162779 Legal stuff when using free art / models in an own game

Posted by spacerat on 25 June 2014 - 09:27 AM

I am wondering how and to what extent free art / sprites / 3d models can be used in an own game.

 

For example, lets say in the case of an animated mickey-mouse sprite, where the readme inside states:

 

"This sprite may be freely distributed UNALTERED.  Which means, you can't pull the readme file out of the zip,or add your own stuff to it and pass it along as your own!"

 

I believe including it in the game an selling it bundled will not be allowed.

On the other hand, if the user downloads the free model himself and installs it in the game-folder, there wont be any problem.

 

Now where is the boundary ?

Lets consider the following cases:

 

Like what happens if the game does not contain this sprite and 

 

-downloads and installs it automatically after installation ?

-downloads and installs it on request (pushing a button) ?

-contains a script to download and install the sprite ?




#5153872 High Speed Quadric Mesh Simplification without problems (Resolved)

Posted by spacerat on 15 May 2014 - 07:31 PM

Thank you Spiro!

 

I just updated the code

  • Now everything is commented
  • Borders are preserved well in the new version
  • Added a new parameter (aggressiveness) to guide the collapsing speed. If the parameter is lower (5 instead of 7 e.g.), more iterations will be required and the result is closer to the optimum.
  • The matrix is reduced from 16 to 10 elements since its symmetric

Here a comparison to other methods, and the updated Code (got a bit larger due to border tests)  DOWNLOAD.

 

ipRxous.png




#5153306 High Speed Quadric Mesh Simplification without problems (Resolved)

Posted by spacerat on 13 May 2014 - 08:04 AM

Thanks a lot for the hints and the code. Now it works well cool.png

 

I have updated the DOWNLOAD

 

You are right - the error updating was the issue. I have resolved that now.

The main problem was apparently that i needed to compute the quadric per vertex from scratch each couple of iterations.

For the other iterations I simply added the quadric of the vertex to collapse to the target vertex and updated all edges.

 

Left to do is a check if a vertex is an border vertex and to add support for attributes.

 

Compared to Meshlab, its about 4x faster. 

It is several times slower than vertex clustering ( Rapid Simplification of Multi-Attribute Meshes HPG Paper ),

but has much higher quality due to the quadric metric.

 

Even there is no sorting and just a steadily increased threshold, the result is still good.

Left is the program output (5 seconds), right the Meshlab result (20 seconds) for reducing 2 million triangles down to 20k.

 

Clipboard03.png




#5153215 High Speed Quadric Mesh Simplification without problems (Resolved)

Posted by spacerat on 12 May 2014 - 10:47 PM

Ok, now the new method almost works. I found two reasons why it didnt work before:

  • I recalculated the normal after collapsing - there its better to keep the original
  • Flipping happened after the triangle became almost line shaped and the normal was 0 

The new version avoids this as follows:

  • The new normal is always tested against the original normal
  • The inner angle between 2 edges of the new triangle is tested to not get too line shaped (dot > 0.99)

Remaining problem:

  • The edge collapse stops now even it does not reach the target number of triangles, as any further collapse would lead to violation of the above two tests.

Now, what is the best solution to that ?

 

Here the new version :  Simplify.h  ( including sorting by error and stopping at a certain number of triangles )




#5153029 High Speed Quadric Mesh Simplification without problems (Resolved)

Posted by spacerat on 12 May 2014 - 03:40 AM

Thank you for both of your comments!

 

Based on what criteria do you prioritize one contraction over the other? I don't see you (re-)sorting the list of triangles.

 

Its threshold based - one threshold for the edge length, and one for the quadric error. Once the triangle error problem is solved, I will try a sorted list for better quality.

Currently, the decision if a triangle is removed is performed in line 60 in the code above.

 

If you use a proper data structure (custom heap) for the edge list, you can get similar performance to your implementation, but with much better quality. This keeps the edge list sorted automatically, and if you store the position in the (array-based) heap along with the edge, you can get O(log(N)) updates/removals with each iteration. The problem with not updating the list of edges on each iteration is that the algorithm no longer optimally picks the edge with the least error, so your results will not be as good as the original quadric-error edge collapse algorithm.

 

The problem you're probably seeing is that triangles can flip over during an edge collapse, causing overlap of mostly coplanar triangles. You can fix this by checking each triangle that shares the edge vertices to make sure that its normal does not change sign (dot product with old normal < 0).

 

The triangle flipping is a good point. I have spend a lot of time now to figure out how to efficiently do this test without slowing down the method too much. The result is a modified method that stores a list of triangles for each vertex, where each of these triangles has one reference to this vertex. It allows to efficiently test for flipping and collapsing one vertex.

 

In the code below, the list with ids named refs. It is referred to by tstart,tcount from vertex and contains the triangle ID (tid) and vertex offset inside the triangle (tvertex). (In the current version it just grows - but in the final version it needs to be re-initialized every couple of iterations to avoid using too much ram)

 

The problem is that even it prevents some errors, still flipped triangles occur somehow. I could not yet resolve the problem. Is there anything else that can cause the flipping error ? Or can you spot an error in the flipping test below ? ( function flipped )

 

Edit: Seems I found a hint: the problem seems to arise when the triangles get almost line - shaped, so all 3 points are along one line.

 

For the speed, the method can basically be even faster as it doesnt require so many iterations.

I first need to resolve the error however, before speeding up and improving the quality by sorting.

 

For the edge based structure, I have tried a edge based structure before, but since I had referenced to the triangles in the edges it got quite messy to update them after one triangle was removed. The speed might be equal, yes.

 

Here the new version ( Simplify.h )

struct Triangle{ int v[3];double err[3];bool deleted,dirty;vec3f n; };
struct Vertex{ vec3f p;int tstart,tcount;Matrix q; };
struct Ref { int tid,tvertex; }; 
std::vector<Triangle> triangles;
std::vector<Vertex> vertices;
std::vector<Ref> refs;

double vertex_error(Matrix q, double x, double y, double z);
double calculate_error(int id_v1, int id_v2, vec3f &p_result);

bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<vec3f> &tmp_n)
{
    // p  : new vertex position ( (v0.p+v1.p)/2 for the most simple case )
    // i0 : index of vertex 0
    // i1 : index of vertex 1
    // v0 : edge vertex 0
    // v1 : edge vertex 1
    // tmp_n : array to temporarily store normals to avoid recomputing them

    loopk(0,v0.tcount)
    {
        Triangle &t=triangles[refs[v0.tstart+k].tid]; 
        int s=refs[v0.tstart+k].tvertex;
        int id1=t.v[(s+1)%3];
        int id2=t.v[(s+2)%3];

        if(id1==i1 || id2==i1 ) // delete ?
        { 
            tmp_n[k].x=-10;
            continue;
        }
        vec3f p1 = vertices[id1].p; 
        vec3f p2 = vertices[id2].p; 
        vec3f n;
        n.cross(p1-p,p2-p);
        n.normalize();
        tmp_n[k]=n;
                        
        if(n.dot(t.n)<0.0) return true;
    }
    return false;
}
void update_triangles(int i0,Vertex &v,std::vector<vec3f> &tmp_n,int &deleted_triangles)
{
    vec3f p;
    loopk(0,v.tcount)
    {
        Ref &r=refs[v.tstart+k];
        Triangle &t=triangles[r.tid]; 
        if(t.deleted)continue;
        if(tmp_n[k].x<-5) 
        {
            t.deleted=1;
            deleted_triangles++;
            continue;
        }
        t.v[r.tvertex]=i0;
        t.n=tmp_n[k];
        t.dirty=1;
        t.err[0]=calculate_error(t.v[0],t.v[1],p);
        t.err[1]=calculate_error(t.v[1],t.v[2],p);
        t.err[2]=calculate_error(t.v[2],t.v[0],p);
        refs.push_back(r);
    }
}

void reduce_polygons()
{
    printf("reduce_polygons - start\n");
    int timeStart=timeGetTime();

    // Init defaults
    loopi(0,vertices.size())
    {
        Vertex &v=vertices[i];
        v.q=Matrix(0.0);
        v.tstart=0;
        v.tcount=0;
    }
    loopi(0,triangles.size()) 
        triangles[i].deleted=
        triangles[i].dirty=0;

    // init Reference ID list
    refs.resize(triangles.size()*3);

    loopi(0,triangles.size())
    {
        Triangle &t=triangles[i];
        loopj(0,3) vertices[t.v[j]].tcount++;
    }
    int tstart=0;
    loopi(0,vertices.size())
    {
        Vertex &v=vertices[i];
        v.tstart=tstart;
        tstart+=v.tcount;
        v.tcount=0;
    }
    loopi(0,triangles.size())
    {
        Triangle &t=triangles[i];            
        loopj(0,3)
        {
            Vertex &v=vertices[t.v[j]];
            refs[v.tstart+v.tcount].tid=i;
            refs[v.tstart+v.tcount].tvertex=j;
            v.tcount++;
        }
    }
    // Init Quadric by Plane
    loopi(0,triangles.size())
    {
        Triangle &t=triangles[i]; vec3f n,p[3];

        loopj(0,3) p[j]=vertices[t.v[j]].p;
        n.cross(p[1]-p[0],p[2]-p[0]);
        n.normalize();
        t.n=n;

        loopj(0,3) vertices[t.v[j]].q = 
            vertices[t.v[j]].q+Matrix(n.x,n.y,n.z,-n.dot(p[0]));
    }
    // Calc Edge Error
    loopi(0,triangles.size())
    {
        Triangle &t=triangles[i];vec3f p;
        loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
    }
    int deleted_triangles=0; 
    std::vector<vec3f> tmp_n0,tmp_n1;

    loopl(0,20) // iteration
    {
        // remove vertices & mark deleted triangles
        loopi(0,triangles.size()) if(!triangles[i].deleted) if(!triangles[i].dirty)
        {
            Triangle &t=triangles[i];

            loopj(0,3) // all edges of one triangle
            {
                int i0=t.v[ j     ]; Vertex &v0 = vertices[i0]; 
                int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];
                
                bool test1=t.err[j] < 0.00000001*l*l*l*l*l;
                bool test2=(v0.p-v1.p).length()<0.3*l;
                
                // remove based on edgelength and quadric error
                if(test1 && test2) // remove edge ?
                {
                    vec3f p;
                    calculate_error(i0,i1,p); // quadric based new edge
                    //p=(v1.p+v0.p)*0.5;      // simple new edge for testing

                    tmp_n0.resize(v0.tcount); // normals temporarily
                    tmp_n1.resize(v1.tcount); // normals temporarily

                    // dont remove if flipped
                    if( flipped(p,i0,i1,v0,v1,tmp_n0) ) continue;
                    if( flipped(p,i1,i0,v1,v0,tmp_n1) ) continue;

                    // not flipped, so remove edge
                    v0.p=p;
                    v0.q=v1.q+v0.q;
                    int tstart=refs.size();
                    update_triangles(i0,v0,tmp_n0,deleted_triangles);
                    update_triangles(i0,v1,tmp_n1,deleted_triangles);
                    int tcount=refs.size()-tstart;
                    v0.tstart=tstart;
                    v0.tcount=tcount;
                    break;
                }
            }
        }
        // clear dirty flag
        loopi(0,triangles.size()) triangles[i].dirty=0;
    }
}



#5152662 High Speed Quadric Mesh Simplification without problems (Resolved)

Posted by spacerat on 10 May 2014 - 05:51 AM

Update May 13th: Download the working version here  

MIT license, well documented and working without errors.

The latest source is also included in the lowermost reply.

 

 

Summary I am currently on a new implementation of a Quadric based mesh simplification algorithm. Its quite short (130 lines for the main part) , but apparently there is a bug somewhere. I searched already a while but couldnt find it yet. If one of you can spot it, any help is appreciated. 

 

In turn, everyone gets a great mesh simplification method for free once it works smile.png

Its able to reduce 90% of 650.000 triangles in about 1 sec. (Single threaded) by using less memory than most other methods.

 

Algorithm Different from the original method, it does not store the per-edge-error in an edges list, but per triangle. This avoids the edges list completely as the edge list is slow creating and updating. On the other hand, every error is computed twice - but that's not so serious.

 

Methods

calculate_error calculates the error between two vertices and output the vertex an edge might be reduced to.

reduce_polygons main function to reduce the mesh

 

The Problem Here a screenshot of the bug: Some faces are flipped and look displaced..

 

kqjc5hL.png

 

Here the source code

struct Triangle{ int v[3];double err[3];bool deleted; };
struct Vertex{ vec3f p,n;int dst,dirty;Matrix q; };
std::vector<Triangle> triangles;
std::vector<Vertex> vertices;

double vertex_error(Matrix q, double x, double y, double z);
double calculate_error(int id_v1, int id_v2, vec3f &p_result);

void reduce_polygons()
{
    // Init defaults
    loopi(0,vertices.size())
    {
        vertices[i].dst=-1;
        vertices[i].q=Matrix(0.0);
        vertices[i].dirty=false;
        vertices[i].n=vec3f(0,0,0);
    }
    loopi(0,triangles.size()) triangles[i].deleted=0;
    
    // Init Quadric by Plane
    loopi(0,triangles.size())
    {
        Triangle &t=triangles[i]; vec3f n,p[3];

        loopj(0,3) p[j]=vertices[t.v[j]].p;
        n.cross(p[1]-p[0],p[2]-p[0]);
        n.normalize();

        loopj(0,3) vertices[t.v[j]].q = 
            vertices[t.v[j]].q+Matrix(n.x,n.y,n.z,-n.dot(p[0]));
    }
    // Calc Edge Error
    loopi(0,triangles.size())
    {
        Triangle &t=triangles[i];vec3f p;
        loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
    }
    int deleted_triangles=0; 

    loopl(0,25) // iteration
    {
        // remove vertices & mark deleted triangles
        loopi(0,triangles.size()) if(!triangles[i].deleted)
        {
            Triangle &t=triangles[i];
            if(vertices[t.v[0]].dirty) continue;
            if(vertices[t.v[1]].dirty) continue;
            if(vertices[t.v[2]].dirty) continue;

            loopj(0,3) 
            {
                int i0=t.v[ j     ]; Vertex &v0 = vertices[i0]; 
                int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];

                bool test1=t.err[j] < 0.00000001*l*l*l*l*l;
                bool test2=(v0.p-v1.p).length()<0.3*l;
                
                // remove based on edgelength and quadric error
                if(test1 && test2)
                {
                    calculate_error(i0,i1,v0.p);
                    //v0.p=(v1.p+v0.p)*0.5;
                    v0.q=v1.q+v0.q;
                    v1.dst=i0;
                    v1.dirty=true;
                    v0.dirty=true;
                    t.deleted=1;
                    deleted_triangles++;
                    break;
                }
            }
        }
        // update connectivity
        loopi(0,triangles.size()) if(!triangles[i].deleted)
        {
            Triangle &t=triangles[i];

            loopj(0,3) 
            if(vertices[t.v[j]].dst>=0)
            {
                t.v[j]=vertices[t.v[j]].dst;
                if(t.v[j]==t.v[(j+1)%3] || t.v[j]==t.v[(j+2)%3] )
                {
                    // two equal points -> delete triangle
                    t.deleted=1;
                    deleted_triangles++;
                    break;
                }
                t.dirty=1;
            }
            if(!t.dirty)continue;

            // update error
            bool dirty=0;
            loopj(0,3) dirty|=vertices[t.v[j]].dirty;
            if(!dirty)continue;

            // update error
            vec3f p;
            loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
        }
        // clear dirty flag
        loopi(0,vertices.size()) vertices[i].dirty=0;
    }
}

double vertex_error(Matrix q, double x, double y, double z)
{
     return   q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[5]*y*y
          + 2*q[6]*y*z + 2*q[7]*y + q[10]*z*z + 2*q[11]*z + q[15];
}

double calculate_error(int id_v1, int id_v2, vec3f &p_result)
{
    Matrix q_bar = vertices[id_v1].q + vertices[id_v2].q;
    Matrix q_delta (  q_bar[0], q_bar[1],  q_bar[2],  q_bar[3],
                      q_bar[4], q_bar[5],  q_bar[6],  q_bar[7], 
                      q_bar[8], q_bar[9], q_bar[10], q_bar[11], 
                             0,        0,          0,        1);
    if ( double det = q_delta.det(0, 1, 2, 4, 5, 6, 8, 9, 10) ) 
    {
        p_result.x = -1/det*(q_delta.det(1, 2, 3, 5, 6, 7, 9, 10, 11));
        p_result.y =  1/det*(q_delta.det(0, 2, 3, 4, 6, 7, 8, 10, 11));
        p_result.z = -1/det*(q_delta.det(0, 1, 3, 4, 5, 7, 8, 9, 11));
    }
    else
    {
        vec3f p1=vertices[id_v1].p;
        vec3f p2=vertices[id_v1].p;
        vec3f p3=(p1+p2)/2;
        double error1 = vertex_error(q_bar, p1.x,p1.y,p1.z);
        double error2 = vertex_error(q_bar, p2.x,p2.y,p2.z);
        double error3 = vertex_error(q_bar, p3.x,p3.y,p3.z);
        double min_error = min(error1, min(error2, error3));
        if (error1 == min_error) p_result=p1;
        if (error2 == min_error) p_result=p2;
        if (error3 == min_error) p_result=p3;
    }
    double min_error = vertex_error(q_bar, p_result.x, p_result.y, p_result.z);
    return min_error;
}

You can download the full source+mesh data here DOWNLOAD




#5151475 400% Raytracing Speed-Up by Re-Projection (Image Warping)

Posted by spacerat on 04 May 2014 - 03:01 PM

Yes, its related to temporal antialiasing techniques. However there, the geometry is not reused and needs to be rendered again. For the coordinates I also tried different ways but it turned out that using relative coordinates (such as the zbuffer) tend to accumulate the error over multiple frames, and the image is not consistent anymore then.  

 

Transparency is not yet handled and might need special treatment indeed. Its just for opaque surfaces.

 

To still cache the pixels efficiently even lots of them get covered-up / overwritten, I am using a multi buffer cache. That means the result of a screen is alternately stored in either cache, while both caches are projected to the screen at the beginning before filling the holes by raycasting. That can keep the pixels efficiently, as pixels that are covered up in one frame might already be visible again in the next frame. In general the caching works well. Also its a relaxed caching theme, where not every empty pixel get filled by a ray. The method uses an image filter for filling small holes and only fills holes that are 2x2 pixels large (the threshold can be defined) by raycasting.

 

If the camera changes to a different view, then obviously the cache cannot be used and the first frame will render slower.




#5151336 400% Raytracing Speed-Up by Re-Projection (Image Warping)

Posted by spacerat on 03 May 2014 - 09:42 PM

Intro I have been working a while on this technology and since real-time raytracing is getting faster like with the Brigade Raytracer e.g., I believe this can be an important contribution to this area, as it might bring raytracing one step closer to being usable for video games.

 

Algorithm: The technology exploits temporal coherence between two consecutive rendered images to speed up ray-casting. The idea is to store the x- y- and z-coordinate for each pixel in the scene in a coordinate-buffer and re-project it into the following screen using the differential view matrix. The resulting image will look as below.
 
The method then gathers empty 2x2 pixel blocks on the screen and stores them into an indexbuffer for raycasting the holes. Raycasting single pixels too inefficient. Small holes remaining after the hole-filling pass are closed by a simple image filter. To improve the overall quality, the method updates the screen in tiles (8x4) by raycasting an entire tile and overwriting the cache. Doing so, the entire cache is refreshed after 32 frames. Further, a triple buffer system is used. That means two image caches which are copied to alternately and one buffer that is written to. This is done since it often happens that a pixel is overwritten in one frame, but becomes visible already in the next frame. Therefore, before the hole filling starts, the two cache buffers are projected to the main image buffer.
 
Results: Most of the pixels can be re-used this way as only a fraction of the original needs to be raycated, The speed up is significant and up to 5x the original speed, depending on the scene. The implementation is applied to voxel octree raycasting using open cl, but it can eventhough be used for conventional triangle based raycasting.
 
Limitations: The method also comes with limitations of course. So the speed up depends on the motion in the scene obviously, and the method is only suitable for primary rays and pixel properties that remain constant over multiple frames, such as static ambient lighting. Further, during fast motions, the silhouettes of geometry close to the camera tends to loose precision and geometry in the background will not move as smooth as if the scene is fully raytraced each time. There, future work might include creating suitable image filters to avoid these effects.
 
How to overcome the Limitations: 
 
Ideas to solve noisy silhouettes near the camera while fast motion:

1. suppress unwanted pixels with a filter by analyzing the depth values in a small window around each pixel. In experiments it removed some artifacts but not all - also had quite an impact on the performance.

2. (not fully explored yet) Assign a speed value to each pixel and use that to filter out unwanted pixels

3. create a quad-tree-like triangle mesh in screenspace from the raycasted result. the idea is to get a smoother frame-to-frame coherence with less pixel noise for far pixels and let the zbuffer do the job of overlapping pixels. Its sufficient to convert one tile from the raycasted result to a mesh per frame. Problem of this method : The mesh tiles dont fit properly together as they are raycasted at different time steps. Using raycasting to fill in holes was not very simple which is why I stopped exploring this method further
http://www.farpeek.com/papers/IIW/IIW-EG2012.pdf fig 5b

** Untested Ideas **

4. compute silhouettes based on the depth discontinuity and remove pixels crossing them
5. somehow do a reverse trace in screenspace between two frames and test for intersection
6. use splats to rasterize voxels close to the camera so speckles will be covered
 
You can find the full text here, including paper references.
 
Clipboard01.png
 



#5136674 Geometry Shaders

Posted by spacerat on 05 March 2014 - 08:53 PM

Edit / @mhagain: 

 

I apologize. didnt read the question carefully.

 

Still i believe that here is an performance impact on using geometry shaders as it costs additional computations.




#5135876 GPU Skinned Skeletal Animation Tutorial

Posted by spacerat on 02 March 2014 - 12:01 PM

Today I completed a skinned skeletal animation tutorial, which is very helpful if you are just about to start with game development. 

 

Different from the other tutorials I found in the web, this one is very light weight ( < 800 lines for the main mesh & animation code ) and works well with most modeling environments.

 

Summary

It has the following properties / features:

  • GPU Skinning / Matrix Palette Skinning
  • Bump Mapping (automatic normal map generation)
  • Spheric environment mapping
  • Ogre XML Based
  • Shaders in GLSL
  • Visual Studio 2010
  • Select LOD level with F1..F5

 

It is ready to use, which means you can load and display the animated models in a few lines of code:

static Mesh halo("halo.material",//	 required material file)
		 "halo.mesh.xml",//	 required mesh file
		 "halo.skeleton.xml");// optional skeleton file

int idle = halo.animation.GetAnimationIndexOf("idle");

halo.animation.SetPose(idle,	   // animation id (2 animations, 0 and 1, are available)
  		    time_elapsed); // time in seconds. animation loops if greater than animation time

halo.Draw( vec3f(0,0,0),	// position
	   vec3f(0,0,0),	// rotation
	    0);			// LOD level 

.

Also getting a bone matrix to put a weapon in the hand of the player e.g. is very simple:

int index  = halo.animation.GetBoneIndexOf("joint1"); 
matrix44 m = halo.animation.bones[ index ].matrix;

.

Setting the arm joint individually for shooting a weapon e.g. works as follows:( press F6 in the demo ):

// get the index
int index  = halo.animation.GetBoneIndexOf("joint2"); 
 
// get / modify / set the matrix
matrix44 m = halo.animation.bones[ index ].matrix;
m.x_component()=vec3f(1,0,0);
m.y_component()=vec3f(0,1,0); // set the rotation to identity
m.z_component()=vec3f(0,0,1);
halo.animation.bones[ index ].matrix=m;
 
// re-evaluate the child bones
loopi(0,halo.animation.bones[ index ].childs.size())
{
    halo.animation.EvalSubtree(
        halo.animation.bones[ index ].childs[i], // bone id
        halo.animation.animations[0],            // animation
        -1);                                     // key frame -1 means not use the animation
}

.

Workflow:

  • Design the Model in Maya/MAX/Blender/etc.
  • Export the model using the OgreExporter
  • Convert the model from Ogre binary to Ogre XML (batch file is included)
  • Load the model in the tutorial code

 

Main Skinning in GLSL: 

 

The main skinning is done in the vertex shader and only requires a few lines.

For the shader, the skinning weights are stored in the color information as 3 floats. 

The bone IDs are unpacked from the w coordinate of the position information.

The bone matrixes are stored as simple matrix array.

 

uniform mat4 bones[100];
uniform int  use_skinning;

void main(void)
{
    mat4 mfinal = gl_ModelViewMatrix ;

    // skinning
    if(use_skinning==1)
    {
    	vec3 weights= gl_Color.xyz;
    	vec3 boneid = gl_Vertex.w * vec3( 1.0/128.0 , 1.0 , 128.0 );
    	boneid = (boneid - floor(boneid))*128.0;

    	mat4 mskin  =	bones[int(boneid.x)]*weights.x+
	    		bones[int(boneid.y)]*weights.y+
			bones[int(boneid.z)]*weights.z;
    	mfinal = mfinal * mskin;
    }

    gl_Position	= gl_ProjectionMatrix * mfinal * vec4(gl_Vertex.xyz,1.0);
}

Note: 
Animating Notes for Maya
For Maya, put all animations in one time-line and export them as separate animations.
Ogre Export
Tangents need to be exported as 4D, to include the handedness. The tutorial version does not generate the tangents in the shader as it is faster to read them from the memory.
Bump Mapping
For the Ogre Material file, the bump map needs to be added manually by hand using the texture_bump parameter as follows:
texture Texture\masterchief_base.tif
texture_bump Texture\masterchief_bump_DISPLACEMENT.bmp
Environment Mapping
Environment mapping can be switched on in the material file using the following parameter:
env_map spherical
 
 
Clipboard04.png



#5127168 Geometry Clipmaps Terrain Tutorial with Source

Posted by spacerat on 29 January 2014 - 04:00 AM

After browsing the web for a usable terrain lib, I ended up writing one myself as the others were simply too bloated.

I am sharing the result now as a tutorial and hope it will help the one or other of you to get started with terrain rendering.

The main code as only 200 lines, so is easy to understand.

Performance is around 1000 fps.

 

Key settings:
Space : wireframe view
Enter : Top-down view

 

You can fetch the zip with all required includes from here or from the attachment.

 

Changelog: 

Bugfix applied: glDrawArrays( GL_LINES, 0, vert.size()); (old) -> glDrawArrays( GL_LINES, 0, vert.size()/3); (new / ok)

///////////////////////////////////////////
int grid= 64;			// patch resolution
int levels=5;			// LOD levels
int width=2048,height=2048;     // heightmap dimensions
///////////////////////////////////////////

void DrawScene()
{
	if ( GetAsyncKeyState(VK_ESCAPE) )  exit(0);

	POINT cursor;
	GetCursorPos(&cursor); // mouse pointer position

	bool	wireframe= GetAsyncKeyState(VK_SPACE);	// render wireframe
	bool	topdown	 = GetAsyncKeyState(VK_RETURN);	// view top-down
	float	viewangle= float(cursor.x)/5.0;
	vec3f	viewpos ( (timeGetTime()>>2)&((1<<17)-1) , -(float(cursor.y)/1000.0)* 0.1-0.01 , 0 );

	glClearDepth(1.0f);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_FRONT);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	static int tex_heightmap=0;
	static int tex_terrain=0;

	static bool init=true;
	static Shader shader("Shader");

	static int vbo=0;
	static std::vector<float> vert;

	if(init)
	{
		/*+++++++++++++++++++++++++++++++++++++*/
		// terrain heightmap
		Bmp bmp(width,height,32);
		loopj(0,height) loopi(0,width)
		{
			float x= float(i)/float(width);
			float y= float(j)/float(height);
			float h = (sin(4*M_PI*x)+sin(4*M_PI*y)+sin(16*M_PI*x)*sin(16*M_PI*y))*0.125+0.5;
			((float*)bmp.data)[i+j*width]=h;
		}	
		//bmp.load_float("../result.f32"); // <-- use this for loading raw float map from file
		tex_heightmap = ogl_tex_new(width,height,GL_LINEAR_MIPMAP_LINEAR,GL_REPEAT,GL_LUMINANCE16F_ARB,GL_LUMINANCE,bmp.data, GL_FLOAT);
		/*+++++++++++++++++++++++++++++++++++++*/
		// terrain texture
		loopj(0,height)	loopi(0,width) loopk(0,3)
		{
			bmp.data[(i+j*width)*3+k]=i^j^(k*192);
		}
		tex_terrain = ogl_tex_new(width,height,GL_LINEAR_MIPMAP_LINEAR,GL_REPEAT,GL_RGB,GL_RGB,bmp.data, GL_UNSIGNED_BYTE);
		/*+++++++++++++++++++++++++++++++++++++*/
		// driver info
		std::cout << "GL_VERSION: " << glGetString(GL_VERSION) << std::endl;
		std::cout << "GL_RENDERER: " << glGetString(GL_RENDERER) << std::endl;
		std::cout << "GL_VENDOR: " << glGetString(GL_VENDOR) << std::endl;
		std::cout << "GLU_VERSION: " << gluGetString(GLU_VERSION) << std::endl;
		std::cout << "GLUT_API_VERSION: " << GLUT_API_VERSION << std::endl;
		/*+++++++++++++++++++++++++++++++++++++*/
		// load shaders
		shader.attach(GL_VERTEX_SHADER,"../shader/vs.txt");
		shader.attach(GL_FRAGMENT_SHADER,"../shader/frag.txt");
		shader.link();
		/*+++++++++++++++++++++++++++++++++++++*/
		// make vbo quad patch
		loopj(0,grid+1)
		loopi(0,grid+2)
		{
			loopk(0, ((i==0) ? 2 : 1) )
			{
				vert.push_back(float(i)/grid);
				vert.push_back(float(j)/grid);
				vert.push_back(0);
			}			
			++j;
			loopk(0, ((i==grid+1) ? 2 : 1) )
			{
				vert.push_back(float(i)/grid);
				vert.push_back(float(j)/grid);
				vert.push_back(0);
			}
			--j;
		}
		/*+++++++++++++++++++++++++++++++++++++*/
		glGenBuffers(1, (GLuint *)(&vbo));
		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(float)*vert.size(),&vert[0], GL_DYNAMIC_DRAW_ARB );
		/*+++++++++++++++++++++++++++++++++++++*/
		init=false;
		/*+++++++++++++++++++++++++++++++++++++*/
	}
	glMatrixMode( GL_PROJECTION);
	glLoadIdentity();

	if (topdown)
	{
		glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
		glRotatef(180,1,0,0);
		wireframe^=1;
	}
	else		 
	{
		int vp[4];
		glGetIntegerv(GL_VIEWPORT, vp);
		gluPerspective(90.0,float(vp[2])/float(vp[3]) , 0.0001, 1.0);
		glTranslatef(0,viewpos.y,0);	// set height
		glRotatef(130,1,0,0);		
		glRotatef(viewangle,0,0,1);	// set rotation
	}

	matrix44 mat;
	glGetFloatv(GL_PROJECTION_MATRIX, &mat.m[0][0]);		
	
	// Enable VBO
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo);				
	glEnableClientState(GL_VERTEX_ARRAY);					
	glVertexPointer  ( 3, GL_FLOAT,0, (char *) 0);			

	glEnable(GL_TEXTURE_2D);
	glActiveTextureARB( GL_TEXTURE0 );
	glBindTexture(GL_TEXTURE_2D, tex_heightmap);
	glEnable(GL_TEXTURE_2D);
	glActiveTextureARB( GL_TEXTURE1 );
	glBindTexture(GL_TEXTURE_2D, tex_terrain);

	// Triangle Mesh
	shader.begin();
	shader.setUniform1i("tex_heightmap",0);
	shader.setUniform1i("tex_terrain",1);

	float sxy=2; // scale x/y
	shader.setUniform4f("map_position", 
		-viewpos.x/float(2*512*grid),
		-viewpos.z/float(2*512*grid),0,0);

	loopi(0,levels)
	{
		float ox=(int(viewpos.x*(1<<i))&511)/float(512*grid);
		float oy=(int(viewpos.z*(1<<i))&511)/float(512*grid);

		vec3f scale	(sxy*0.25,sxy*0.25,1);
		shader.setUniform4f("scale" , scale.x,scale.y,1,1);	

		loopk(-2,2) loopj(-2,2) // each level has 4x4 patches
		{
			if(i!=levels-1) if(k==-1||k==0) if(j==-1||j==0) continue;

			vec3f offset(ox+float(j),oy+float(k),0);
			if(k>=0) offset.y-=1.0/float(grid); // adjust offset for proper overlapping
			if(j>=0) offset.x-=1.0/float(grid); // adjust offset for proper overlapping

			//cull
			int xp=0,xm=0,yp=0,ym=0,zp=0;
			looplmn(0,0,0,2,2,2)
			{
				vec3f v = scale*(offset+vec3f(l,m,float(-n)*0.05)); // bbox vector
				vec4f cs = mat * vec4f(v.x,v.y,v.z,1); // clipspace
				if(cs.z< cs.w) zp++;				
				if(cs.x<-cs.w) xm++;	if(cs.x>cs.w) xp++;
				if(cs.y<-cs.w) ym++;	if(cs.y>cs.w) yp++;
			}
			if(zp==0 || xm==8 || xp==8 || ym==8 || yp==8)continue; // skip if invisible
			
			//render
			shader.setUniform4f("offset", offset.x,offset.y,0,0);
			if(wireframe)	glDrawArrays( GL_LINES, 0, vert.size()/3);
			else		glDrawArrays( GL_TRIANGLE_STRIP, 0, vert.size()/3);
		}
		sxy*=0.5;
	}	
	shader.end();

	// Disable VBO
	glDisableClientState(GL_VERTEX_ARRAY);									
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);								
	glutSwapBuffers();
}
///////////////////////////////////////////
int main(int argc, char **argv) 
{ 
  glutInit(&argc, argv);  
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  
  glutInitWindowSize(1024, 512);  
  glutInitWindowPosition(0, 0);  
  glutCreateWindow("Geometry Clipmaps Example");
  glutDisplayFunc(DrawScene);
  glutIdleFunc(DrawScene);
  glewInit();
  wglSwapIntervalEXT(0);
  glutMainLoop();  
  return 0;
}
///////////////////////////////////////////

Terrain_GeometryClipmaps_Tutorial.png

 

Clipboard02.png

Attached Files




#5122944 C++ templates, are they worth it?

Posted by spacerat on 11 January 2014 - 05:40 PM

well, templates can partially be replaced by the new "auto" type in c++




#5086412 3D voxel painter - feedback appreciated

Posted by spacerat on 16 August 2013 - 01:20 AM

>> Could you add different starting sizes, so if I make a character I could work on the hand in a small file and import it to a larger file when done?

 

I have added a menu to let you select the voxel space resolution, so you can create up to 1024^3 now. Also you now have a colorize function.
If you download the zip above, the new functions are built in
 
>> You have a load OBJ option, does this mean it will convert any polygon model to a voxel model?
 
Yes. Its not yet implemented, but will be supported soon



PARTNERS