freestyle experimental images

The images for the post here:
http://blenderartists.org/forum/showpost.php?p=1566704&postcount=814

Attachments






More testing

Attachments


more testing

global_z_depending_color-01.py

# global_z_depending_thickness.py
# Tamito KAJIYAMA <25 November 2008>
# Free software under the terms of GNU GPL (version 2 or later)
# added WithinCameraViewUP1D
# attempt to fake color like normalized blender z pass
 
import Freestyle
from freestyle_init import *
from logical_operators import *
from ChainingIterators import *
from PredicatesU1D import *
from shaders import *

scene = Freestyle.getCurrentScene()
w = scene.render_data.resolution_x
h = scene.render_data.resolution_y
 
class WithinCameraViewUP1D(UnaryPredicate1D):
    def getName(self):
        return "WithinCameraViewUP1D"
    def __call__(self, inter):
        for v in [inter.A(), inter.B()]:
            x = v.getProjectedX()
            y = v.getProjectedY()
            if 0 <= x <= w and 0 <= y <= h:
                return 1
        return 0
 
class pyGlobalZInspector(UnaryPredicate1D):
    """Inspect the minimum and maximum Z depth values of all vertices
    in the scene."""
    z_min, z_max = 1e+8, -1e+8
    def __call__(self, inter):
        it = inter.verticesBegin()
        while it.isEnd() == 0:
            z = it.getObject().getProjectedZ()
            if z < pyGlobalZInspector.z_min:
                pyGlobalZInspector.z_min = z
            if z > pyGlobalZInspector.z_max:
                pyGlobalZInspector.z_max = z
            it.increment()
        return 1
 
class pyGlobalZDependingThicknessShader(StrokeShader):
    """Give a variable thickness to a stroke depending on Z depth
    values of the vertices in the stroke.  The thickness at a vertex
    is proportional to the vertex's relative Z depth (0.0 to 1.0) in
    the range of global minimum and maximum Z depth values examined by
    pyGlobalZInspector."""
    def __init__(self, min_thickness, max_thickness):
        StrokeShader.__init__(self)
        self.__min = min_thickness
        self.__max = max_thickness
    def shade(self, stroke):
        global_z_min = pyGlobalZInspector.z_min
        global_z_max = pyGlobalZInspector.z_max
        z_diff = 1.0 / (global_z_max - global_z_min)
        z_min, z_max = 1e+8, -1e+8
        it = stroke.strokeVerticesBegin()
        while it.isEnd() == 0:
            v = it.getObject()
            z = (v.getProjectedZ() - global_z_min) * z_diff
            thickness = (1.0 - z) * self.__max + z * self.__min
            v.attribute().setThickness(thickness/2, thickness/2)
            it.increment()
            
class pyGlobalZDependingColorThicknessShader(StrokeShader):
    """Give a variable thickness to a stroke depending on Z depth
    values of the vertices in the stroke.  The thickness at a vertex
    is proportional to the vertex's relative Z depth (0.0 to 1.0) in
    the range of global minimum and maximum Z depth values examined by
    pyGlobalZInspector.
    Also set closest z stroke color to 0, furtherst to 1"""
    def __init__(self, min_thickness, max_thickness):
        StrokeShader.__init__(self)
        self.__min = min_thickness
        self.__max = max_thickness
    def shade(self, stroke):
        global_z_min = pyGlobalZInspector.z_min
        global_z_max = pyGlobalZInspector.z_max
        z_diff = 1.0 / (global_z_max - global_z_min)
        z_min, z_max = 1e+8, -1e+8
        it = stroke.strokeVerticesBegin()
        while it.isEnd() == 0:
            v = it.getObject()
            att = it.getObject().attribute()
            z = (v.getProjectedZ() - global_z_min) * z_diff
            thickness = (1.0 - z) * self.__max + z * self.__min
            v.attribute().setThickness(thickness/2, thickness/2)
            att.setColor( z, z, z)
            att.setAlpha( 1 )
            it.increment()
 
class pyTestBP1D(BinaryPredicate1D):
    """True if both operands have the same shape ID and both of them
    are either external contours or non-external contours (i.e. unless
    one operand is an external contour and the other is not)."""
    def __init__(self):
        BinaryPredicate1D.__init__(self)
        self.__bpred = SameShapeIdBP1D()
        self.__upred = ExternalContourUP1D()
    def __call__(self, inter1, inter2):
        return self.__bpred(inter1, inter2) and \
               self.__upred(inter1) == self.__upred(inter2)
 
upred = AndUP1D(WithinCameraViewUP1D(),QuantitativeInvisibilityUP1D(0))
bpred = pyTestBP1D()
Operators.select(upred)
Operators.select(pyGlobalZInspector())
Operators.bidirectionalChain(ChainPredicateIterator(upred, bpred),
                             NotUP1D(upred))
shaders_list = [
    SamplingShader(5),
    #ConstantColorShader(0.0, 0.0, 0.0),
    #pyMaterialColorShader(),
    #pyGlobalZDependingThicknessShader(.5, 5)
    pyGlobalZDependingColorThicknessShader(1.5, 7.5)
    ]
       
#Operators.create(pyDensityUP1D(8,0.4, IntegrationType.MEAN), shaders_list)
Operators.create(TrueUP1D(), shaders_list)
 
print ( "global_z_min =", pyGlobalZInspector.z_min)
print ("global_z_max =", pyGlobalZInspector.z_max)

Attachments






04-26980.blend (66.6 KB)

Two passes, cubes are z composited together after freestyle with false color z depth are added in

This was done with 2.5 freestyle branch 26980

global_z_depending_color-06.py

# global_z_depending_color.py
# Tamito KAJIYAMA <25 November 2008>
# Free software under the terms of GNU GPL (version 2 or later)
# added WithinCameraViewUP1D
# false color to match normalized blender z pass
# do this with fixed values that can be matched to offset/size in map node for blender z pass
# line thickness by depth
 
import Freestyle
from freestyle_init import *
from logical_operators import *
from ChainingIterators import *
from PredicatesU1D import *
from shaders import *


# reduce workload on freestyle by only selecting that which is within view

scene = Freestyle.getCurrentScene()
w = scene.render_data.resolution_x
h = scene.render_data.resolution_y
 
class WithinCameraViewUP1D(UnaryPredicate1D):
    def getName(self):
        return "WithinCameraViewUP1D"
    def __call__(self, inter):
        for v in [inter.A(), inter.B()]:
            x = v.getProjectedX()
            y = v.getProjectedY()
            if 0 <= x <= w and 0 <= y <= h:
                return 1
        return 0
 

# inspect the Z values, 
# getProjectedZ() returns a partially normalized value that will change depending on the 
# extreme min and max for the normalize which could change from shot to shot, 
# getZ() returns the distance from the camera as a negative number, ie: 
# -4 Z in freestyle would equate to +4bu Z in blender camera space 
class pyGlobalZInspector(UnaryPredicate1D):
    """Inspect the minimum and maximum Z depth values of all vertices
    in the scene."""
    z_min, z_max = 1e+8, -1e+8
    non_normalized_z_min, non_normalized_z_max = 1e+8, -1e+8
    def __call__(self, inter):
        it = inter.verticesBegin()
        while it.isEnd() == 0:
            z = it.getObject().getProjectedZ()
            non_normalized_z = it.getObject().getZ()
            if z < pyGlobalZInspector.z_min:
                pyGlobalZInspector.z_min = z
            if z > pyGlobalZInspector.z_max:
                pyGlobalZInspector.z_max = z
            if non_normalized_z < pyGlobalZInspector.non_normalized_z_min:
                pyGlobalZInspector.non_normalized_z_min = non_normalized_z
            if non_normalized_z > pyGlobalZInspector.non_normalized_z_max:
                pyGlobalZInspector.non_normalized_z_max = non_normalized_z                
            it.increment()
        return 1
 


class pyFixedZThicknessShader(StrokeShader):
    """Give a variable thickness to a stroke depending on Z depth
    values of the vertices in the stroke.  All strokes at or closer than near
    will be rendered at max_thickness, all strokes further away than far
    will be rendered at min_thickness, all other strokes will be a
    correct value in between
    values outside of near, far will be clamped to 0, 1
    near/far are in blender units - positive heading away from camera"""
    def __init__(self, min_thickness, max_thickness, near, far):
        StrokeShader.__init__(self)
        self.__min = min_thickness
        self.__max = max_thickness
        self.__near = near
        self.__far = far
    def shade(self, stroke):
        z_diff = 1.0 / (self.__far - self.__near)
        it = stroke.strokeVerticesBegin()
        while it.isEnd() == 0:
            v = it.getObject()
            normalized_z = ((v.getZ() * -1) - self.__near) * z_diff
            #normalized and now clamp to 0,1
            if normalized_z < 0:
                normalized_z = 0
            if normalized_z > 1:
                normalized_z = 1
            thickness = (self.__max - self.__min) * (1 - normalized_z) + self.__min
            v.attribute().setThickness(thickness/2, thickness/2)
            it.increment()    
    

class pyFixedZFalseColorShader(StrokeShader):
    """Give a variable false color to a stroke depending on Z depth
    values of the vertices in the stroke.  The color at a vertex
    is proportional to the vertex's relative Z depth (0.0 to 1.0) in
    the range of actual minimum and maximum Z depth values manually 
    passed in, which can be matched to blender map value node
    offset = 0 - near
    size = 1/(far - near)
    values outside of near, far will be clamped to 0, 1 and an error issued
    ideally the near/far will be large enough to make sure when converted
    back into a z in blender compositer the full range will be used
    near/far are in blender units - positive heading away from camera"""
    def __init__(self, near, far):
        StrokeShader.__init__(self)
        self.__near = near
        self.__far = far
    def shade(self, stroke):
        z_diff = 1.0 / (self.__far - self.__near)
        print ("z_diff: ",z_diff)
        it = stroke.strokeVerticesBegin()
        while it.isEnd() == 0:
            v = it.getObject()
            att = it.getObject().attribute()
            normalized_z = ((v.getZ() * -1) - self.__near) * z_diff
            #normalized and now clamp to 0,1
            if normalized_z < 0:
                normalized_z = 0
                print ("warning - false color shader has had to clamp z to 0")
            if normalized_z > 1:
                normalized_z = 1
                print ("warning - false color shader has had to clamp z to 1")
            att.setColor( normalized_z, normalized_z, normalized_z)
            att.setAlpha( 1 )
            it.increment() 


    
 
class pyTestBP1D(BinaryPredicate1D):
    """True if both operands have the same shape ID and both of them
    are either external contours or non-external contours (i.e. unless
    one operand is an external contour and the other is not)."""
    def __init__(self):
        BinaryPredicate1D.__init__(self)
        self.__bpred = SameShapeIdBP1D()
        self.__upred = ExternalContourUP1D()
    def __call__(self, inter1, inter2):
        return self.__bpred(inter1, inter2) and \
               self.__upred(inter1) == self.__upred(inter2)



# time to start stroke shader
 
upred = AndUP1D(WithinCameraViewUP1D(),QuantitativeInvisibilityUP1D(0))
bpred = pyTestBP1D()
Operators.select(upred)
#re enable this if you want a report of min and max z in the freestyle scene
"""Operators.select(pyGlobalZInspector())

print ("post proc global_z_min =", pyGlobalZInspector.z_min)
print ("post proc global_z_max =", pyGlobalZInspector.z_max)
print ("post proc non_normalized global_z_min =", pyGlobalZInspector.non_normalized_z_min)
print ("post proc non_normalized global_z_max =", pyGlobalZInspector.non_normalized_z_max)
"""

Operators.bidirectionalChain(ChainPredicateIterator(upred, bpred),
                             NotUP1D(upred))
shaders_list = [
    SamplingShader(5),
    # test, normalize 1 units to 26 units away from camera, so blender map node is
# offset -1, size 1/25 = 0.04
    pyFixedZThicknessShader(1.5, 10.5, 8, 14),
    pyFixedZFalseColorShader(1, 26)
    ]

Operators.create(TrueUP1D(), shaders_list)

Attachments




freestyle-z-blend-test-05.blend (68.6 KB)

I am really hoping this will get integrated into BLENDER. Do you have any info on the status? ?I know there is a special build, I wonder why it has not been integrated into the trunk?

This is just where I put images for the main thread:
http://blenderartists.org/forum/showthread.php?t=89986

If you take a look at the main thread, stability improvements, features and updates have been happening multiple times a week. It will go into trunk when ever the powers that be decide its time, but right now it is about as stable as 2.5 trunk, gets trunk updates merged over weekly and getting better all the time.

anaglyph stereo test

Attachments




Dazzle, I’ve been wanting to comment, but didn’t want to bog down the already too-huge primary thread. Glad I happened upon this one!

Just wanted to say “thanks” for sharing your efforts on testing out freestyle branch, esp. the z-depth-related issues. I’ve been needing to delve into this stuff, but have been (and still am) quite daunted by all the python programming that seems to be needed. For you to post examples of your research is (and will continue to be) a great resource for the rest of us “interested” parties!

Much appreciated, and good work! :smiley:

(PS: oh, and I’m lovin’ the anaglyph stuff!)

cool anaglyph renders!

Hey Dazzle
Wow Blender+freestyle+3d glasses.
I tossed on my specs on, and of course it took a few minutes for eyes to get used to it,but these Suzes look great.Now I wonder how a short or a feature would look with this type of effect.

Someones gonna do it eventually.

Btw.Your Ideas and theories on z depth for freestyle are brilliant.

I cannot disclose (yet) what I am working on, but it involves stereo 3d, and if I can get Freestyle to work in the pipeline it is going to be pretty damn sweet.

Perhaps we should be having this conversation on the main Freestyle thread. I have been trying to do my bit to drive development along on Freestyle. There seems to be technically minded coders around in the newbie developer gatherings looking to contribute to 2.5, and I was thinking maybe we should be driving a few of em Freestyles way.

Hopefully I am not stepping on T.K.s toes here. He has been doing good work.

On the python and styles front I am going to put the word around on the 2.5 python thread with a few ideas that someone may want to work on. Seeing if they can get the style modules to take parameters from the 2.5 interface. On the Freestyle c/cpp front, perhaps some fresh eyes to look for memory leaks, pointer errors and why sometimes animation renders fall over after a few frames.

comments going up in main freestyle thread shortly
edit - done
http://blenderartists.org/forum/showthread.php?p=1577349#post1577349

Attachments





import Freestyle
from ChainingIterators import *

from freestyle_init import *
from logical_operators import *
from PredicatesU1D import *
from PredicatesB1D import *
from Functions0D import *
from shaders import *


"""
take global x location of empty called 'Free'
it should be animated between global 0 and 1 BU
the variable 'adjust' is then clamped to a min/max of 0/1
"""

adjust = 1
obs = bpy.data.objects
for o in obs:
    if o.name == "Free":
        adjust = o.location[0]

adjust = max( 0, (min( adjust, 1)) )    
    
print("adjust value:",adjust)




Operators.select(QuantitativeInvisibilityUP1D(0))

Operators.bidirectionalChain(ChainSilhouetteIterator(),NotUP1D(QuantitativeInvisibilityUP1D(0)))
## Splits strokes at points of highest 2D curavture 
## when there are too many abrupt turns in it
func = pyInverseCurvature2DAngleF0D()
Operators.recursiveSplit(func,  pyParameterUP0D(0.2,0.8), NotUP1D(pyHigherNumberOfTurnsUP1D(3, 0.5)), 2)
## Keeps only long enough strokes
Operators.select(pyHigherLengthUP1D(100))
## Sorts so as to draw the longest strokes first
Operators.sort(pyLengthBP1D())

shaders_list =     [
        pySamplingShader(10),
        #randomizes the curve
        BezierCurveShader(30),
        # thin range 0-4, thick range 5-25
        pyNonLinearVaryingThicknessShader(4 - 4*adjust, 25 - 20*adjust, 0.6),
        # fade in gray from .2 (adjust =1) to 0 (black, adjust = 0)
        ConstantColorShader(0.2*adjust, 0.2*adjust, 0.2*adjust, 1 ),
        # trim off 10 to 45 from end of strokes
        TipRemoverShader(10 + (adjust * 35) )
        ]
        
Operators.create(TrueUP1D(), shaders_list)



Attachments





3 examples of round tip shader, with a blank example at same AA for AA comparison

Attachments





Figured out what was going on with jaggy AA freestyle renders. Examples here, explanation on main thread.

In short - FSA = good, AA + alpha = bad

Attachments




Three blend files
from 2.5 alpha 2 Freestyle branch svn 27228

used to explain alpha/z/freestyle/FSA issues.


Dazzle: thanks for the PREMUL tip in the comp nodes, I’m sure many of us will implement this in our freestyle node trees.
I was thinking about jaggy freestyle, FSA and suddenly twigged the problem was one and the same. FSA completely solved the problem. The premul is a work around for AAed alphas, as you are about to see.

Hopefully what I am about to do here will be educational and helpful for all freestyle users.

I have uploaded three blend files here:
http://blenderartists.org/forum/showpost.php?p=1582380&postcount=16

from 2.5 alpha 2 Freestyle branch svn 27228
I will use these to explain alpha/z/freestyle/FSA issues, some of the work arounds and gotchas.

The examples in these files are not pretty to the eye, but illustrate the issues.

First example - how AA causes issues with Z compositing.
Open up the first file, ‘z-fsa-example.blend’. Set AA on and to 8, be sure turn FSA off (this is the Full Sample tick box directly below the AA settings). F12 will render this to the first render slot.
Zoom in (scroll middle mouse button) on where the black bars cross the cube. Notice the blue background has bled through in the AA on the black bars.
Change to render slot 2. Go back to the render window and re-enable AA8, and turn FSA on. F12 to render, and notice all the extra images that flash up and the messages in the text console noting the creation of 8 EXR files.
Zoom in on where the black bars cross the cube. Notice the lack of blue bleed through. This image is as it should be.
In the zoomed in view, you can press the J key to swap between render slot 1 and 2 to see the difference.

Second example - how AA causes issues with Alpha compositing.
Open up the second file, ‘alpha-fsa-example.blend’. Set AA on and to 8, be sure turn FSA off. F12 will render this to the first render slot.
Zoom in (scroll middle mouse button) on where the black bars cross the cube. Notice the blue background has bled through in the AA on the black bars.
Change render slot. Go back to the render window and make sure you have AA on and set to 8, and turn FSA on. F12 to render, and notice all the extra images that flash up and the messages in the text console noting the creation of 8 EXR files.
Zoom in on where the black bars cross the cube. Notice the lack of blue bleed through. This image is as it should be.
In the zoomed in view, you can press the J key to swap between render slots to see the difference.

What is going on? With just using AA, the blue background of the grill layer in being blended into the AA of the pixles on the edges of the grill. When this layer is composited in using Z or Alpha over the cube, the blue background goes with it.
With FSA, 8 separate passes are created which represent one each of the 8 samples that would happen in the pixels, each pass is separately composited THEN they are AA’ed together. Each pass that has been composited has no AA of its own in front of a blue background, no bleed through of the blue background into the pixels before they are composited, but then they are AAed together to make nice smooth lines. To get your head around it, you could look at the individual .EXR files that are created during the FSA pass, the names and location of which come up in the text console. Notice that individually they don’t look AAed, but they have different step patterns in their jaggyness.

Now for the third example. freestyle-premul-alpha-example.blend
This will hopefully show people how to do premul-alpha freestyle composits using separate render layers, but it also shows another issue that FSA will fix.

Open up the file.
Notice the World/Horizon Color is set black.
Go over to Render settings. Notice there are two layers.
“cube-only-layer” has Include - Solid turned on and Freestyle turned off
and
“freestyle-only-layer” has Include - Solid turned off and Freestyle turned on
Select the “freestyle-only-layer” layer and make sure there is a style module - japanese bigbrush, selected as the style. This can get lost when .blends are moved around (known bug).
F12 render.
Change render slots to slot 2. Go back to nodes, notice there are two render layers, one has the cube only, one has the freestyle only, and that the ‘convert premul’ option in the alpha over was ticked. Untick it, and render again.
J key to switch render slots between the two renders. Zoom in.
Keep switching back and forth. The difference is very subtle, but with no premult, the line is slightly thicker and more jaggy.

Not a dramatic difference. But it becomes more apparent in extreme situations, like white lines on black.

Now, wanna see something that only FSA can fix?
Go to World, and change the horizon color from black to bright dramatic blue. Make that blue SCREAM.
Render twice, to two different render slots, one with Premul, one with out.

Oh noes.
Very very very broken.
The blue has bleed through on all those AA pixels, and the non premul has LOTS of AA pixels.
With no premul, the blue is dramaticly fringing the freestyle line, with premul it is downplayed, but still there.
For a laugh, turn AA off, and the blue fringing goes away all together, because there are no mixed forground/background pixels.

It is important to remember, premul alpha compositing assumes that you created your layer on a black background. The AA will always bleed the background color in, and if it is anyting other than black, the result will show through in the composit.

FSA does the composit first, so no background bleeds through, THEN the AA. Which will fix this.
At the moment Freestyle will make the FSA EXR files (look at the text console to see where and what names), which you have to manually composit in and merge, but if you do, no blue fringe.

Hopefully this all makes sense.
All three examples show something that FSA would fix.
First and second example show general examples where you can use FSA to help in the composit.

The third shows people how to use separate render layers to premul composit a layer with just freestyle onto a layer with anything else. The gotcha, use a black background for the freestyle and premultiply the alpha composit.

And of course FSA would fix that.

Attachments

z-fsa-example.blend (65.9 KB)alpha-fsa-example.blend (65.8 KB)freestyle-premul-alpha-example.blend (64.6 KB)

Wow, excellent experiments.

For freestyle integration, it is up to the developer to let us know when it is ready for merging to head.

LetterRip

Testing pyNatureUP1D
Discussion in main thread

Attachments






The anaglyph renders are awesome!!