Sparse Textures / Shader splatting

For my terrain, I want to use Shader splatting, and now I have some questions about it. I have already the terrain mesh, now I want to texture it.

I want to create a Virtual Texture (let’s say its 32x32k). Additional I have a texture atlas, which stores the information where each of (the currently visible) chunks has its texture on the atlas:

         r      | g     |  b    | a     
Atlas:   x-pos  | y-pos | scale | unused

I then pass the atlas to the chunk, and the virtual texture, and the chunk will fetch his texture like that:

float3 atlas = tex2D(k_tex_atlas, l_texcoord0).rgb;
float3 color = tex2D(k_virtual_tex, atlas.xy + l_texcoord0 / scale).rgb;

The virtual texture gets filled by a shader, which processes the heightfield information to generate a color and normalmap.

Now my questions are:

  • How do I create and initialize a virtual texture? Does panda3D support it anyway?
  • How do I apply the shader to that virtual texture, but only to a specific region?
  • How do I make the shader only process only when I call it, not every frame?
  • How do I fetch the virtual texture in the fragment shader? tex2D?

Edit: I’ve already found that: panda3d.org/blog/?p=96 but the link to the sample is not working anymore

Thanks in advance
Tobias

Can’t say I know anything about virtual textures, but I have done similar to yourself but using texture array.
The player is in the centre cell, then a 3x3 group of cells around them, and then 5x5 around that.
This requires an array of 25 textures, 1 for each cell. I used 512x512 textures.

This is super easy to update as the player moves as its just a move/copy and load new cells if necessary.
Texture coord and cell are easy calculated by current player position.

To be honest i did find 512x512 over kill for height data…but it was needed for the texturing i encoded in the rest of the image.

So to save yourself so time, you could store height/normal data in first cell array. Make each pixel equate to 4x4 or 8x8 meters. Store the texturing/colouring/ data in a separate array at 1x1 meter.
That is how i would do it, if i needed to again. You could also bake shadows/occlusion and light map into another array.

I do not think it is applicable for my project …
I’m using a 4x4k mesh with a 4k heightfield and a 4k texture atlas … I can’t load it all at once … For the mesh I’m using a quadtree, so I have ~50-100 chunks visible at one time … therefore comes a density map which modifies the lod of the chunks, too. I can’t also bake lighting and shadows (later I want to used precomputed lighting, but that’s something different) because I have a day-night cyclus.
My first attempt of terrain used exactly the same technique you used … but the problem is low view distance and a high fillrate overload … I wan’t the terrain to have a view distance of ~10km and still be tesselated.

But thanks for your help :slight_smile:
Tobias

Archive.org is your friend in times like this :smiley:

web.archive.org/web/201002160436 … blog/?p=96

Below is the code, but theres also some other items in the zip, including a pfmTexture. Couldn’t get it running through, theres also C++ code, so it might need compiling.


from pandac.PandaModules import *
from direct.task import Task

from PfmReader import PfmReader

FRAME_COUNT_STOP = 3
FRAME_TO_SET_TEX = 2

class PfmTexture(Texture):
    def __init__(self, fileNameStr):
        Texture.__init__(self)
        self.frameCount = 0
        
        #setup pfm texture
        self.pfmObj = PfmReader()
        self.pfmObj.loadPfmFile(fileNameStr)
        self.pfmObj.swapChannels(0,2) #why panda likes bgr I don't know
        self.pfmObj.appendChannel(1.0)
        (xSize, ySize, pixDepth) = self.pfmObj.getDimensions()
        self.setCompression(Texture.CMOff)
        self.setup2dTexture(xSize, ySize, Texture.TFloat, Texture.FRgba16) 
        self.makeRamImage()
        
        #add the task
        taskMgr.add(self.frameCounterTask, "frameCounterTask")

    def frameCounterTask(self,t):
        #Task to do things at specific frame counts
        self.frameCount += 1
        if self.frameCount == FRAME_TO_SET_TEX:
            #we do this on the 2nd frame rather than the 1st because the order Panda
            #does things in.
            (xSize, ySize, pixDepth) = self.pfmObj.getDimensions()
            #since this is in bytes, *4 is needed because we are uploading 4 byte (32-bit) floats
            self.setRamMipmapPointerFromInt(self.pfmObj.getPointerAsLong(), 0, xSize*ySize*pixDepth*4)
        if self.frameCount > FRAME_COUNT_STOP: return Task.done
        else: return Task.cont

if __name__ == "__main__":
    from direct.directbase.DirectStart import *
    print "This demo only for Panda 1.7.0 on 32bit windows only"
    cm = CardMaker("cm")
    cm.setFrame(-1,1,-1,1)
    card = render2d.attachNewNode(cm.generate())
    tex = PfmTexture("stpeters.pfm")
    card.setTexture(tex)
    run()

I did not know archive.org also stores zip archives :open_mouth:
I’ll give it a try now!

Edit: I think pfmTexture does still not fit the requirements … I need a virtual Buffer with a very high resolution.

To make clear what I am looking for:

See also this awesome video: youtube.com/watch?v=fIzGdc_KIK4
This tutorial from nvidia: http.developer.nvidia.com/GPUGem … ter37.html
The cg specification is here: opengl.org/registry/specs/AM … exture.txt

I have thought about an implementation, would it be possible to create a huge framebuffer (I think about 32x32k) and pass only a part of it as shader input to a NodePath?

Another Idea: There is a DX11 Python extension … It allows you to use ComputeShaders, which are really fast and optimized for such operations. When I compute the neccessary texture parts in a compute shader, and pass a pointer of the result to a pfm texture, which will then get passed to the chunk … would that work?

Thanks
Tobias