Board index FlightGear Development Effects and shaders

Volumetric grass (was 3D textures and volumetric data)

An exciting "new" option in FlightGear, that includes reflections, lightmaps, the particle system etc.. A lot is yet to be discovered/implemented!

Volumetric grass (was 3D textures and volumetric data)

Postby icecode » Sat May 13, 2017 4:21 pm

Hi,

While experimenting with fur shaders for volumetric airport grass I've come across the need of using 3D textures (or 2D texture arrays). Apparently they are supported by the Effects framework but I haven't seen any examples apart from noise textures. Are they actually supported by FG? If so, how should I input my volumetric data to a shader? That data would be just a 512x512x32 array. My current code encodes the 32 depth layers into one and the shader decodes it, but it generates floating point errors and is generally more inefficient.

Thanks.
Last edited by icecode on Tue May 16, 2017 1:04 pm, edited 1 time in total.
icecode
 
Posts: 709
Joined: Thu Aug 12, 2010 1:17 pm
Location: Spain
Version: next
OS: Fedora

Re: 3D textures and volumetric data

Postby Thorsten » Sat May 13, 2017 4:33 pm

$FGData/Docs/README.effects:

texture-unit - has several child properties:
unit - The number of an OpenGL texture unit
point-sprite - true, false - Whether this should rendered as a point-sprite
type - This is either an OpenGL texture type or the name of a
builtin texture. Currently supported OpenGL types are 1d, 2d,
3d which have the following common parameters:
image (file name)
filter - nearest, linear, [nearest|linear]-mipmap-[nearest|linear]
mag-filter - nearest, linear, [nearest|linear]-mipmap-[nearest|linear]
wrap-s - clamp, clamp-to-border, clamp-to-edge, mirror, repeat
wrap-t - clamp, clamp-to-border, clamp-to-edge, mirror, repeat
wrap-r - clamp, clamp-to-border, clamp-to-edge, mirror, repeat
mipmap-control - control the mipmap on a per-channel basis. Children:
function-r - auto, average, sum, product, min, max
function-g - auto, average, sum, product, min, max
function-b - auto, average, sum, product, min, max
function-a - auto, average, sum, product, min, max

The following built-in types are supported:
white - 1 pixel white texture
noise - a 3d noise texture. (size parameter defines size of texture)
light-sprite - a procedurally generated sprite suitable for point lights
cubemap - build a cube-map. Children:
images - build from 6 images. Children: [positive|negative]-[x|y|z]
image - build from a single cross-image



If that's true, something like

Code: Select all
    <texture n="10">
      <image>Textures/Terrain/my3dtexture.png</image>
     <type>3d</type>
      <filter>linear-mipmap-linear</filter>
      <wrap-s>repeat</wrap-s>
      <wrap-t>repeat</wrap-t>
      <wrap-r>repeat</wrap-r>
      <internal-format>normalized</internal-format>
    </texture>


in the effect declaration ought to do the trick, and later

Code: Select all
      <texture-unit>
        <unit>0</unit>
        <image><use>texture[10]/image</use></image>
    <type><use>texture[10]/type</use></type>
        <filter><use>texture[10]/filter</use></filter>
        <wrap-s><use>texture[10]/wrap-s</use></wrap-s>
        <wrap-t><use>texture[10]/wrap-t</use></wrap-t>
        <wrap-r><use>texture[10]/wrap-t</use></wrap-r>
        <internal-format><use>texture[10]/internal-format</use></internal-format>
      </texture-unit>
Thorsten
 
Posts: 12490
Joined: Mon Nov 02, 2009 9:33 am

Re: 3D textures and volumetric data

Postby icecode » Sat May 13, 2017 4:37 pm

Yes, I saw that in the README.effects. But what file format should I use? From what I know PNG just supports 32 bits depth values (RGBA), so how do I represent that 3rd array dimension?
icecode
 
Posts: 709
Joined: Thu Aug 12, 2010 1:17 pm
Location: Spain
Version: next
OS: Fedora

Re: 3D textures and volumetric data

Postby Thorsten » Sat May 13, 2017 6:19 pm

Honestly no idea - perhaps look into the OSG documentation? After all OSG has to read it.
Thorsten
 
Posts: 12490
Joined: Mon Nov 02, 2009 9:33 am

Re: 3D textures and volumetric data

Postby erik » Sat May 13, 2017 9:57 pm

It looks like there are two options: DDS and TIFF.

Erik
Current: Parachutist, Paraglider, Pterosaur, Pilatus PC-9M and variants, ERCO Ercoupe, Fokker Dr.1, Fokker 50, Fokker 100
Less active: Cessna T-37, T-38, Santa Claus. Previous: General Dynamics F-16. Worked on: Wright Flyer
erik
 
Posts: 2244
Joined: Thu Nov 01, 2007 2:41 pm

Re: 3D textures and volumetric data

Postby icecode » Sun May 14, 2017 9:27 pm

I've tried both formats but the shader crashes as soon as I issue a texture3D lookup. I guess the answer is somewhere inside SimGear, which is kind of frustrating. :| Thank you both for the help!
icecode
 
Posts: 709
Joined: Thu Aug 12, 2010 1:17 pm
Location: Spain
Version: next
OS: Fedora

Re: 3D textures and volumetric data

Postby Thorsten » Mon May 15, 2017 6:20 am

What kind of data structure do you need (aka, can we cheat and encode it in a different way?)
Thorsten
 
Posts: 12490
Joined: Mon Nov 02, 2009 9:33 am

Re: 3D textures and volumetric data

Postby erik » Mon May 15, 2017 8:28 am

I was also thinking that texture sheet as being used for trees might be an alternative too.

Erik
Current: Parachutist, Paraglider, Pterosaur, Pilatus PC-9M and variants, ERCO Ercoupe, Fokker Dr.1, Fokker 50, Fokker 100
Less active: Cessna T-37, T-38, Santa Claus. Previous: General Dynamics F-16. Worked on: Wright Flyer
erik
 
Posts: 2244
Joined: Thu Nov 01, 2007 2:41 pm

Re: 3D textures and volumetric data

Postby icecode » Mon May 15, 2017 1:03 pm

I'll give some background on what I want to do so it's clearer for you. Shell texturing relies on duplicating each triangle a set amount of times along its surface normal, applying a texture to each duplicated layer to give the appeareance of continuous fur, something like this. If we have 32 layers then each texel of one of these 2D textures would be 1/32 of a single grass blade. Since we just need to store whether there is a grass blade in a texel or not, a boolean would be enough to describe the grass topology. Color can be applied later with a second 2D texture.

32 booleans are 32 bits, which is exactly the amount of bits regular RGBA images can store per pixel. I wrote a small script in Python that splits the layers in 4 and stores 8 layers (or bits) on each channel of the RGBA texture. So for example if I want to know if there is a grass blade on layer 4, I'll take the 0-255 value from the alpha channel, decode it to binary and take its 4th least significant bit. This works perfectly in the shader testing program I use, but once I copy everything to FG everything starts to "shake" weirdly and layers aren't decoded properly. I imagine this is because of floating point errors since I have to scale the texcoords by a hundred or so. The problem with all of this is that it's really frustrating to debug. Everything works but suddenly nothing works properly in FG, which it should since nothing changed.
icecode
 
Posts: 709
Joined: Thu Aug 12, 2010 1:17 pm
Location: Spain
Version: next
OS: Fedora

Re: 3D textures and volumetric data

Postby icecode » Mon May 15, 2017 1:21 pm

Here is how it should look:
Image

Here is how it looks:
Image

(Note that the separation between blades is intentional so it's easier to see everything).
icecode
 
Posts: 709
Joined: Thu Aug 12, 2010 1:17 pm
Location: Spain
Version: next
OS: Fedora

Re: 3D textures and volumetric data

Postby erik » Mon May 15, 2017 1:26 pm

To me it sound you'd be better off with textures of n x n size and then make the width n x n*m (or even n*m x n*m) where m is the number of layers (like the tree texture sheets):
Image

Erik
Current: Parachutist, Paraglider, Pterosaur, Pilatus PC-9M and variants, ERCO Ercoupe, Fokker Dr.1, Fokker 50, Fokker 100
Less active: Cessna T-37, T-38, Santa Claus. Previous: General Dynamics F-16. Worked on: Wright Flyer
erik
 
Posts: 2244
Joined: Thu Nov 01, 2007 2:41 pm

Re: 3D textures and volumetric data

Postby Thorsten » Mon May 15, 2017 1:34 pm

To me it sound you'd be better off with textures of n x n size and then make the width n x n*m (or even n*m x n*m) where m is the number of layers (like the tree texture sheets):


I dunno - we know for sure that this suffers from lookup issues - Stuart fought a while to get the borders clean. The boolean encoding seems less error-prone in theory.

Random guess - what about mip-mapping? FG presumably does it until you switch it off, your shader testbox might not.

If you like, you can point me to your repository - I'm interested in learning more and playing with this, I've never used geometry shaders before (always wanted to study them, never found a good use case or a cool tutorial where I'd have said 'That's it, need it.').
Thorsten
 
Posts: 12490
Joined: Mon Nov 02, 2009 9:33 am

Re: 3D textures and volumetric data

Postby icecode » Mon May 15, 2017 1:42 pm

I've disabled bilinear filtering (both min and mag) and set mipmapping to nearest (if I disable mipmapping entirely FG lags when looking at distant grass). But even with mipmapping disabled it looks the same.
Code: Select all
        <texture n="21">
            <image>Textures/grass_density.png</image>
            <type>2d</type>
            <filter>nearest-mipmap-nearest</filter>
            <mag-filter>nearest</mag-filter>
            <wrap-s>repeat</wrap-s>
            <wrap-t>repeat</wrap-t>
            <internal-format>normalized</internal-format>
        </texture>


I've thought of the texture sheet approach. Even if we ignore what Thorsten said about lookup issues, 512 times 32 is 16384. That's a lot of wasted memory when I just want to store booleans. Imagine if the texture size is 1024 instead of 512 (which is probably the case in the final version).

This is my FGData fork: https://sourceforge.net/u/fgarlin/flightgear/ci/grass/tree/. Relevant files are grass.vert, .frag, .geom and .eff. The encoded texture is in Textures/grass_density.png, and the Python script to generate it is here: https://pastebin.com/AdKeRE7d. That should be enough to give you hours of entertainment. :)
icecode
 
Posts: 709
Joined: Thu Aug 12, 2010 1:17 pm
Location: Spain
Version: next
OS: Fedora

Re: 3D textures and volumetric data

Postby erik » Mon May 15, 2017 1:58 pm

You could use GL_RED8 and maybe even use it to store 8 bits per color component.

Erik
Current: Parachutist, Paraglider, Pterosaur, Pilatus PC-9M and variants, ERCO Ercoupe, Fokker Dr.1, Fokker 50, Fokker 100
Less active: Cessna T-37, T-38, Santa Claus. Previous: General Dynamics F-16. Worked on: Wright Flyer
erik
 
Posts: 2244
Joined: Thu Nov 01, 2007 2:41 pm

Re: 3D textures and volumetric data

Postby Thorsten » Tue May 16, 2017 12:33 pm

Oh, this is NICE!

Well, textures are overrated anyway... This function works, can be biased with a wind-bending via two additional uniforms and doesn't need a texture:

Code: Select all
// -*-C++-*-
#version 120

#define BLADE_FRACTION 0.1

uniform float time;

uniform float bend_x;
uniform float bend_y;

uniform sampler2D colorTex;
uniform sampler2D densityTex;

flat in float g_layer;            // The layer where the fragment lives

float rand2D(in vec2 co);

float map(float s, float a1, float a2, float b1, float b2)
{
    return b1+(s-a1)*(b2-b1)/(a2-a1);
}

float decodeBinary(float n, float layer)
{
   return float(mod(floor(n*pow(0.5, layer)), 2.0));
}


float bladeNoise2D(in float x, in float y, in float dDensity, in float layer)
{
   float integer_x    = x - fract(x);
    float fractional_x = x - integer_x;

    float integer_y    = y - fract(y);
    float fractional_y = y - integer_y;

   if (rand2D(vec2(integer_x+1.0, integer_y +1.0)) > dDensity)
      {return 0.0;}

    float xoffset = (rand2D(vec2(integer_x, integer_y)) -0.5);
    float yoffset = (rand2D(vec2(integer_x+1.0, integer_y)) - 0.5);
   
   float xbend =  (rand2D(vec2(integer_x+1.0, integer_y + 1.0)) - 0.5) + bend_x;
   float ybend =  (rand2D(vec2(integer_x, integer_y + 1.0)) - 0.5) + bend_y;



   float bend = 0.003 * layer * layer;
   
   vec2 truePos = vec2 (0.5 + xoffset * (1.0 - 2.0 * BLADE_FRACTION) + xbend * bend, 0.5 + yoffset * (1.0 -2.0 * BLADE_FRACTION) +  ybend * bend);

   float distance = length(truePos - vec2(fractional_x, fractional_y));
   
   return 1.0 - step (BLADE_FRACTION, distance);
}

float BladeNoise2D(in vec2 coord, in float wavelength, in float dDensity, in float layer)
{
return bladeNoise2D(coord.x/wavelength, coord.y/wavelength, dDensity, layer);
}

void main()
{
   vec2 texCoord = gl_TexCoord[0].st;

   vec3 diffuse = texture2D(colorTex, texCoord).rgb;
   
   //vec4 densityTexel = texture2D(densityTex, texCoord);
   //float channel = floor(g_layer/8.0); // Alpha 0, Blue 1, Green 2, Red 3
   //float value = decodeBinary(floor(densityTexel[int(3.0-channel)]*255.0), g_layer-8.0*channel);
   
   float value = 0.0;
   
   float bladeFlag = BladeNoise2D(texCoord, 0.01, 1.0, g_layer);
      
   //if (length(densityTexel) > 0.0)
   if (bladeFlag > 0.0)
      {
      value = 1.0;   
      }
   else
      {
      value = 0.0;
      }

   if (g_layer == 0.0) {value = 0.3;}
   
    gl_FragColor = vec4(diffuse, value);
}


There's still some Moire patterns being generated because it doesn't mipmap, I have yet to figure out how to really get the varying values I need best to the fragment shader - if I do it explicitly, the hardware balks and refuses to generate so many vertices.

Which means we need a good solution for ALS lighting and fogging because that needs lots of varying. Though probably just discarding fragments too far out will do the trick most of the time.
Thorsten
 
Posts: 12490
Joined: Mon Nov 02, 2009 9:33 am

Next

Return to Effects and shaders

Who is online

Users browsing this forum: No registered users and 2 guests