Alpha texture in shaderterrainmesh not work

Hello i try to have multi texturing in shaderterrain with use alpha map (for apply texture in zone)

With geomip it’s work but with shaderterrain alphamap is not applied

this is my code :

from pandac.PandaModules import GeoMipTerrain
from pandac.PandaModules import TextureStage
from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data
 




class myTerrainClass:
    def __init__(self):
        self.tssort = 0

        
    def buildterrain(self, heightmap, tername):
    
        # Construct the terrain
        self.terrain_node = ShaderTerrainMesh()

        # Set a heightfield, the heightfield should be a 16-bit png and
        # have a quadratic size of a power of two.
        self.terrain_node.heightfield = loader.loadTexture("heightfield.png")

        # Set the target triangle width. For a value of 10.0 for example,
        # the terrain will attempt to make every triangle 10 pixels wide on screen.
        self.terrain_node.target_triangle_width = 10.0

        # Generate the terrain
        self.terrain_node.generate()

        # Attach the terrain to the main scene and set its scale. With no scale
        # set, the terrain ranges from (0, 0, 0) to (1, 1, 1)
        self.terrain = render.attach_new_node(self.terrain_node)
        self.terrain.set_scale(1024, 1024, 100)
        self.terrain.set_pos(-512, -512, -70.0)

        # Set a shader on the terrain. The ShaderTerrainMesh only works with
        # an applied shader. You can use the shaders used here in your own application
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl")
        self.terrain.set_shader(terrain_shader)
        self.terrain.set_shader_input("camera", base.camera)

        # Store the root NodePath for convenience
        root = self.terrain
        root.reparentTo(render)
        root.setSz(75)
        
    def addterraintex(self, tex, stage):
        self.tssort += 1
        root = self.terrain

        # Step 4: full map texture is blended with everything
        ts = TextureStage(stage)
        ts.setSort(self.tssort)
        ts.setCombineRgb(TextureStage.CMModulate, TextureStage.CSPrevious, TextureStage.COSrcColor,
                                                    TextureStage.CSTexture, TextureStage.COSrcColor)
        ts.setRgbScale(2)
        root.setTexture(ts, loader.loadTexture(tex))
        ts.setSavedResult(True)


    def addalphatex(self, alphamap, tex, scale, stage):

        self.tssort += 1
        root = self.terrain

        #apply the texture to the entire terrain
        ts = TextureStage(stage)
        ts.setSort(self.tssort)
        ts.setMode(TextureStage.MReplace)
        root.setTexture(ts, loader.loadTexture(tex))
        root.setTexScale(ts, scale, scale)
        self.tssort += 1

        #Use an alpha map set texture to either previous texture or last saved texture
        ts = TextureStage(stage + "alpha")
        ts.setSort(self.tssort)
        root.setTexture(ts, loader.loadTexture(alphamap))
        ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSPrevious, TextureStage.COSrcColor,
                                                    TextureStage.CSLastSavedResult, TextureStage.COSrcColor,
                                                    TextureStage.CSTexture, TextureStage.COSrcColor)
        ts.setSavedResult(True)



    # Add a task to keep updating the terrain
    def myupdateTerrainTask(self, task):
        self.terrain.update()
        return task.cont
      
      
import direct.directbase.DirectStart
from pandac.PandaModules import Vec4
from pandac.PandaModules import TextureStage
from pandac.PandaModules import GeoMipTerrain
from pandac.PandaModules import AmbientLight

# Required for matrix calculations
load_prc_file_data("", "gl-coordinate-system default")

render.setShaderAuto()


myterrain = myTerrainClass()
myterrain.buildterrain("heightfield.png", "myterrain")


myterrain.addalphatex("rock_alpha.png", "Rock.png", 64, "stage-grass1")

run()

with geomip terrain it’s work and i have this output :

but with shadder terrain i have this :

i have not white carre

this is my rock texture and my alpha texture

ShaderTerrainMesh uses a shader, which overrides any TextureStage settings you may have specified. Instead, you need to add instructions to do whatever texture sampling you wish to the fragment shader.

I don’t understand what you’re telling me, is it possible to provide a simple example?

Here’s a shader using 4 detail textures and a fifth texture to control where the derail textures are splatted: https://gist.github.com/wezu/3892a0a61ae9ddfe57c0aa3f22825ac0
You need to use terrain.set_shader_input(‘detail_tex1’, loader.load_texture(…)) to set the textures, the attribute texture shader input name is ‘attribute_tex’

2 Likes

thanks wezu for your help but i can not load your shader :

i write this in my python code :
terrain_shader = Shader.load(Shader.SL_GLSL, “terrain_tex_f.glsl”)
self.terrain.set_shader(terrain_shader)

i have this error :
terrain_shader = Shader.load(Shader.SL_GLSL, “terrain_tex_f.glsl”)
TypeError: an integer is required (got type str)

You need both a vertex and a fragment shader terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain_tex_f.glsl")

wezy but can you clarify, with this shading lighting and shadows work?

No. The thing about shaders is that they do only what you tell em to do, combining custom shaders with those generated by the shader generator is tricky unless you know exactly what features will be used.

I still can’t get it to work, I can get my alpha texture (the grey rectangle) but the grass texture is not displayed
I only have a black and white terrain

and i not undestand how can i have grass and rock (not only grass) ?

i have this :

my code
#!/usr/bin/env python

from direct.showbase.ShowBase import ShowBase
from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data
from panda3d.core import SamplerState


class ShaderTerrainDemo(ShowBase):
    def __init__(self):

        # Load some configuration variables, its important for this to happen
        # before the ShowBase is initialized
        load_prc_file_data("", """
            textures-power-2 none
            gl-coordinate-system default
            window-title Panda3D ShaderTerrainMesh Demo
        """)

        # Initialize the showbase
        ShowBase.__init__(self)

        # Increase camera FOV as well as the far plane
        self.camLens.set_fov(90)
        self.camLens.set_near_far(0.1, 50000)

        # Construct the terrain
        self.terrain_node = ShaderTerrainMesh()

        # Set a heightfield, the heightfield should be a 16-bit png and
        # have a quadratic size of a power of two.
        self.terrain_node.heightfield = self.loader.loadTexture("heightfield.png")

        # Set the target triangle width. For a value of 10.0 for example,
        # the terrain will attempt to make every triangle 10 pixels wide on screen.
        self.terrain_node.target_triangle_width = 10.0

        # Generate the terrain
        self.terrain_node.generate()

        # Attach the terrain to the main scene and set its scale. With no scale
        # set, the terrain ranges from (0, 0, 0) to (1, 1, 1)
        self.terrain = self.render.attach_new_node(self.terrain_node)
        self.terrain.set_scale(1024, 1024, 100)
        self.terrain.set_pos(-512, -512, -70.0)

        # Set a shader on the terrain. The ShaderTerrainMesh only works with
        # an applied shader. You can use the shaders used here in your own application
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl")
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain_tex_f.glsl")
        self.terrain.set_shader(terrain_shader)
        self.terrain.set_shader_input("camera", self.camera)

        # Shortcut to view the wireframe mesh
        self.accept("f3", self.toggleWireframe)
        
        self.terrain.set_shader_input('attribute_tex', self.loader.load_texture("rock_alpha.png"))
        self.terrain.set_shader_input('detail_tex1', self.loader.load_texture("textures/rock.png"))        
        
        self.terrain.set_shader_input('attribute_tex', self.loader.load_texture("grass_alpha.png"))
        self.terrain.set_shader_input('detail_tex2', self.loader.load_texture("textures/grass.png"))

ShaderTerrainDemo().run()

no idea what is my problem ?

i think you must use offiical panda3d shader
and create many terrains mono texture for make this:

if work if not use many texture (one is max)

Here’s the full version, with sample textures and all so it’s easier to use:
https://drive.google.com/file/d/1VjpEnEktwGfw6rZyE6nGDkdj-8gZF29Z/view?usp=sharing

Do check out the attribute.png file it has a mask for each detail texture in one channel, so rock is red, grass is green some other rock is blue and dirt is alpha. I suggest opening it in Gimp or something like it and decomposing it into channels if my explanation is not clear.

Ah okay I get it!
thanks for your help

I have a question, for define color, it’s possible to define rgb code color ?

actually for change green to pink i make this :
diffuse += texture(detail_tex2, terrain_uv * 16.0).rgb * texture(attribute_tex, terrain_uv).g;
.g is green, for apply pink color i change to .p

but it’s possible tu use code rgb ? for example for white 255.255.255
what is this language ? i can not use opengl function like glColor3f

In GLSL you can access vector components using .rgba or .xyzw, for example you can do:

vec4 color = vec4(0.1, 0.2, 0.3, 0.4);  
float red = color.r; //0.1
float also_red = color.x; //0.1
vec2 red_and_green = color.rg; //vec2(0.1, 0.3) 
vec4 mixed = color.wxyz; // vec4(0.4, 0.1, 0.2, 0.3)

This technique works by having a mask in each channel of the attribute texture (a texture has up to 4 channels - red, green, blue, alpha). Each of these channels are just black and white images - white (1.0) where a detail texture should be used and black (0.0) where it should not be used. 1.0 * color = color, 0.0 * color = 0.0

Pink would be (1.0, 0.41, 0.7) so in this context that makes no sense at all.
You’d have to write a lot of if-else to use pink as a mask, and shaders don’t like that.

For white, for another, set any value between 0 and 255.

R = 255
G = 255
B = 255
A = 255

vec4(R/255, G/255, B/255, A/255)

R = 255
G = 255
B = 255

vec3(R/255, G/255, B/255)

I ask my question differently:
i want to add other texture,
if I want to add a fifth texture (wood texture for example), I must have to add a color ?

Just to nitpick, in an sRGB correct pipeline this conversion formula is not correct. To convert an sRGB-encoded 0-255 colour to a linear colour in the 0-1 range, you can use this:

from panda3d.core import decode_sRGB_float as dec

R = 255
G = 255
B = 255
A = 255
print(dec(int(R)), dec(int(G)), dec(int(B)), A / 255.0)

If you are not bothering with gamma-correctness (though you should think about this sooner or later), you can safely ignore this.

Hmm, but this is not a formula, but a function. I wonder what kind of math she uses … is it really that hard.

no matter what the color code is,
What I don’t understand is how do you define worm as not the texture of grass and red as stone?
If I want to add for example forest and water texture I do how?

if i add
diffuse += texture(detail_tex5, terrain_uv * 16.0).rgb * texture(attribute_tex, terrain_uv).r;
in terrain.frag.glsl for add new texture, for example i want that detail_tex5 is wood or water texture

I think r,g,b and a in shader are color codes ? how add detail_tex5

i update attribute.png to this :

i want water or wood texture in my pink zone

in green i have grass
in red a have light rock
in blue i have dark rock
in pink i want other (water,wood…)

The problem is that the texture has only 4 channels to apply other layers, you need to use additional channels and shader passes.