Real-Time Fur Rendering For Short Haired Creatures


Modern Game Technologies Project


Figure 1.  In the figure above we show an example rendered in real-time with the technique presented in this paper, showing its applicability not just to fur but also to simulate small feathers on this bird model.

I. Introduction

Making characters that look real has been a driving goal of computer graphics for most of the past 30 years.  To complete this task a number of issues come to mind:  character animation, facial animation, shading and lighting, physics, and a host of other effects studied by computer graphicists for over 30 years.  One of the most important and challenging aspects has been the simulation and rendering of fur.  A number of researchers have looked into this issue with varying degrees of success: first with the goal of off-line rendering realism, but more recently at real time performance.  We review the state of the art in real-time rendering of fur and provide a working implementation using a real-time fin and shell technique using the Intel Performance Primitives (IPP) running on mainstream graphics hardware-the Intel 915G. The 915G is DirectX9 capable hardware supporting vertex shader 3.0 and pixel shader 2.0. Our fur rendering technique is fully parameterized:  material properties such as color and thickness can be adjusted as well as the number of fins and shells used for rendering.

Section II describes the previous work in the field, including the most recent real-time academic implementations.  In section III we discuss the theory behind our implementation.  Section IV provides details on the implementation of our fur rendering technique.  In Section V we describe future work in the area of real-time fur rendering.

II. Previous Work

As mentioned in the introduction, a number of researchers have investigated fur rendering.  [Kajiya89] was one of the earliest attempts at accurately depicting fur in a scene.  Additionally, this paper is the first known reference to the word ‘texel’ in the literature. Interestingly, Kajiya actually used it differently than it is commonly used today: rather than using it for shorthand to refer to any dimensional textures texture element, Kajiya defined it specifically as “a 3-dimensional texture map in which both a surface frame-normal, tangent, and binormal-and the parameters of a lighting model are distributed freely throughout a volume.”   Our work attempts to approximate the ray tracing technique presented in this paper by using an approximation for real-time rendering of fur.

Another recent paper intended for non real-time rendering was the work done by [Goldman97] for Industrial Light and Magic to simulate the fur for 101 Dalmations*.  He describes an algorithm that is faster than their ‘realfur’ technique but is suitable when the animals are far enough away from the camera.  The results look very good, however not directly suitable to a real-time implementation given today’s technology.

More recently there has been work in real-time fur rendering [Lengyel01], [Chong04].  Lengyel proposes a real-time fur techn ique for arbitrary surfaces.  They first sample the virtual hair with a particle system and render the results into a volume texture.  Next, they parameterize over a surface using a lapped texture algorithm [Praun00] in a series of concentric shells using this volume texture.   While shells are an acceptable simulation of geometry within a polygon, the silhouette edges of the model look noticeably problematic when observed.  This problem is solved with the introduction of fins.  The fins are placed along polygon edges of the silhouette of the object to eliminate the visual defect along these edges.

[Chang04] implements a technique similar to [Lengyel01] without using lapped textures.  As a preprocess a noise function is used to render into the alpha channel of a texture map.  This texture map is used as the base texture map for the mesh.  For each shell layer being rendered, the threshold of what is used to texture map the shell for that particular level is increased, decreasing the number of texels that are rendered at each later as we move away from the surface.  This simulates the distribution of hair density as we move away from a surface.

III. Overview

Figure 2.   The figure above demonstrates a simple sampling technique in two dimensions, on the left is the intentionally exaggerated fur we would like to model.  In the middle is the set of shells we use to sample the hairs.  On the right is the final set of shells.  Locations that have a line segment above the shell line are samples which represent fur.  These locations will contain material parameters for lighting and shading the fur. And the alpha will be set to opaque or semi-transparent.  For sections for which there was no fur to sample, these will be transparent (alpha channel will be set to 0).

Our technique is inspired by the texture, shell and fin generation presented in [Lengyel01].  First, we generate textures for the shells and fins using the Intel Performance Primitives.  Next, the geometry for fins is created.  Finally, we rendered the image into the framebuffer.  As the geometry is rendered into the framebuffer, the vertex shader calculates the position of shell vertices.  The use of fins and shells is motivated by the fact that they are very aesthetically appealing ways of approximating the ray-tracing implementations presented in [Kajiya89] and [Goldman97] and prove suitable for the short, straight hair we were trying to simulate.

Texture creation has to happen the first time the application is run to procedurally generate the textures used for the shells.  Next, the textures for the fins are generated.  Each set of texture is written out to a file.  There is no need to run these algorithms if the files already exist. Each time the application is run we generate the geometry for the fins before rendering.  The shell geometry is created by moving the original vertex positions outward along the normal in the vertex shader.  In the following sections we will give details on steps 1-4.  Step 5, rendering, is a straightforward implementation that runs on mainstream graphics hardware using DirectX9 vertex and pixel shaders.  In summary, our approach has 5 compo nents:

  • Generate Textures for Shells using IPP
  • Generate Textures for Fins
  • Generate Geometry for Fins
  • Generate Geometry for Shells
  • Rendering using components [1-4]

IV. Implementation Details

4.1 Textures for Shells

In a pre-process we generate the textures for our fins and shells using the Intel Performance Primitive Library.  Shell 0 is created by sampling a Gaussian distribution using the IPP function RandGaussDist().  This function generates pseudo-random samples with a Gaussian distribution:

 ippiAddRandGauss_Direct_8u_C1IR(&bTem[0][0], w, roi, 0, 100, &n);


Each subsequent shell is created by sampling the previous texture with another Gaussian created similarly.  For each pixel in the previous shell, we determine if the pixel in the newly generated Gaussian is above a threshold value.  If so, we permit that pixel to be rendered into the next texture.  If it is not above the threshold it has been filtered out and we instead render a pixel with full transparency and no color, essentially creating a transparent point in space for that pixel.  This is repeated for each shell.  Color of the textures can be adjusted at this point, or in the shader to customize the look and feel of the fur being generated.

Figure 3.  5 shells sampled from a Gaussian distribution using a Gaussian based threshold function.  Shell 0 is the left most shell.  Notice as we move up the shells more and more of the noise is filtered out, effectively simulating the thinning of fur as we move away from the skin.

4.2 Textures for Fins

The fin textures are generated by sampling the set of textures written out for each shell layer.  We pick an edge and sample the texture along that edge.  If is determined to be hair we render our hair color into the texture.  The hair color extends from this position in the texture until the next shell layer.  If we determine that this is empty space we render nothing.  This process is continued through the set of texture samples.  This texture is used for each edge regardless of the edge.

Figure 4.  In the figure above we can see the result of the application of the shell textures in Figure 2.  No fins are used in this figure.

4.3 Geometry for Shells

When the application begins we create geometry for each of th e shells.  The number of shells and distance between the shells is model dependent.  Each shell is created with two triangles to form a quadrilateral by the vertices of the mesh that the fur is being applied.  To obtain the vertex positions of each quadrilateral the vertex positions are translated outward in the direction of the surface normal in the vertex shader.  The distance between each shell is uniform in our demo but can be varied in your application.

4.4 Geometry for Fins

Fin geometry is created along the edges of each polygon by creating a quadrilateral oriented perpendicular to the edge in object space.  The edge geometry is created at load time and used each frame.  Each quadrilateral consists of two triangles and is texture mapped using the texture map created as previously described.

4.5 Run-time

Since we have created the geometry and textures before run-time, rendering is straightforward..  At run-time, we first render the geometry of the model, and then render the geometry, texture mapping with the textures created in section 4.1 and 4.2.  We set alpha to 0.5 for each shell.  We need no special vertex or pixel shader code for rendering the shells, each quad has a single texture map representing ‘fur’ or ‘empty-space’ by the value in the alpha channel.  The vertices of the mesh are pushed out along the normal direction for each shell when rendering.  This translation is done in the vertex shader.  The best way to see this is to take a look at the example code included with this article.

4.6 Noise function

The Intel Performance Primitives are a set of routines optimized for running on x86 CPUs.  While they are optimized for high performance, the primary motivation for their selection was speed of development: the routines were easy to incorporate and reduced the opportunities for bugs in our example implementation.  In particular, the function we chose to use was:

ippiAddRandGauss_Direct_8u_C1IR(&bTem[0][0], w, roi, 0, 100, &n);  


Details of this function can be found in the Appendix of this article.  This function is used to create the texture.  It is also used to sample the texture for each shell .  Details can be seen in the example code included with this article.

V. Future Work

Several areas of future work come to mind.  One is to implement the lapped texture technique as shown in [Lengyel01].  Another is to support the ability to handle physical forces being applied to our fur simulation.  How might we handle when another object presses against our fur?  How would we handle wind?  Also, it would be interesting to add support for fur that curls.  Thinking about shading, we would like to explore the addition of parameters to simulate wetness.

VI. Acknowledgements

Kim Pallister, Harish Nag, and Pete Baker for giving us the time to do this work.  David Reagin, John Van Drasek, and Cody Northrop for technical review.

VI. References

[Chong04] JCChong.  Fur Rendering and Dynamics Using Discrete Shells.  CMPT 461 Final Project:  http://www.sfu.ca/~jcchong/461/.

[Lengyel01] Jerome Lengyel, Emil Praun, Adam Finkelstein, and Hugues Hoppe.  Real-Time Fur over Arbitrary Surfaces.  Symposium in Interactive 3D Graphics, 2001.  Pages 227-232.

[Goldman97]  Dan Goldman.  Fake Fur Rendeing. SIGGRAPH 1997.  Pages 127-134.

[Kajiya89]  James Kajiya and Timothy Kay.  Rendering Fur with Three Dimensional Models.  ACM SIGGRAPH 89.  Pages 271-280. 1989.

[Praun00]  Emil Praun, Adam Finkelstein, and Hugues Hoppe.  Lapped Textures. SIGGRAPH 2000.  Pages 465-470.  2000.

Appendix A: Example Code for Shell Texture Generation

HRESULT generateShellTextures()

{

              HRESULT hr;

              IDirect3DDevice9* pd3dDevice = g_pd3dDevice;

WCHAR str[MAX_PATH];

 

              //generate textures

              ippStaticInit();

              IDirect3DSurface9* pSurface;

              D3DLOCKED_RECT lockedRect;

              BYTE* pBData;

              const int w = 200;

              const int h = 200;

              IppiSize roi = {w, h};

              lockedRect.Pitch = w*3;

              Ipp8u bTem[h][w];

              Ipp32u n = 7;

 

              memset(bTem, 0, w*h);

              ippiAddRandGauss_Direct_8u_C1IR(&bTem[0][0], w, roi, 0, 100, &n);

              //ippiAddRandUniform_Direct_8u_C1IR(&bTem[0][0], w, roi, 0, 256, &n);

pd3dDevice->CreateOffscreenPlainSurface(w, h,

D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);

              pSurface->LockRect(&lockedRect, NULL, D3DLOCK_NOOVERWRITE);

              pBData = (BYTE*)lockedRect.pBits;

          
;    for (int j=0; j<h; j++)

              {

                            for (int k=0; k<w; k++)

                            {

                                          if(bTem[j][k]==0)

                                          {

                                                        *pBData++ = 0;

                                                        *pBData++ = 0;

                                                        *pBData++ = 0;

                                                        *pBData++ = 255;

                                          }

                                          else

                                          {

                                                        *pBData++ = g_shellTextureColor[0];

        &nb
sp;                                               *pBData++ = g_shellTextureColor[1];

                                                        *pBData++ = g_shellTextureColor[2];

                                                        *pBData++ = g_shellTextureColor[3];

                                          }

                            }

              }

              pSurface->UnlockRect();

              WCHAR sTem1[8];

              WCHAR sTem2[128];

              wcscpy(sTem2, L"Media est");

              wcscat(sTem2, (WCHAR*)_itow(0, sTem1, 10));

              wcscat(sTem2, L".bmp");

              D3DXSaveSurfaceToFile(sTem2, D3DXIFF_BMP, pSurface, NULL, NULL);

 

              for (int i=1; i<g_nNumShellLayers; i++)

              {

                            memset(bTem, 0, w*h);

                            ippiAddRandGauss_Direct_8u_C1IR(&bTem[0][0], roi.width, roi, 0, 100, &n);

                            pSurface->LockRect(&lockedRect, NULL, D3DLOCK_NOOVERWRITE);

                            pBData = (BYTE*)lockedRect.pBits;

        &n
bsp;                   for (int j=0; j<h; j++)

                            {

                                          for (int k=0; k<w; k++)

                                          {

                                                        if(bTem[j][k]>150)

                                                        {

                                                                      *pBData++ = 0;

                                                                      *pBData++ = 0;

                                                                      *pBData++ = 0;

                                                                      *pBData++ = 0;

                                                        }

   &nbs
p;                                                    else

                                                        {

                                                                      if (pBData[0]==0&&pBData[1]==0&&pBData[2]==0)

                                                                      {

                                                                                    pBData++;

                                                                                    pBData++;

                                                                                    pBData++;

                                                                                    *pBData++ = 0;

      &nbs
p;                                                               }

                                                                      else

                                                                      {

                                                                                    pBData++;

                                                                                    pBData++;

                                                                                    pBData++;

                                                                                    pBData++;

                                                                &nbs
p;     }

                                                        }

                                          }

                            }

                            pSurface->UnlockRect();

                            WCHAR sTem1[8];

                            WCHAR sTem2[128];

                            wcscpy(sTem2, L"Media est");

                            wcscat(sTem2, (WCHAR*)_itow(i, sTem1, 10));

                            wcscat(sTem2, L".bmp");

                            D3DXSaveSurfaceToFile(sTem2, D3DXIFF_BMP, pSurface, NULL, NULL);

              }

 

              SAFE_RELEASE(pSurface);

}


Appendix B: Example Code for Fin And Shell Texture Generation

void generateFinsAndShells()

{

              IDirect3DDevice9* pd3dDevice = g_pd3dDevice;

 

              generateTextures();

 

              // Create vertex and index buffers for fins

              pd3dDevice->CreateVertexBuffer(sizeof(finVertex)*g_pMesh->GetNumFaces()*3*(g_nNumFinLayers+1), 0, D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE2(0), D3DPOOL_DEFAULT, &g_pFinsVB, NULL);

              pd3dDevice
->CreateIndexBuffer(sizeof(SHORT)*g_pMesh->GetNumFaces()*3*(6*g_nNumFinLayers), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pFinsIB, NULL);

              for (int i=0; i<g_nNumShellLayers; i++)

              {

                            pd3dDevice->CreateVertexBuffer(sizeof(shellVertex)*g_pMesh->GetNumFaces()*3, 0, D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE2(0), D3DPOOL_DEFAULT, &g_pShellsVB[i], NULL);

                            pd3dDevice->CreateIndexBuffer(sizeof(SHORT)*g_pMesh->GetNumFaces()*3, 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pShellsIB[i], NULL);

              }

 

              // Get vertex buffer from mesh

              // Compute face normals

              // Extrude shells and fins

              SHORT* p_IB;

              struct vertexData

              {

                            D3DXVECTOR3 pos;

                            D3DXVECTOR3 nor;

                            FLOAT tu, tv;

              };

              vertexData* p_VB;

 

              finVertex* p_finsVB = new finVertex[g_pMesh->GetNumFaces()*3*(g_nNumFinLayers+1)];

              SHORT* p_finsIB = new SHORT[g_pMesh->GetNumFaces()*3*(6*g_nNumFinLayers)];

 

              shellVertex* p_shellsVB[MAX_SHELLS];

              SHORT* p_shellsIB[MAX_SHELLS];

              for (int i=0; i<g_nNumShellLayers; i++)

              {

                            p_shellsVB[i] = new shellVertex[g_pMesh->Ge
tNumFaces()*3];

                            p_shellsIB[i] = new SHORT[g_pMesh->GetNumFaces()*3];

              }

 

              g_pMesh->LockIndexBuffer(D3DLOCK_READONLY, (void**) &p_IB);

              g_pMesh->LockVertexBuffer(D3DLOCK_NOOVERWRITE, (void**) &p_VB);

              for (int i=0; i<g_pMesh->GetNumFaces()*3; i+=3)

              {

                            // compute face normals

                            D3DXVECTOR3 faceNormal;

                            D3DXVECTOR3 v0, v1;

                            v0.x = p_VB[p_IB[i+1]].pos.x - p_VB[p_IB[i]].pos.x;

                            v0.y = p_VB[p_IB[i+1]].pos.y - p_VB[p_IB[i]].pos.y;

                            v0.z = p_VB[p_IB[i+1]].pos.z - p_VB[p_IB[i]].pos.z;

                            v1.x = p_VB[p_IB[i+2]].pos.x - p_VB[p_IB[i]].pos.x;

                            v1.y = p_VB[p_IB[i+2]].pos.y - p_VB[p_IB[i]].pos.y;

                            v1.z = p_VB[p_IB[i+2]].pos.z - p_VB[p_IB[i]].pos.z;

                            D3DXVec3Cross(&faceNormal, &v0, &v1);

                            D3DXVec3Normalize(&faceNormal, &faceNormal);

 

                            p_finsVB[i*(g_nNumFinLayers+1)].position = p_VB[p_IB[i]].pos;

                 &
nbsp;          p_finsVB[i*(g_nNumFinLayers+1)].tu = 0.0;

                            p_finsVB[i*(g_nNumFinLayers+1)].tv = 0.0;

                            p_finsVB[i*(g_nNumFinLayers+1)+1].position = p_VB[p_IB[i+1]].pos;

                            p_finsVB[i*(g_nNumFinLayers+1)+1].tu = 0.0;

                            p_finsVB[i*(g_nNumFinLayers+1)+1].tv = 1.0;

                            p_finsVB[i*(g_nNumFinLayers+1)+2].position = p_VB[p_IB[i+2]].pos;

                            p_finsVB[i*(g_nNumFinLayers+1)+2].tu = 1.0;

                            p_finsVB[i*(g_nNumFinLayers+1)+2].tv = 0.0;

 

                            for (int j=0; j<g_nNumFinLayers; j++)

                            {

                                          // extrude and store in vertex buffer

p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3].position   = p_VB[p_IB[i]].pos + (float)(j+1)*g_fFinHeight*faceNormal;

                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3].tu = 0.0;

                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3].tv = 0.0;

p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+1].position = p_VB[p_IB[i+1]].pos + (float)(j+1)*g_fFinHeight*faceNormal;

                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+1].tu = 0.0;

                  
;                        p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+1].tv = 1.0;

p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+2].position = p_VB[p_IB[i+2]].pos + (float)(j+1)*g_fFinHeight*faceNormal;

                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+2].tu = 1.0;

                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+2].tv = 0.0;

 

                                          // generate indices in the fins index buffer to create fins

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)] = i*(g_nNumFinLayers+1);

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+1] = i*(g_nNumFinLayers+1)+1;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+2] = i*(g_nNumFinLayers+1)+(j+1)*3;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+3] = i*(g_nNumFinLayers+1)+(j+1)*3;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+4] = i*(g_nNumFinLayers+1)+1;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+5] = i*(g_nNumFinLayers+1)+(j+1)*3+1;

 

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+6] = i*(g_nNumFinLayers+1)+(j+1)*3+1;

              &nb
sp;                           p_finsIB[i*6*g_nNumFinLayers+(j*18)+7] = i*(g_nNumFinLayers+1)+1;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+8] = i*(g_nNumFinLayers+1)+2;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+9] = i*(g_nNumFinLayers+1)+2;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+10] = i*(g_nNumFinLayers+1)+(j+1)*3+2;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+11] = i*(g_nNumFinLayers+1)+(j+1)*3+1;

 

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+12] = i*(g_nNumFinLayers+1)+2;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+13] = i*(g_nNumFinLayers+1);

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+14] = i*(g_nNumFinLayers+1)+(j+1)*3;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+15] = i*(g_nNumFinLayers+1)+(j+1)*3;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+16] = i*(g_nNumFinLayers+1)+(j+1)*3+2;

                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+17] = i*(g_nNumFinLayers+1)+2;

 

                    
        }

 

                            for (int j=0; j<g_nNumShellLayers; j++)

                            {

                                          // extrude and store in vertex buffer

                                          p_shellsVB[j][i].position   = p_VB[p_IB[i]].pos + (float)(j+1)*g_fShellHeight*faceNormal;

                                          p_shellsVB[j][i].normal = faceNormal;

                                          p_shellsVB[j][i].tu = (i+1)%2;

                                          p_shellsVB[j][i].tv = i%2;

                                          p_shellsVB[j][i+1].position = p_VB[p_IB[i+1]].pos + (float)(j+1)*g_fShellHeight*faceNormal;

                                          p_shellsVB[j][i+1].normal = faceNormal;

                                          p_shellsVB[j][i+1].tu = (i+1)%2;

                                          p_shellsVB[j][i+1].tv = (i+1)%2;

                                          p_shellsVB[j][i+2].position = p_VB[p_IB[i+2]].pos + (float)(j+1)*g_fShellHeight*faceNormal;

                   
                       p_shellsVB[j][i+2].normal = faceNormal;

                                          p_shellsVB[j][i+2].tu = i%2;

                                          p_shellsVB[j][i+2].tv = (i+1)%2;

 

                                          // generate indices in the shells index buffer to create shells

                                          p_shellsIB[j][i] = i;

                                          p_shellsIB[j][i+1] = i+1;

                                          p_shellsIB[j][i+2] = i+2;

                            }

              }

              g_pMesh->UnlockVertexBuffer();

              g_pMesh->UnlockIndexBuffer();

 

              // copy to vertex and index buffers

              D3DXVECTOR3* p_VBa;

              SHORT* p_IBa;

              if (g_nNumFinLayers>0)

              {

                            g_pFinsVB->Lock(0, 0, (void**) &p_VBa, 0);

                            memcpy(p_VBa, p_finsVB, sizeof(finVertex)*g_pMesh->GetNumFaces()*3*(g_nNumFinLayers+1));

                            g
_pFinsVB->Unlock();

                            g_pFinsIB->Lock(0, 0, (void**) &p_IBa, 0);

                            memcpy(p_IBa, p_finsIB, sizeof(SHORT)*g_pMesh->GetNumFaces()*3*2*3*g_nNumFinLayers);

                            g_pFinsIB->Unlock();

              }

              for (int i=0; i<g_nNumShellLayers; i++)

              {

                            g_pShellsVB[i]->Lock(0, 0, (void**) &p_VBa, 0);

                            memcpy(p_VBa, p_shellsVB[i], sizeof(shellVertex)*g_pMesh->GetNumFaces()*3);

                            g_pShellsVB[i]->Unlock();

                            g_pShellsIB[i]->Lock(0, 0, (void**) &p_IBa, 0);

                            memcpy(p_IBa, p_shellsIB[i], sizeof(SHORT)*g_pMesh->GetNumFaces()*3);

                            g_pShellsIB[i]->Unlock();

              }

 

              delete[] p_finsVB;

              delete[] p

For more complete information about compiler optimizations, see our Optimization Notice.