Brushes cannot pass through polygons

As shown, I want to add fog to the entire scene through cards and textures. But no matter how you set it, the pen cannot show the scene through the card, but the background color of the card. How do I clear the background of the card?

import random
from panda3d.core import *
from direct.directbase import DirectStart

scene = loader.loadModel("scene")
scene.reparentTo(render)
scene.showTightBounds()
mina,minb = scene.getTightBounds()
disVec = minb - mina 
center = (mina + minb )*0.5


veilColor = 0.2  
imgSize = 32
img = PNMImage(imgSize, imgSize)
img.fill(veilColor)
tex = Texture()
tex.load(img)


cd = CardMaker('square')
cd.setFrame(mina.x, minb.x,mina.y , minb.y)
cd.setColor(1,1,1,0.2)
card = cd.generate()
spuare = render.attachNewNode(card)
#spuare.setTransparency(True)
spuare.setP(-90)
spuare.setPos(0,0,120)


brush = PNMBrush.makeSpot((1,1,1,1), 3, True)
painter = PNMPainter(img)
painter.setPen(brush)
veilTS = TextureStage('')
spuare.setTexture(veilTS, tex)
spuare.setTexGen(veilTS, RenderAttrib.MWorldPosition)
spuare.setTexScale(veilTS, (1/disVec[0],1/disVec[1]))
spuare.setTexOffset(veilTS, -0.5+center[0]/disVec[0], -0.5-center[1]/disVec[1])

panda = loader.loadModel("panda")
panda.setScale(10)
panda.reparentTo(render)
pos = panda.getPos().xy - mina.xy
pos.x /= minb.x-mina.x
pos.y /= minb.y-mina.y
painter.drawPoint(pos.x*img.getXSize(), (1-pos.y)*img.getYSize())
tex.load(img)
base.run()

Hello @1111
You are correct - brushes cannot pass trhough polygons.

PNMPainter and PNMBrush operate only on images. Images are 2D, they have only x and y coordinates. So you can’t use them to draw in 3D space.
https://docs.panda3d.org/1.10/python/reference/panda3d.core.PNMPainter

I don’t know much, I am myself currently reading the manual and trying to learn. But I would suggest you the following approaches:

  1. If you want to use PNMPainter and PNMBrush classes to paint over the screen, use render to texture approach: https://docs.panda3d.org/1.10/python/programming/render-to-texture/low-level-render-to-texture In short: draw the contents of your screen to the texture, then use PNMPainter to draw over this texture, then draw this texture to screen.

  2. Use built-in fog effect: https://docs.panda3d.org/1.10/python/programming/render-attributes/fog Exponential fog won’t work for the effect you want, but you can try and use linear fog.

  3. Write a custom fog shader (it will give the best results, but may be hard to code).

I don’t have any code to show examples, sorry, I am still learning to use the engine.

I think what you’re asking is to make the card transparent where you painted it? If so, you can do that by giving the image only 1 channel, setting the texture format to F_alpha, and enabling transparency on the card. You’ll want to paint black where it should be transparent, and white where it should be visible.

However, I think it would be better to do the equivalent effect using texture blending.

I still don’t quite understand what you mean? How to set only one channel for the texture?

Well, thank you very much for your answers, but I know all of what you said, not the question I want to ask. Ha, thank you very much!

I might suggest that, instead of applying your texture to a card, you instead project it onto the objects of your scene. That way it should “light” objects as expected, and without a card that gets in the way.

This manual page seems to describe one means of implementing this; alternatively, I imagine that it could be done in a shader instead.

Although it is not straightforward to make an image transparent where a spot is drawn, there is still a way to do this. It can be done by drawing the spot (black) onto a completely transparent image, then subtracting this first image from another one (which you make opaque white) and loading that other image into the texture instead of the first one.

Here is an altered version of your code that should work:

import random
from panda3d.core import *
from direct.directbase import DirectStart

scene = loader.loadModel("scene")
scene.reparentTo(render)
scene.showTightBounds()
mina,minb = scene.getTightBounds()
disVec = minb - mina 
center = (mina + minb )*0.5


veilColor = LColorf(.1, .05, .35, 1.)
imgSize = 32
img = PNMImage(imgSize, imgSize, 4)  # add alpha channel to image
# the following 2 lines are not really needed, as the default
# image color is already (0, 0, 0, 0)
# img.fill(0.)        # image must be black...
# img.alpha_fill(0.)  # ...and completely transparent
tex = Texture()
tex.load(img)


cd = CardMaker('square')
cd.setFrame(mina.x, minb.x,mina.y , minb.y)
cd.setColor(1,1,1,0.2)
card = cd.generate()
spuare = render.attachNewNode(card)
# make the card transparent
spuare.setTransparency(TransparencyAttrib.M_alpha)
spuare.setP(-90)
spuare.setPos(0,0,120)


# the brush color must be opaque black
brush = PNMBrush.makeSpot((0., 0., 0., 1.), 3, True)
painter = PNMPainter(img)
painter.setPen(brush)
veilTS = TextureStage('')
spuare.setTexture(veilTS, tex)
spuare.setTexGen(veilTS, RenderAttrib.MWorldPosition)
spuare.setTexScale(veilTS, (1/disVec[0],1/disVec[1]))
spuare.setTexOffset(veilTS, -0.5+center[0]/disVec[0], -0.5-center[1]/disVec[1])

panda = loader.loadModel("panda")
panda.setScale(10)
panda.reparentTo(render)
pos = panda.getPos().xy - mina.xy
pos.x /= minb.x-mina.x
pos.y /= minb.y-mina.y
painter.drawPoint(pos.x*img.getXSize(), (1-pos.y)*img.getYSize())
# create a second image (opaque white) and subtract the first image from it
img2 = PNMImage(imgSize, imgSize, 4)
img2.alpha_fill(1.)
img2.fill(1.)
# the resulting image contains white fog; give it the desired color by
# multiplying this image by that color
img2 = (img2 - img) * veilColor
# now load this second image instead of the first one
tex.load(img2)
base.run()

Maybe not as efficient as the other methods proposed earlier, but it should be OK for small textures.

Why doesn’t the blend mode I set take effect? I set the color of the brush to black, but the painting is indeed white. Why is this?

import random
from panda3d.core import *
from direct.directbase import DirectStart

scene = loader.loadModel("scene")
scene.reparentTo(render)
scene.showTightBounds()
mina, minb = scene.getTightBounds()
disVec = minb - mina
center = (mina + minb)*0.5

imgSize = 32
img = PNMImage(imgSize, imgSize,1)
img.fill(1)
tex = Texture()
tex.load(img)
#tex.setFormat(6)


cd = CardMaker('square')
cd.setFrame(mina.x, minb.x, mina.y, minb.y)
card = cd.generate()
spuare = render.attachNewNode(card)
spuare.setTransparency(True)
spuare.setP(-90)
spuare.setPos(0, 0, 120)


brush = PNMBrush.makeSpot((0, 0, 0,1), 3, True)
painter = PNMPainter(img)
painter.setPen(brush)
veilTS = TextureStage('')
veilTS.setMode(TextureStage.MBlend)
veilTS.setColor((1,1, 0.5, 0))
spuare.setTexture(veilTS, tex)
spuare.setTexGen(veilTS, RenderAttrib.MWorldPosition)
spuare.setTexScale(veilTS, (1/disVec[0], 1/disVec[1]))
spuare.setTexOffset(veilTS, -0.5+center[0]/disVec[0], -0.5-center[1]/disVec[1])

panda = loader.loadModel("panda")
panda.setScale(10)
panda.reparentTo(render)
pos = panda.getPos().xy - mina.xy
pos.x /= minb.x-mina.x
pos.y /= minb.y-mina.y
painter.drawPoint(pos.x*img.getXSize(), (1-pos.y)*img.getYSize())
tex.load(img)
base.run()

Thank you so much, this method seems to work for me, I need to test it further!

Actually, it can be done even simpler :slight_smile: ; I had overlooked the ~ PNMImage operator, which returns the inverted image. So it is not necessary to create a second PNMImage to subtract the original from.

Here is the updated code:

import random
from panda3d.core import *
from direct.directbase import DirectStart

scene = loader.loadModel("scene")
scene.reparentTo(render)
scene.showTightBounds()
mina,minb = scene.getTightBounds()
disVec = minb - mina 
center = (mina + minb )*0.5


veilColor = LColorf(.1, .05, .35, 1.)
imgSize = 32
img = PNMImage(imgSize, imgSize, 4)  # add alpha channel to image
# the following 2 lines are not really needed, as the default
# image color is already (0, 0, 0, 0)
# img.fill(0.)        # image must be black...
# img.alpha_fill(0.)  # ...and completely transparent
tex = Texture()
tex.load(img)


cd = CardMaker('square')
cd.setFrame(mina.x, minb.x,mina.y , minb.y)
cd.setColor(1,1,1,0.2)
card = cd.generate()
spuare = render.attachNewNode(card)
# make the card transparent
spuare.setTransparency(TransparencyAttrib.M_alpha)
spuare.setP(-90)
spuare.setPos(0,0,120)


# the brush color must be opaque black
brush = PNMBrush.makeSpot((0., 0., 0., 1.), 3, True)
painter = PNMPainter(img)
painter.setPen(brush)
veilTS = TextureStage('')
spuare.setTexture(veilTS, tex)
spuare.setTexGen(veilTS, RenderAttrib.MWorldPosition)
spuare.setTexScale(veilTS, (1/disVec[0],1/disVec[1]))
spuare.setTexOffset(veilTS, -0.5+center[0]/disVec[0], -0.5-center[1]/disVec[1])

panda = loader.loadModel("panda")
panda.setScale(10)
panda.reparentTo(render)
pos = panda.getPos().xy - mina.xy
pos.x /= minb.x-mina.x
pos.y /= minb.y-mina.y
painter.drawPoint(pos.x*img.getXSize(), (1-pos.y)*img.getYSize())
# the inverted image contains white fog; give it the desired color by
# multiplying this image by that color
tex.load(~img * veilColor)
base.run()

不客气 :wink: .

1 Like

This is so cool, thank you so much! This is exactly what I expected!

1 Like