Difference between revisions of "Sculpted Prims: Technical Explanation"

From Second Life Wiki
Jump to navigation Jump to search
(Do not set image_desc to 8. Add correct TGA footer.)
 
(10 intermediate revisions by 6 users not shown)
Line 10: Line 10:
The alpha channel (if any) in position maps is currently unused, so we have 24 bits per pixel giving us 8 bits per color channel, or values from 0-255. Each color channel represents an axis in 3D space, where red, green, and blue map to X, Y, and Z respectively. If you're mapping from other software, keep in mind that not all 3D software will name their axes the same as SL does. If you have a different default orientation in SL than in your designer, compare the axes and swap where appropriate.
The alpha channel (if any) in position maps is currently unused, so we have 24 bits per pixel giving us 8 bits per color channel, or values from 0-255. Each color channel represents an axis in 3D space, where red, green, and blue map to X, Y, and Z respectively. If you're mapping from other software, keep in mind that not all 3D software will name their axes the same as SL does. If you have a different default orientation in SL than in your designer, compare the axes and swap where appropriate.


The color values map to an offset from the origin of the object <0,0,0>, with values less than 128 being negative offsets and values greater than 128 being positive offsets. An image that was entirely <128,128,128> pixels (flat gray) would represent a single dot in space at the origin.
The color values map to an offset from the origin of the object <0,0,0>, with values less than 128 being negative offsets and values greater than 127 being positive offsets. Consider a prim of scale 1 by 1 by 1 meter.  An image that is solid black through-out, red, green, blue (RGB) value <0,0,0>, would map all the vertices of that sculpted prim to a single coordinate, <-0.5,-0.5,-0.5>, relative to the prim's center, while an image that was entirely white through-out, RGB value <255,255,255>, maps all that sculpty's vertices to a single coordinate, <0.5,0.5,0.5>, relative to the prim's center.  


On a sculpted prim that has a size of one meter, the color values from 0-255 map to offsets from -0.5 to 0.496 meters from the center. Combined with the scale vector that all prims in Second Life possess, sculpted prims have the nearly same maximum dimensions as regular procedural prims (9.961 meter diameter)The last 39mm is lost due to the fact that the maximum color value is 255, whereas 256 would be required to generate a positive offset of exactly 0.5.
For each color, red, green, or blue, there are 256 possible values ( 0 being the first possible value, and 255 being the 256th ). 256 is an even numberEven numbers do not have a single median value, they have two, and therefore there is no color value that will map a vertex exactly at the prim's center, or exactly along the prim's x, y, or z center-line.


==Texture Mapping==
==Texture Mapping==
Line 23: Line 23:
This analysis is based on 1.22 client usage.  
This analysis is based on 1.22 client usage.  


'''Warning:''' A s of release candidate 1.20.14, 32x32, 64x64 and 128x128 textures are stored lossless JPEG2000 compression. If the texture is sized outside this compression artifacts exist and can corrupt your shape ruining any attempt at photo realistic texturing. Also the assert server block doesn't recognize anything above 128x128 as a sculptie even thought it will function if placed in the sculptie window (actually any texture can be placed into the window.)
'''Warning:''' As of 1.20.14, 32x32, 64x64 and 128x128 textures are stored lossless JPEG2000 compression if you check the lossless compression box in the upload window. If the texture is sized outside this compression artifacts exist and can corrupt your shape ruining any attempt at photo realistic texturing. Also the assert server block doesn't recognize anything above 128x128 as a sculptie even though it will function if placed in the sculptie window (actually any texture can be placed into the window.)
'''Important:''' When we refer to the texture in terms of how the viewer code sees it, the first row (0) is the bottom row of your texture. For the columns they are left to right.


The Second life viewer will at it's highest level of detail render a grid of vertices in the following form (for a sculpted prim with spherical stitching):
'''Important:''' When we refer to the texture in terms of how the viewer code sees it, the first row (00 - F0) is the top row of your texture. For the columns they are left to right.
 
[[Image:Mesh.png]]
 
The Second life viewer will at its highest level of detail render a grid of vertices in the following form (for a sculpted prim with spherical stitching):
:1 top row of 33 vertices all mapped to a pole
:1 top row of 33 vertices all mapped to a pole
:31 rows of 33 vertices taken from your texture
:31 rows of 33 vertices taken from your texture
Line 55: Line 58:
==Examples==
==Examples==


A pack of sculpture maps was available for download at www.jhurliman.org/download/sculpt-tests.zip but isn't anymore.
A pack of sculpture maps was available for download at http://www.jhurliman.org/download/sculpt-tests.zip but isn't anymore.




Line 71: Line 74:


<lsl>
<lsl>
  // Test program for sculptures -- Aleric Inglewood, Feb 2009.
// Test program for sculptures -- Aleric Inglewood, Feb 2009.
// Fixed to add TGA footer - Oct 2013.
 
#include <cmath>
#include <stdint.h>
#include <sys/types.h>
#include <cstring>
#include <algorithm>
#include <fstream>
#include <iostream>
 
// TGA header definition.
#pragma pack(push,1) // Pack structure byte aligned
struct tga_header
{
  uint8_t  id_length;              // Image id field length.
  uint8_t  colour_map_type;        // Colour map type.
  uint8_t  image_type;            // Image type.
  uint16_t  colour_map_index;      // First entry index.
  uint16_t  colour_map_length;      // Colour map length.
  uint8_t  colour_map_entry_size;  // Colour map entry size.
  uint16_t  x_origin;              // x origin of image.
  uint16_t  y_origin;              // u origin of image.
  uint16_t  image_width;            // Image width.
  uint16_t  image_height;          // Image height.
  uint8_t  pixel_depth;            // Pixel depth.
  uint8_t  image_desc;            // Image descriptor.
};


   #include <cmath>
struct tga_footer
   #include <stdint.h>
{
   #include <sys/types.h>
   uint8_t  zeroes[8];
  #include <cstring>
   char    magic[18];   // Length of "TRUEVISION-XFILE." including trailing 0.
  #include <algorithm>
};
  #include <fstream>


  // TGA header definition.
int main()
  #pragma pack(push,1) // Pack structure byte aligned
{
   struct tga_header
   int const width = 64;
   {
   int const height = 64;
    uint8_t   id_length;             // Image id field length.
   int const bpp = 24;
    uint8_t   colour_map_type;       // Colour map type.
  float const scale = 255.0f;
    uint8_t   image_type;             // Image type.
   size_t filesize = sizeof(tga_header) + width * height * bpp / 8 + sizeof(tga_footer);
    uint16_t  colour_map_index;       // First entry index.
  uint8_t* tga_buf = new uint8_t [filesize];
    uint16_t  colour_map_length;     // Colour map length.
 
    uint8_t   colour_map_entry_size; // Colour map entry size.
  tga_header tga;
    uint16_t  x_origin;              // x origin of image.
  std::memset(&tga, 0, sizeof(tga_header));
    uint16_t y_origin;               // u origin of image.
   tga.pixel_depth = bpp;
    uint16_t  image_width;            // Image width.
  tga.image_width = width;
    uint16_t  image_height;           // Image height.
  tga.image_height = height;
    uint8_t   pixel_depth;           // Pixel depth.
   tga.image_type = 2;  // Uncompressed.
    uint8_t   image_desc;            // Image descriptor.
   std::memcpy(tga_buf, &tga, sizeof(tga_header));
   };


   int main()
   tga_footer footer;
   {
   std::memset(&footer, 0, sizeof(tga_footer));
    int const width = 64;
  strcpy(footer.magic, "TRUEVISION-XFILE.");
    int const height = 64;
    int const bpp = 24;
    float const scale = 255.0f;
    size_t filesize = sizeof(tga_header) + width * height * bpp / 8;
    uint8_t* tga_buf = new uint8_t [filesize];


    tga_header tga;
   uint8_t* out = tga_buf + sizeof(tga_header);
    std::memset(&tga, 0, sizeof(tga_header));
    tga.pixel_depth = bpp;
    tga.image_width  = width;
    tga.image_height = height;
    tga.image_type = 2;   // Uncompressed.
    tga.image_desc = 8;  // 8 bits per component.
    std::memcpy(tga_buf, &tga, sizeof(tga_header));


    uint8_t* out = tga_buf + sizeof(tga_header);
  for (int y = 0; y < height; ++y)
     for (int y = 0; y < height; ++y)
  {
     for (int x = 0; x < width; ++x)
     {
     {
       for (int x = 0; x < width; ++x)
       double r, g, b;
      {
        double r, g, b;


        r = (double)x / (width - 1);
      r = (double)x / (width - 1);
        g = (double)y / (height - 1);
      g = (double)y / (height - 1);
        b = 1.0;
      b = 1.0;


        // None of these points are taken into account in a sculptie.
      // None of these points are taken into account in a sculptie.
        if (x % 2 == 1)
      if (x % 2 == 1)
          b = 0.5;
        b = 0.5;
        if (y % 2 == 1)
      if (y % 2 == 1)
          b = 0.5;
        b = 0.5;


        // Except those (depending on the stitching type).
      // Except those (depending on the stitching type).
        if (x == width - 1)
      if (x == width - 1)
          b = 0.98;            // plane.
        b = 0.98;            // plane.
        if (y == height - 1)
      if (y == height - 1)
          b = 0.98;            // plane and cylinder.
        b = 0.98;            // plane and cylinder.
        // In the spherical stitching, these two are the poles.
      // In the spherical stitching, these two are the poles.
        if (x == 32)
      if (x == 32)
        {
      {
          if (y == 0 || y == 63)
        if (y == 0 || y == 63)
           b = 0.7;
           b = 0.7;
        }
      }


        // TGA format writes BGR ...
      // TGA format writes BGR ...
        *out++ = (uint8_t)(round(b * scale));
      *out++ = (uint8_t)(round(b * scale));
        *out++ = (uint8_t)(round(g * scale));
      *out++ = (uint8_t)(round(g * scale));
        *out++ = (uint8_t)(round(r * scale));
      *out++ = (uint8_t)(round(r * scale));
      }
     }
     }
  }
  std::memcpy(out, &footer, sizeof(tga_footer));


    std::ofstream file;
  std::ofstream file;
    file.open("testsculpture.tga");
  file.open("testsculpture.tga");
    file.write((char*)tga_buf, filesize);
  file.write((char*)tga_buf, filesize);
    file.close();
  file.close();
    delete [] tga_buf;
  delete [] tga_buf;
  }
}
</lsl>
</lsl>




[[Category:Sculpted Prims]]
[[Category:Sculpted Prims]]

Latest revision as of 15:58, 15 October 2013

Introduction

Sculpted prims are three dimensional meshes created from textures. Each texture is a mapping of vertex positions, where at full resolution each pixel would be one vertex, this can be less due to sampling (read below how the Second Life viewer treats your data). Each row of pixels (vertices) links back to itself, and for every block of four pixels two triangles are formed. At the top and bottom the vertices link to their respective pole.

Sculpted-prim-explanation.png

Pixel Format

The alpha channel (if any) in position maps is currently unused, so we have 24 bits per pixel giving us 8 bits per color channel, or values from 0-255. Each color channel represents an axis in 3D space, where red, green, and blue map to X, Y, and Z respectively. If you're mapping from other software, keep in mind that not all 3D software will name their axes the same as SL does. If you have a different default orientation in SL than in your designer, compare the axes and swap where appropriate.

The color values map to an offset from the origin of the object <0,0,0>, with values less than 128 being negative offsets and values greater than 127 being positive offsets. Consider a prim of scale 1 by 1 by 1 meter. An image that is solid black through-out, red, green, blue (RGB) value <0,0,0>, would map all the vertices of that sculpted prim to a single coordinate, <-0.5,-0.5,-0.5>, relative to the prim's center, while an image that was entirely white through-out, RGB value <255,255,255>, maps all that sculpty's vertices to a single coordinate, <0.5,0.5,0.5>, relative to the prim's center.

For each color, red, green, or blue, there are 256 possible values ( 0 being the first possible value, and 255 being the 256th ). 256 is an even number. Even numbers do not have a single median value, they have two, and therefore there is no color value that will map a vertex exactly at the prim's center, or exactly along the prim's x, y, or z center-line.

Texture Mapping

The position map of a sculpted prim also doubles as the UV map, describing how a texture will wrap around the mesh. The image is already an explanation of what vertices correlate to what pixel, which is used to generate UV coordinates for vertices as they are created. This presents a big advantage for texture mapping over procedural prims created in Second Life because you can do all of your texturing in a 3D modeling program such as Maya, and when you export the position map you know the UV coordinates will be exactly preserved in Second Life.


Rendering in Second Life viewer

This analysis is based on 1.22 client usage.

Warning: As of 1.20.14, 32x32, 64x64 and 128x128 textures are stored lossless JPEG2000 compression if you check the lossless compression box in the upload window. If the texture is sized outside this compression artifacts exist and can corrupt your shape ruining any attempt at photo realistic texturing. Also the assert server block doesn't recognize anything above 128x128 as a sculptie even though it will function if placed in the sculptie window (actually any texture can be placed into the window.)

Important: When we refer to the texture in terms of how the viewer code sees it, the first row (00 - F0) is the top row of your texture. For the columns they are left to right.

Mesh.png

The Second life viewer will at its highest level of detail render a grid of vertices in the following form (for a sculpted prim with spherical stitching):

1 top row of 33 vertices all mapped to a pole
31 rows of 33 vertices taken from your texture
1 bottom row of 33 vertices all mapped to a pole

For each row the 33rd vertex is the same as the 1st, this stitches the sculpture at the sides. The poles are determined by taking the pixel on width/2 on the first and last row of your texture. (In a 64x64 texture that would be the 33rd column (pixels (32, 0) and (32, 63)) as we start counting rows and columns from 0).

For example, a spherical sculpted prim at the highest level of detail Second Life supports requires a mesh of 32 x 33 vertices. If we have a 64x64 texture, this grid of vertices is sampled from the texture at the points:

 (32,0)  (32,0)  (32,0) ...  (32,0)  (32,0)  (32,0)
  (0,2)   (2,2)   (4,2) ...  (60,2)  (62,2)   (0,2)
   .                                       . 
   .                                       .
   .                                       .
 (0,60)  (2,60)  (4,60) ... (60,60) (62,60)  (0,60)
 (0,62)  (2,62)  (4,62) ... (60,62) (62,62)  (0,62)
(32,63) (32,63) (32,63) ... (32,63) (32,63) (32,63)

The other types of sculpted prim stitching (torus, plane, cylinder) work similarly, but without the complication of poles. The torus requires a grid of 32x32 points (at the highest level of detail), which it samples at pixel positions 0, 2, 4, ..., 60, 62 in each direction on the texture. The plane requires a 33x33 grid, which it samples at pixel positions 0, 2, 4, ..., 60, 62, 63 in each direction. The cylinder requires a 32x33 grid, which it samples using the torus scheme in the horizontal direction and the plane scheme in the vertical.

It is recommended to use 64x64 images (32x32 textures are not officially supported for sculpted prim use and may give unexpected results). In theory 32x32 should give you (almost) the same quality but current code will not use your last (top!) row of your texture both as vertices and as the row to get the pole from (for a spherical sculpted prim). It will trigger generation of a pole 1 row early and you'll hence get your 2 last rows of vertices filled with the pole. There are however 33 vertices from the north to the south pole of a spherical sculpted prim, and so we would not expect a 32x32 texture to contain quite enough information to determine the sphere.

The next levels of detail are 17 and 9 positions (for the plane scheme sampling), or 16 and 8 (for the torus scheme sampling) which are taken in similar ways to above but with a sparser sampling. For example, instead of sampling at positions 0, 2, 4, ..., 60, 62, 63 (to get 33 points), it samples at positions 0, 4, 8, ..., 56, 60, 63 (to get 17 points). Make sure the most important pixels are positioned on those positions that are used at every LOD.

The quality of your final result is also determined by the JPEG compression. For organic shapes you may find that lossy compression is sufficient for your needs, otherwise use a 64x64 texture with lossless compression (and check "Use lossless compression" when uploading).

Examples

A pack of sculpture maps was available for download at http://www.jhurliman.org/download/sculpt-tests.zip but isn't anymore.


An apple sculpt shown in the sculptedpreview program Sculpted-preview-03.png


An S-shaped sculpture shown in the latest version of sculptedpreview, with the sculpture map also applied as the texture map Sculpted-preview-04.png


The following C++ program writes to a file "testsculpture.tga". The generated sculpture image can be used to understand the stitching type, and which pixels are used. View a prim in wireframe mode with this image both as sculpture as well as texture and change the stitching type.

<lsl> // Test program for sculptures -- Aleric Inglewood, Feb 2009. // Fixed to add TGA footer - Oct 2013.

  1. include <cmath>
  2. include <stdint.h>
  3. include <sys/types.h>
  4. include <cstring>
  5. include <algorithm>
  6. include <fstream>
  7. include <iostream>

// TGA header definition.

  1. pragma pack(push,1) // Pack structure byte aligned

struct tga_header {

 uint8_t   id_length;              // Image id field length.
 uint8_t   colour_map_type;        // Colour map type.
 uint8_t   image_type;             // Image type.
 uint16_t  colour_map_index;       // First entry index.
 uint16_t  colour_map_length;      // Colour map length.
 uint8_t   colour_map_entry_size;  // Colour map entry size.
 uint16_t  x_origin;               // x origin of image.
 uint16_t  y_origin;               // u origin of image.
 uint16_t  image_width;            // Image width.
 uint16_t  image_height;           // Image height.
 uint8_t   pixel_depth;            // Pixel depth.
 uint8_t   image_desc;             // Image descriptor.

};

struct tga_footer {

 uint8_t  zeroes[8];
 char     magic[18];   // Length of "TRUEVISION-XFILE." including trailing 0.

};

int main() {

 int const width = 64;
 int const height = 64;
 int const bpp = 24;
 float const scale = 255.0f;
 size_t filesize = sizeof(tga_header) + width * height * bpp / 8 + sizeof(tga_footer);
 uint8_t* tga_buf = new uint8_t [filesize];
 tga_header tga;
 std::memset(&tga, 0, sizeof(tga_header));
 tga.pixel_depth = bpp;
 tga.image_width  = width;
 tga.image_height = height;
 tga.image_type = 2;   // Uncompressed.
 std::memcpy(tga_buf, &tga, sizeof(tga_header));
 tga_footer footer;
 std::memset(&footer, 0, sizeof(tga_footer));
 strcpy(footer.magic, "TRUEVISION-XFILE.");
 uint8_t* out = tga_buf + sizeof(tga_header);
 for (int y = 0; y < height; ++y)
 {
   for (int x = 0; x < width; ++x)
   {
     double r, g, b;
     r = (double)x / (width - 1);
     g = (double)y / (height - 1);
     b = 1.0;
     // None of these points are taken into account in a sculptie.
     if (x % 2 == 1)
       b = 0.5;
     if (y % 2 == 1)
       b = 0.5;
     // Except those (depending on the stitching type).
     if (x == width - 1)
       b = 0.98;             // plane.
     if (y == height - 1)
       b = 0.98;             // plane and cylinder.
     // In the spherical stitching, these two are the poles.
     if (x == 32)
     {
       if (y == 0 || y == 63)
         b = 0.7;
     }
     // TGA format writes BGR ...
     *out++ = (uint8_t)(round(b * scale));
     *out++ = (uint8_t)(round(g * scale));
     *out++ = (uint8_t)(round(r * scale));
   }
 }
 std::memcpy(out, &footer, sizeof(tga_footer));
 std::ofstream file;
 file.open("testsculpture.tga");
 file.write((char*)tga_buf, filesize);
 file.close();
 delete [] tga_buf;

} </lsl>