Smeared Texture on Sphere but only when using `TextureStage`s other than default

Hi, this is a (sort of) continuation of the post here. This is the same issue, but the smearing only appears when using any TextureStage other than TextureStage.default.

Doesn’t work:

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)
    self.model = self.loader.loadModel('test_model.stl')
    self.tex = self.loader.loadTexture('test_texture.jpg')
    self.tex_stage = TextureStage('test_texture_stage')
    self.model.setTexGen(self.tex_stage, TexGenAttrib.MWorldPosition)
    self.tex.setWrapU(Texture.WM_repeat)
    self.tex.setWrapV(Texture.WM_repeat)
    self.model.set_texture(self.tex)
    self.model.set_tex_scale(self.tex_stage, 0.05, 0.05)
    self.model.reparentTo(self.render)
    self.model.setScale(1, 1, 1)
    self.model.setPos(0, 0, 0)
    self.useDrive()
    self.useTrackball()


app = TextureTester()
app.run()

Does work

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)
    self.model = self.loader.loadModel('test_model.stl')
    self.tex = self.loader.loadTexture('test_texture.jpg')
    self.tex_stage = TextureStage.default
    self.model.setTexGen(self.tex_stage, TexGenAttrib.MWorldPosition)
    self.tex.setWrapU(Texture.WM_repeat)
    self.tex.setWrapV(Texture.WM_repeat)
    self.model.set_texture(self.tex)
    self.model.set_tex_scale(self.tex_stage, 0.05, 0.05)
    self.model.reparentTo(self.render)
    self.model.setScale(1, 1, 1)
    self.model.setPos(0, 0, 0)
    self.useDrive()
    self.useTrackball()


app = TextureTester()
app.run()

Do I need to do anything specific to self.tex_stage? The docs says to just make a new one; no changes needed. Or maybe, do the TextureStages need to be in a different node or node path?

Thanks in advance.

Could you share the test model and texture so I can try it for myself and see what is going on?

As far as I know, assign TextureStage('test_texture_stage') should be an entry in the model with this name test_texture_stage for UV coordinates.

Although a name match is not required, the additional layer must be named differently than nothing. Nothing this is the default name.

Thanks for the reply. Sorry I don’t have the 3D model right now, but the image used is this:

Also, by the additional layer, do you mean the self.tex having no name, or it being not initalised with the self.tex_stage?

  <Vertex> 0 { -1 -1 0 
   <UV> { 0 0 }
   <UV> UVMap { 0.052657 0.441651 }
  }

Additional layer UVMap
Which has no name, it is default.

test.egg

<CoordinateSystem> { Z-Up }

<Group>  Plane {
 <VertexPool> Plane {
  <Vertex> 0 { -1 -1 0 
   <UV> { 0 0 }
   <UV> UVMap { 0.052657 0.441651 }
  }
  <Vertex> 1 { 1 -1 0 
   <UV> { 1 0 }
   <UV> UVMap { 0.558349 0.052657 }
  }
  <Vertex> 2 { 1 1 0 
   <UV> { 1 1 }
   <UV> UVMap { 0.947343 0.558349 }
  }
  <Vertex> 3 { -1 1 0 
   <UV> { 0 1 }
   <UV> UVMap { 0.441651 0.947343 }
  }
 }

 <Polygon> 0 { 
  <Normal> { 0 0 1 }
  <VertexRef> { 0 1 2 3 <Ref> { Plane } }
  }
}

main.py

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')
    tex_stage.setTexcoordName('UVMap')
    #tex_stage.setTexcoordName('')

    model = loader.loadModel('test')
    #model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.reparentTo(render)

app = TextureTester()
app.run()

Use tex_stage.setTexcoordName('')

Use tex_stage.setTexcoordName('UVMap')

Generated coordinates I have works so, perhaps that with your model. I used egg.

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(loader.loadTexture('panda.jpg'))
    model.reparentTo(render)

app = TextureTester()
app.run()

I have the 3D model now, it’s A Google Drive link since I cant upload STL files.https://drive.google.com/file/d/1zmrqbEE5lrRJ2We-8jqnd_mnwrvSfTky/view?usp=sharing

Thanks! I’ll test this when I get to my computer.

Your example works like this:

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test_model.stl')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.reparentTo(render)

app = TextureTester()
app.run()

Hi, sorry, I am a bit confused. So is using a STL a problem? I think you’ve mentioned using an egg file instead. Also, when I used the same code but with an STL, it doesn’t work. Maybe I should convert to egg? It seems that the “setTextcoordName” function is related to the egg file format: “Indicate which set of UV’s this texture stage will use. Geometry may have any number of associated UV sets, each of which must have a unique name.” I don’t think STL (especially binary) has a name for UV sets.

Hi,

The problem is simply that you need to match the texture stage in the set_texture call to the one in your set_tex_gen call. It has nothing to do with the model used.

self.model.set_texture(self.tex_stage, self.tex)

Otherwise, the texture is applied in a different texture stage than your tex-gen setting.

If you need more texture layers, you should take care of it in the 3d editor. On the other hand, texture coordinate generation works on the fly, but it may not be adequate due to the complex grid. So you should take care of this in the editor, but as I understand it, CAD doesn’t use the concept of a UV map for solids, especially for multiple channels.

For example.

from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.setHpr(0, 90, 0)
    model.setPos(0, 10, 0)
    model.reparentTo(render)

app = TextureTester()
app.run()

But if you do this:

model.set_texture(loader.loadTexture('panda.jpg'))

Then just write the texture to the default channel, and it will look correct.

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(loader.loadTexture('panda.jpg'))
    model.setHpr(0, 90, 0)
    model.setPos(0, 10, 0)
    model.reparentTo(render)

app = TextureTester()
app.run()

But if you rotate the model, then the result will be different.

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage


class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.reparentTo(render)

app = TextureTester()
app.run()

The conclusion is: Don’t use texture coordinate generation.

Thanks, rdb! That solved my problem!

Thanks serega! I think I’ll be fine now since I’m only using a single layer, but I’ll definitely use Blender or Maya when it gets more complicated.

I explained in a back post what the problem is, so I don’t think everything will be fine when using

model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)

I don’t really understand what you were saying, @serega-kkz. Firstly, UV coordinates are not used at all when you use setTexGen. Secondly, even if they were, the name of the texture stage is irrelevant; the UV name is a separate field on the TextureStage object.

Just compare.

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage

class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.setHpr(0, 90, 0)
    model.setPos(0, 10, 0)
    model.reparentTo(render)

app = TextureTester()
app.run()

And

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage

class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.setHpr(0, 45, 0)
    model.setPos(0, 10, 0)
    model.reparentTo(render)

app = TextureTester()
app.run()

I think there’s a difference, right? Use the default UV that was set in the editor.

from direct.showbase.ShowBase import ShowBase, Texture, TexGenAttrib
from panda3d.core import TextureStage

class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    tex_stage = TextureStage('test_texture_stage')

    model = loader.loadModel('test')
    model.set_texture(tex_stage, loader.loadTexture('panda.jpg'))
    model.setHpr(0, 45, 0)
    model.setPos(0, 10, 0)
    model.reparentTo(render)

app = TextureTester()
app.run()

However, if there is no UV by default:

model.setTexGen(tex_stage, TexGenAttrib.MWorldPosition)

In any case, it will not replace them and will not work correctly.

Because it depends on the complexity of the grid and the transformation of the object, that’s what I want to say.

Are you saying that MWorldPosition is relative to the world, instead of relative to the object? Then yes, that’s exactly what MWorldPosition is advertised to do. If you want coordinates relative to the world, use MWorldPosition. If you want coordinates relative to the model, use UV coordinates.

I say model.setTexGen() is not a rescue when there is no UV map. Regardless of the modes:

Therefore, you need to rely on UV models, but they are created only in the 3D editor. In other cases, this is 100% incorrect behavior.

When there are texture coordinates, this code is sufficient.

from direct.showbase.ShowBase import ShowBase, Texture

class TextureTester(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)

    model = loader.loadModel('test_model.stl')
    model.set_texture(loader.loadTexture('panda.jpg'))
    model.reparentTo(render)

app = TextureTester()
app.run()

However, the test_model.stl file does not contain a UV map. This is why the topikstarter is trapped in the imaginary effect of when used: setTexGen()