Question about Cartoon Shader

Hello,

Using Tut-Cartoon-Advanced.py I figured that you could change the line 2 lines before the end in the file inkGen.sha to change the color of the lines around the object, it is dark blue in the code below.
I am wondering now how to achieve giving certain objects on the scene a contour line with a different color than the default one?

//Cg inkGen.sha
//
//Cg profile arbvp1 arbfp1

void vshader(float4 vtx_position : POSITION,
             float4 vtx_texcoord0 : TEXCOORD0,
             out float4 l_position : POSITION,
             out float4 l_texcoord0 : TEXCOORD0,
             uniform float4x4 mat_modelproj)
{
  l_position=mul(mat_modelproj, vtx_position);
  l_texcoord0 = vtx_texcoord0;
}

void fshader(float4 l_texcoord0 : TEXCOORD0,
             uniform sampler2D tex_0 : TEXUNIT0,
             uniform float4 k_cutoff : C6,
             uniform float4 k_separation : C7,
             out float4 o_color : COLOR)
{
  float4 texcoord0 = l_texcoord0 + k_separation.xyzw;
  float4 color0=tex2D(tex_0, float2(texcoord0.x, texcoord0.y));
  float4 texcoord1 = l_texcoord0 - k_separation.xyzw;
  float4 color1=tex2D(tex_0, float2(texcoord1.x, texcoord1.y));
  float4 texcoord2 = l_texcoord0 + k_separation.wzyx;
  float4 color2=tex2D(tex_0, float2(texcoord2.x, texcoord2.y));
  float4 texcoord3 = l_texcoord0 - k_separation.wzyx;
  float4 color3=tex2D(tex_0, float2(texcoord3.x, texcoord3.y));
  float4 mx = max(color0,max(color1,max(color2,color3)));
  float4 mn = min(color0,min(color1,min(color2,color3)));
  float4 trigger = saturate(((mx-mn) * 3) - k_cutoff.x);
  float thresh = dot(float3(trigger.x, trigger.y, trigger.z),float3(1,1,1));
  float4 output_color = float4 (0.0, 0.0, 0.4, thresh); // THIS LINE
  o_color = output_color;
}

Thanks.

You’ll need to set up shader inputs. Look at some of the more advanced shader examples and their corresponding shaders to see how. Effectively it should just be one more line in panda once your shader is ready to accept other inputs.

Do you mean I will be able to give a Node as input to the shader so the shader applies only to this node?

Shaders are just render states in Panda3d. You can selectively enable or disable them per PandaNode.

You can even turn autoShader on for some nodes and off for others. (Instead of doing render.setAutoShader(), you can call it on selective nodes.)

The render state documentation in the manual is a bit lacking, so you’ll have to browse code sometimes to figure out what they do and how they operate.

First thanks for this answer.

I checked again and found out how to apply 2 different colors for the outline by duplicating part of the code.

I put my code below.

But the problem I have is the outlines for the 2 objects are in Overlay so they are on the top of the other models.

How to ensure the content of the normalsBuffer is only the part of the object which is visible on screen?

Thanks again.

import direct.directbase.DirectStart
from pandac.PandaModules import PandaNode,LightNode,TextNode
from pandac.PandaModules import Filename
from pandac.PandaModules import NodePath
from pandac.PandaModules import Shader
from pandac.PandaModules import Point3,Vec4
from direct.task.Task import Task
from direct.actor.Actor import Actor
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.showbase.BufferViewer import BufferViewer
import sys,os

# Figure out what directory this program is in.
MYDIR=os.path.abspath(sys.path[0])
MYDIR=Filename.fromOsSpecific(MYDIR).getFullpath()

font = loader.loadFont("cmss12")

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font = font,
                        pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1), font = font,
                        pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)


class ToonMaker(DirectObject):
    def __init__(self):
        base.disableMouse()
        camera.setPos(0, -50, 0)
        
        # Check video card capabilities.

        if (base.win.getGsg().getSupportsBasicShaders() == 0):
            addTitle("Toon Shader: Video driver reports that shaders are not supported.")
            return

        # Post the instructions.
        self.title = addTitle("Panda3D: Tutorial - Toon Shading with Normals-Based Inking")
        self.inst1 = addInstructions(0.95,"ESC: Quit")
        self.inst2 = addInstructions(0.90,"Up/Down: Increase/Decrease Line Thickness")
        self.inst3 = addInstructions(0.85,"Left/Right: Decrease/Increase Line Darkness")
        self.inst4 = addInstructions(0.80,"V: View the render-to-texture results")

        # This shader's job is to render the model with discrete lighting
        # levels.  The lighting calculations built into the shader assume
        # a single nonattenuating point light.

        tempnode = NodePath(PandaNode("temp node"))
        tempnode.setShader(Shader.load(MYDIR+"/lightingGen.sha"))
        base.cam.node().setInitialState(tempnode.getState())
        
        # This is the object that represents the single "light", as far
        # the shader is concerned.  It's not a real Panda3D LightNode, but
        # the shader doesn't care about that.

        light = render.attachNewNode("light")
        light.setPos(30,-50,0)
                
        # this call puts the light's nodepath into the render state.
        # this enables the shader to access this light by name.

        render.setShaderInput("light", light)
        
        #create NodePath
        
        myBlueNode = NodePath(PandaNode("B"))
        myBlueNode.reparentTo(render)
        myOrangeNode = NodePath(PandaNode("O"))
        myOrangeNode.reparentTo(render)

        # The "normals buffer" will contain a picture of the model colorized
        # so that the color of the model is a representation of the model's
        # normal at that point.

        normalsBuffer=base.win.makeTextureBuffer("normalsBuffer", 0, 0)
        normalsBuffer.setClearColor(Vec4(0.5,0.5,0.5,1))
        normalsBuffer2=base.win.makeTextureBuffer("normalsBuffer2", 0, 0)
        normalsBuffer2.setClearColor(Vec4(0.5,0.5,0.5,1))
        self.normalsBuffer=normalsBuffer
        self.normalsBuffer2=normalsBuffer2       
        normalsCamera=base.makeCamera(normalsBuffer, lens=base.cam.node().getLens())
        normalsCamera.node().setScene(myBlueNode)
        normalsCamera2=base.makeCamera(normalsBuffer2, lens=base.cam.node().getLens())
        normalsCamera2.node().setScene(myOrangeNode)
        tempnode = NodePath(PandaNode("temp node"))
        tempnode.setShader(Shader.load(MYDIR+"/normalGen.sha"))
        tempnode2 = NodePath(PandaNode("temp node2"))
        tempnode2.setShader(Shader.load(MYDIR+"/normalGen.sha"))
        normalsCamera.node().setInitialState(tempnode.getState())
        normalsCamera2.node().setInitialState(tempnode2.getState())        

        #what we actually do to put edges on screen is apply them as a texture to 
        #a transparent screen-fitted card

        drawnScene=normalsBuffer.getTextureCard()
        drawnScene.setTransparency(1)
        drawnScene.setColor(1,1,1,0)
        drawnScene.reparentTo(render2d)
        self.drawnScene = drawnScene
        drawnScene2=normalsBuffer2.getTextureCard()
        drawnScene2.setTransparency(1)
        drawnScene2.setColor(1,1,1,0)
        drawnScene2.reparentTo(render2d)
        self.drawnScene2 = drawnScene2
        
        # this shader accepts, as input, the picture from the normals buffer.
        # it compares each adjacent pixel, looking for discontinuities.
        # wherever a discontinuity exists, it emits black ink.
                
        self.separation = 0.001
        self.cutoff = 0.3
        inkGenBlue=Shader.load(MYDIR+"/inkGen-blue.sha")
        drawnScene.setShader(inkGenBlue)
        drawnScene.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        drawnScene.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        
        inkGenOrange=Shader.load(MYDIR+"/inkGen-orange.sha")
        drawnScene2.setShader(inkGenOrange)
        drawnScene2.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        drawnScene2.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));     
        
        # Panda contains a built-in viewer that lets you view the results of
        # your render-to-texture operations.  This code configures the viewer.

        self.accept("v", base.bufferViewer.toggleEnable)
        self.accept("V", base.bufferViewer.toggleEnable)
        base.bufferViewer.setPosition("llcorner")

        # Load a dragon model and animate it.

        self.character=Actor()
        self.character.loadModel('models/nik-dragon')
        self.character.reparentTo(myBlueNode)
        self.character.loadAnims({'win': 'models/nik-dragon'})
        self.character.loop('win')
        self.character.hprInterval(15, Point3(360, 0,0)).loop()
        
        self.character2=Actor()
        self.character2.loadModel('models/nik-dragon')
        self.character2.reparentTo(myOrangeNode)
        self.character2.loadAnims({'win': 'models/nik-dragon'})
        self.character2.loop('win')
        self.character2.hprInterval(15, Point3(360, 0,0)).loop()
        self.character2.setPos(10, 10, 10)
        

        # these allow you to change cartooning parameters in realtime

        self.accept("escape", sys.exit, [0])
        self.accept("arrow_up", self.increaseSeparation)
        self.accept("arrow_down", self.decreaseSeparation)
        self.accept("arrow_left", self.increaseCutoff)
        self.accept("arrow_right", self.decreaseCutoff)
        
    def increaseSeparation(self):
        self.separation = self.separation * 1.11111111;
        print self.separation
        self.drawnScene.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        self.drawnScene2.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        
    def decreaseSeparation(self):
        self.separation = self.separation * 0.90000000;
        print self.separation
        self.drawnScene.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        self.drawnScene2.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));      
        
    def increaseCutoff(self):
        self.cutoff = self.cutoff * 1.11111111;
        print self.cutoff
        self.drawnScene.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        self.drawnScene2.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        
    def decreaseCutoff(self):
        self.cutoff = self.cutoff * 0.90000000;
        print self.cutoff
        self.drawnScene.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        self.drawnScene2.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));        
        
t=ToonMaker()

run()