Basic Windows F-Tex Tools tutorial to make Virtual Textures

Here you find pointers to utilities that help you create addons for Celestia.
Avatar
Topic author
Shadow-Dragon-777
Posts: 79
Joined: 03.11.2021
Age: 30
With us: 3 years
Location: A Galaxy Far Far Away....

Basic Windows F-Tex Tools tutorial to make Virtual Textures

Post #1by Shadow-Dragon-777 » 26.07.2022, 19:23

For those who are newer, F-Tex is a tool that can turn large textures (like 64K) into tiles automatically. This is a tutorial for Windows users as a large number of users have Windows.
Developed by Fridger Schrempp in 2007, the usage and know how of these tools has been lost to many and a guide to installing them is not easy to find. I did some digging in the wayback machine and found a post from 2008 by Bob Hegwood on the discontinued Celestialmatters forum however it is poorly worded and asks you to download a bunch of unneeded extra stuff. There is a copy of F-Tex already posted in the forum though I will be posting a copy here too.
A lot of people may be unfamiliar to using F-Tex tools because the people using it are used to making textures on a program with flashy windows, not typing word based commands with the command prompt. I have decided to make a guide on using its basic features because it was a pain for me to figure out how to do this without an easy to find, up to date, proper guide.
NOTE: You MUST be System Administrator to use F-Tex.
STEP 1... DOWNLOAD FTEX
F-TexTools-2.0pre2.zip
F-Tex Tools
(7.48 MiB) Downloaded 264 times

First step is to download F-tex in the attachment above. Then, right click the zip folder and extract to unzipped folder.
STEP 2... INSTALL FTEX
Go into the folder and open Win32_PC.bin. Within that folder is "F-TexTools-2.0pre2" as an application. Run the application and have it install, preferably in your Program Files (x86) but you can install it wherever.
ftex install app.png
F-Tex install location

STEP 3... IMPLEMENT FTEX
This is where people get confused... they go into this program and start double clicking stuff then get frustrated when it does nothing and abandon it. Or they go straight into the command prompt and also does nothing.
The reason is you have to configure Windows to use it first.
1. Go into your control panel then into "Advanced System Settings" or type it into program search at the bottom left and into "view advanced system settings"
2. Click "Environment Variables" then in "System Variables" locate "Path" and click it to highlight it.
ftex environment variables.png
Environment Variables

3. Click "Edit". Now there will be a list, click the bottommost entry and click "New", then click "browse" with the cursor in a new line.
Locate the location of the Ftex install folder, usually it will be in the your Program Files (x86) in the Drive letter folder. Click on the "F-TexTools" folder and click "Ok".
variables path.png
File path for Environment variables to Ftex

The folder path should now appear in the list. With it, click "ok" out of the windows.
STEP 4... FINDING MAPS FOR FTEX
Next up, go into an image processing program and make or download a big texture in it, I used an Enceladus texture from NASA in gimp for this example upscaled and turned into a 16384 x 8192 but any texture using exponent powers of 2 will work. In Gimp, use the following settings when exporting as png (the compression level can be anything but I use 9 for saving space to share addons) Do not check any of the unchecked stuff or Ftex will glitch out:
Gimp png settings.png
Png settings in GIMP

STEP 5... USING FTEX
Now that we have an image, go into the folder you saved the png in.
click on the folder path at the top and type "cmd" and hit Enter
The command prompt will appear with the folder path specified.

Type in "png2bin" < (texture name.png) > (texture name.bin) without brackets

Example: png2bin <Enceladus.png> Enceladus.bin

The png image should then be converted to a binary file.

FINALLY THE RESULTS:
There is one FINAL STEP

Images come in a number of channels
Channel Number "1" is Grayscale
Channel number "3" is RGB color
Channel number "4" is color with transparency

Levels in Celestia You should know how they work, "2" for level2 "5" for level5 etc.

PNG compression "0" is uncompressed as in it just takes up more space but opens faster. Compression "9" is least space but opens and runs slower. This does NOT affect image quality. It can be a value from 0-9.

USE THE .BIN FILE, NOT THE .PNG FILE FOR THIS STEP OR IT WILL NOT WORK!!!

Type "txtiles" (channel number) (image width in pixels) (level number in celestia) (PNG compression number) < (texture name.bin)
Using Ftex example.png
Example using an Enceladus map

For Example: txtiles 3 16384 2 9 <Enceladus.bin

Converts my 16K Enceladus.bin into a series of square color tiles that are 2K in resolution and at png compression level 9.

And Bingo! You should have yourself a series of tiles correctly labeled for your VT!!!
VT tiles.png
Level2 tiles for Enceladus!!!


I hope this guide was a lot of help because hopefully this makes people regain interest in making VTs again without my prior pain of manually creating and renaming tiles. My days of wasting a whole week renaming tiles for a 64K virtual texture are over!!!

Avatar
Topic author
Shadow-Dragon-777
Posts: 79
Joined: 03.11.2021
Age: 30
With us: 3 years
Location: A Galaxy Far Far Away....

Post #2by Shadow-Dragon-777 » 28.07.2022, 19:51

There is also nmtools, however, I have not been able to get it working without it causing artifacts. The tool is set up the same way as steps 1-3 with Ftex.
nmtools-2.0pre2.zip
nmtools
(3.89 MiB) Downloaded 246 times

The tools requires you to save a bumpmap at 16 bit integer in B/W with no gamma.
You can do this in GIMP with a bumpmap by going to "image - precision - 16 bit integer" then "image - mode - B/W" and with only a single layer, right click it and "remove alpha channel" if available.

Then you use png2bin to convert the image to binary.

Then you type in:
"nms" (object radius) (image width) (exaggeration) <(image name.png)> (image name.ppm)


Again, I have had plenty of trouble with this and the poles seem to have induced artifacts occur and I am leaving here for others to experiment on and perhaps figure out.

Avatar
John Van Vliet
Posts: 2944
Joined: 28.08.2002
With us: 22 years 2 months

Post #3by John Van Vliet » 29.07.2022, 00:57

normally i convert a 16 bit signed tiff ( YES save them as a tiff image ) to raw using " Gmic" then use nms to make a normal map


gmic 4k.Ariel.DEM.inpaint.16s.tiff -o 4k.Ariel.DEM.inpaint.16s.raw,short

then

nms 3000 4096 < 4k.Ariel.DEM.inpaint.16s.raw > 4k.Ariel.Normal.png

4k.Ariel.Normal.png

Avatar
John Van Vliet
Posts: 2944
Joined: 28.08.2002
With us: 22 years 2 months

Post #4by John Van Vliet » 30.07.2022, 01:37

Here is a small hack of nms to use 16 bit UNSIGNED integer raw image

this should make using it a bit easier


Code: Select all


//
//  Authors:  Chris Laurel, Fridger Schrempp & Robert Skuridin & John Van Vliet
//
//  claurel@gmail.com
//  fridger.schrempp@desy.de
//  skuridin1@mail.ru
//  john.k.vanvliet@gmail.com

// The program reads an elevation map in unsigned 16-bit raw format
// from STDIN. It outputs to STDOUT a normalmap (PNG format),
// corrected for spherical geometry.


#include <iostream>
#include <cstdio>
#include <math.h>
#include <string>
#include <vector>
#include <ctime>
#include <png.h>
#include <iomanip>

#ifdef _WIN32
   #include <io.h>
   #include <fcntl.h>
#endif

#if _MSC_VER >= 1400  // MSVC 2005/8
   #define SSCANF sscanf_s
#else
   #define SSCANF sscanf
#endif

using namespace std;

const string version = "2.0, November 2008, authors: C. Laurel, F. Schrempp and R. Skuridin J. Van Vliet\n";

int byteSwap = 0;
int kernelSize = 2;
int halfKernelSize = 1;

int IsLittleEndian()
{
   ushort word = 0;
   if((*(char *)& word) != 0x21 )
       return 0;
   else
       return 1;
}

ushort readu16(FILE *in)
{
    ushort b2;
   
    fread(&b2, 2, 1, in);

    if (byteSwap == 1)
       b2 = ((b2 & 0xff00) >> 8) | ((b2 & 0x00ff) << 8);
   
    return (ushort) b2;
}

ushort* readRowU16(FILE* in, int width)
{
    ushort* row = new ushort[width];
    for (int i = 0; i < width; i++)
        row[i] = readu16(in);

    return row;
}

int main(int argc, char* argv[])
{
    const double pi = 3.1415926535897932385;
    int width  = 0;
    int height = 0;
    double radius        = 0.0;
    double exag         = 1.0;
    double heightmapunit = 1.0;
    vector<string> pcOrder(2);
   
   pcOrder[0] = "Big-endian (PPC-MAC,...)";
   pcOrder[1] = "Little-endian (Intel-PC, Intel-MAC,...)";
              
    if (argc < 3 || argc > 6)
    {
        cerr << "\nUsage: nms.u16  <bodyradius> <width> [<exag> [<byteswap> [ <heightmapunit> OutputImage.png]]]\n\n";
        cerr << "Version "<< version << endl;
        cerr << "-------------------------------------------------------\n";
        cerr << "The program reads an elevation map in unsigned 16-bit integer \n";
        cerr << "raw format from STDIN. It outputs to STDOUT a normalmap (PNG),\n";
        cerr << "corrected for spherical geometry\n\n";
        cerr << "Units    : bodyradius[km], width[pixel]\n";
        cerr << "Assume   : aspect ratio = width : height = 2 : 1\n\n";
        cerr << "Defaults : exag = 1.0 (exaggeration factor, recommended: exag ~ 2.5)\n";
        cerr << "           byteswap = 0 <=> byte ordering of input file and computer are equal\n";
       
        cerr << "           heightmapunit = 1.0 (length of one heightmap unit in meters)\n\n";
        cerr << "-------------------------------------------------------\n\n";
        cerr << "You computer uses +++ "<<pcOrder[IsLittleEndian()]<<" +++ byte order!\n";
        cerr << "If different from byte order of input file, use byteswap = 1\n\n"; 
        cerr << "Reference: bodyradius = 6378.140 (Earth)\n";
        cerr << "                      = 3396.0   (Mars)\n\n";
        return 1;
    }
   else
   {
       if (SSCANF(argv[1], "%lf", &radius)  != 1)
       {
          cerr<<"Read error: body_radius\n";
          return 1;
       }
             
       if (SSCANF(argv[2], "%d", &width)  != 1)
       {
          cerr << "Bad image dimensions.\n";
           return 1;
       }
       height = width / 2;
       
       if (argc > 3)
       {       
          if (SSCANF(argv[3], "%lf", &exag)  != 1)          
          {
             cerr<<"Read error: exag\n";
             return 1;
          }
       
          if (argc > 4)
          {
             if (SSCANF(argv[4], "%d", &byteSwap) != 1)
             {
                cerr << "Bad byteorder specs.\n";
                 return 1;
             };

             if (argc == 6)
             {          
                if (SSCANF(argv[5], "%lf", &heightmapunit) != 1)                 
                {
                   cerr<<"Read error: heightmapunit\n";
                   return 1;
                }
            }
         }
      }   
   }
   
      #ifdef _WIN32
      if (_setmode(_fileno(stdin), _O_BINARY) == -1 )
       {
          cerr<<"Binary read mode from STDIN failed\n";
          return 1;
       }

      if (_setmode(_fileno(stdout), _O_BINARY) == -1 )
       {
          cerr<<"Binary write mode via STDOUT failed\n";
          return 1;
       }
      #endif

   //   
   // Output to STDOUT in PNG format
   //           
   png_structp png;
     png_infop info;

   if ((png = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL,NULL)) == NULL)
   {
       fprintf(stderr, "Failed creating PNG write structure (out of memory?)");
       exit(99);
   }
     // Allocate image info
   if ((info = png_create_info_struct (png)) == NULL)
   {
       fprintf(stderr, "Failed creating PNG info structure (out of memory?)");
       exit(99);
     }
    if (setjmp (png_jmpbuf (png)))
   {
       fprintf(stderr, "Failed setting the PNG jump value (errors)");
       exit(99);
   }
            
   png_init_io(png, stdout);   
   png_set_IHDR (png, info, width, height, 8, PNG_COLOR_TYPE_RGB,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
    png_set_compression_level(png, 4);
   cerr<<"[nms    ]: Input file is a  16 bit elevation map:"<< setw(6)<<width<<" x"<< setw(6)<<height<<"\n\n";
   cerr<<"           Generating a normalmap for spherical geometry in PNG format"<<"\n\n";   
   clock_t start = clock(), end; 
   //write header          
   png_write_info (png, info);
               
    ushort** h = new ushort* [height+1];
    unsigned char* rgb = new unsigned char[width * 3];

    for (int i = 0; i < kernelSize - 1; i++)
        h[i] = readRowU16(stdin, width);
    double bumpheight = heightmapunit * exag * width / (2000 * pi * radius);
    double hp = pi / (double) height;

    for (int y = 0; y < height; y++)
    {

        // Out with the old . . .
        if (y > halfKernelSize)
            delete[] h[y - halfKernelSize - 1];

        // . . . and in with the new.
        if (y < height - halfKernelSize)
            h[y + halfKernelSize] = readRowU16(stdin, width);
       
        double spherecorr = 1.0 / sin(hp * (y + 0.5));
        for (int x = 0; x < width; x++)
        {
            double dx, dy;

            // use forward differences throughout!
         dx = (double)(h[y][x] - h[y][(x + 1) % width]);
            // the pixel x in the row nearest to South pole uses the
            // pixel across the pole at position width/2 + x for the dy gradient
            if (y == height - 1)
               dy = (double)(h[y][x] - h[y][((width >> 1) + x) % width]);
            else         
                dy = (double)(h[y][x] - h[y + 1][x]);
                   
          dx *= bumpheight * spherecorr;
          dy *= bumpheight;
          double mag = sqrt(dx * dx + dy * dy + 1.0);
          double rmag = 127.0 / mag;

          rgb[x * 3 + 0] = (unsigned char) (128.5 + dx * rmag);
          rgb[x * 3 + 1] = (unsigned char) (128.5 + dy * rmag);
          rgb[x * 3 + 2] = (unsigned char) (128.5 + rmag);
      }
      png_bytep row_pointer = rgb;
      // Write a row of the image       
      png_write_row(png, row_pointer);
        if ((y + 1) % 1024 == 0 )
        {
           end = clock();
         double duration = ( double )( end - start ) / CLOCKS_PER_SEC;
         cerr << "nms ["<< setw(6) << y + 1 << " rows of " << setw(5) << height << " -> " << setw(6) << fixed << setprecision(2) << duration << " s] \n";
        }            
   }         
   png_write_end (png, info);
     png_destroy_write_struct (&png, &info);   
    return 0;
}




you will need to compile it as " nms.u16 ( or nms.u16.exe ) "

Code: Select all

g++ -O3 -Wall -D_LINUX nms.u16.cpp -o ../Linux_PC.bin/nms.u16 -lpng


Avatar
culturediff
Posts: 10
Joined: 04.08.2022
With us: 2 years 3 months
Location: France
Contact:

Post #5by culturediff » 07.04.2023, 13:16

Shadow-Dragon-777,
Great explanations concerning the F-Tex tool !
Very helpful.
Thanks a lot !
Karine.

Avatar
John Van Vliet
Posts: 2944
Joined: 28.08.2002
With us: 22 years 2 months

Post #6by John Van Vliet » 09.04.2023, 03:31

if you use " G'Mic " to convert a tiff to raw , the terminal code has changed in the current release

Code: Select all


gmic 4k.Ariel.DEM.inpaint.16s.tiff -o 4k.Ariel.DEM.inpaint.16s.raw,int16

Avatar
culturediff
Posts: 10
Joined: 04.08.2022
With us: 2 years 3 months
Location: France
Contact:

Post #7by culturediff » 19.10.2023, 16:43

To easily convert a Tiff image into raw format, you can also use XnConvert : https://www.xnview.com/fr/xnconvert/#downloads
Very easy to use and very quick conversion - less than 1 second for a 21600x10800 pixels image.
Have a nice evening ...


Return to “Utilities”