Soft edges with normal maps


I know you don’t like feature requests and links to features of other products, but I can’t write a script like this by myself.

Maybe you know the bevel feature from kerkythea (Bevel Mapping), MentalRay (round edges in the MR Arch & Design shader) or “f-edge for Max” from

Bevel mapping is a bevel without additional geometry, only based on normal or bump maps. You don’t have to setup UV coordinates, this will be done by the plugin / script.

  • How can I start a script with such features, do you know some papers?
  • Or do you have the time and interrest to start developing such kind of script / plugin?

I like this kind of scripts / plugins. They are so much faster than geometry, specialy with AAO.
I have tried to reproduce this with a high poly version with the new bevel tools and “tangent normal map bakeing”. But the baking from Blender and the ATI NormalMapper are missing some of the bevels. Some parts are looking good, other edges are buggy. :frowning:
The AAO rendering with the normal map is much faster than with bevel modifier on every sharp edge.

I would be happy to see such a feature in Blender and will support every effort to develop such a bevel material / mapping feature. :slight_smile:

I’m a blender head and have some basic python knowledge, but this task is a little bit to much for me, please help. :wink:

EDIT: sorry, can’t link to the kerkythea thread. Search for “bevel” with the forum search in the kerkythea forum.

hmm…i second that

Early bevel mapping test. But does not work like expected.
I have merged some parts from the “” for the angle calculation and parts from the “” for drawing lines in a texture.

I’m sure there is a much smarter solution. :wink:

The first Prepre-Alpha version of the bevel mapping script:

import Blender
import bpy
import BPyMesh
import BPyMessages

from math import *

def AddExtension(filename, object_name):
    ext = ".tga"
    hasExtension = (ext in filename or ext.upper() in filename)
    if object_name and hasExtension:
        filename = filename.replace(ext, "")
        hasExtension = False
    if object_name:
        filename += "_" + object_name
    if not hasExtension:
        filename += ext
    return filename
def GetDefaultFilename():
    filename = Blender.Get("filename")
    filename = filename.replace(".blend", "_bm")
    filename += ".tga"
    return filename

def ExtractUVFaces(mesh, allface):
    if allface: return [f.uv for f in mesh.faces]
    else:  return [f.uv for f in mesh.faces if f.sel]

def Buffer(height=16, width=16, profondeur=1,rvb=127 ):  
    reserve l'espace memoire necessaire  
    print"Memory  : %ikB" % (myb/1024)
    return b

def write_tgafile(loc2,bitmap,width,height,profondeur):  


    for t in range(18):

    entete0[12]=chr(width % 256)
    entete0[14]=chr(height % 256)

    # Origine_en_haut_a_gauche
    print"  ...writing tga..."
    for t in entete0:
    redpx=chr(0) + chr(0) + chr(255)
    blackpx=chr(0) + chr(0) + chr(0)
    whitepx=chr(255) + chr(255) + chr(255)
    for t in bitmap:
        if t==255:
        elif t==127:
            f.write(chr(127) + chr(127) + chr(127))
        elif t==0:

def BM_Export_TGA(mesh, size, wsize, file):
    extreme_warning = False

    DotVecs = Blender.Mathutils.DotVecs
    Ang= Blender.Mathutils.AngleBetweenVecs

    UVFaces = ExtractUVFaces(mesh, True)
    minx = 0
    miny = 0
    scale = 1.0
    step = 0

    img = Buffer(size,size)
    max_index = size - 1 # max index of the buffer (height or width)
    fnum = 0
    fcnt = len (UVFaces)
    for i, f in enumerate(mesh.faces):
        fc= f.cent
        # print "FaceCenter: %f %f %f" % (fc.x, fc.y, fc.z)
        fno =
        # print "FaceNormal: %f %f %f" % (fno.x, fno.y, fno.z)
        fuv = UVFaces[i]
        vnum = 0
        for v in f.v:
   # get a scaled down normal.
            # print dir(v)
            # print "VertexNormal: %f %f %f" % (vno.x, vno.y, vno.z)
            dot= DotVecs(vno, - DotVecs(vno, fc)
            # vert_tone_count[v.index]+=1
                a= Ang(vno, fno)
            if dot>0:             # Convex
                a= min(20, a)
                color = 255
                # print "Convex Angle: %f" %a
                # if not PREF_SHADOW_ONLY:
                    # vert_tone[v.index] += a
            elif dot == 0:         # Planar
                color = 1
            else:                 # Concave
                a= min(90, a)
                color = 0
                # print "Concave Angle: %f" %a
                # vert_tone[v.index] -= a
            if vnum < len(fuv) - 1:
                co2 = fuv[vnum + 1]
                co2 = fuv[0]
            print "co1: %f %f" % (co1.x, co1.y)
            print "co2: %f %f" % (co2.x, co2.y)
            step = int(ceil(size*sqrt((co1[0]-co2[0])**2+(co1[1]-co2[1])**2)))    
            if step:
                    for t in xrange(step):
                            x = int(floor((co1[0] + t*(co2[0]-co1[0])/step) * max_index))
                            y = int(floor((co1[1] + t*(co2[1]-co1[1])/step) * max_index))
                            for dx in range(-1*wsize + 1, wsize):
                                wx = int ((x - minx + dx) * scale)
                                for dy in range(-1*wsize + 1, wsize):
                                    wy = int ((y - miny + dy) * scale)
                                    co = wx * 1 + wy * 1 * size
                                    img[co] = color
                except OverflowError:
                    if not extreme_warning:
                        print "Skipping extremely long UV edges, check your layout for excentric values"
                        extreme_warning = True


def ExportCallback(f):
    obj = Blender.Scene.GetCurrent()
    time1= Blender.sys.time()

    if not obj or obj.type != "Mesh":
    is_editmode = Blender.Window.EditMode()
    if is_editmode: Blender.Window.EditMode(0)
    mesh = obj.getData(mesh=1)
    if not mesh.faceUV:
        if is_editmode: Blender.Window.EditMode(1)

    name = AddExtension(f,

    if is_editmode: Blender.Window.EditMode(1)

    print "TGA export is running..."
    BM_Export_TGA(mesh, bImageSize, bBevelSize, name)

    print "     ...finished exporting in %.4f sec." % (Blender.sys.time()-time1)

def Export():
    Blender.Window.FileSelector(ExportCallback, "Save BevelMap (%s)" % "tga", GetDefaultFilename())

def main():

    global bImageSize, bBevelSize, bObFile # , bWrap, bAllFaces
    print "Bevel mapping"

    bImageSize = 1024
    bBevelSize = 3

    print         "Imagesize: %ipx" % bImageSize
    print         "Bevelsize : %ipx" % bBevelSize
if __name__=='__main__':

  • no Gui
  • you have to select a mesh
  • the mesh must have UV-coords. (Unwrap smart projection)
  • The scene must be saved before running the script
  • copy the script in a blender texteditor and press “Alt+P”

Than it works like the UV-export (only TGA).

Here a simple Mesh:

If I assign an “Edgesplit modifier”, I got this result:

The idea is to find convex und concave edges and paint a black or white line on the edge.
The red Lines are flat edges and will later get no bump.

As you can see, there are a lot of Problems.

  • With the logic from “vertexpaint self AO” I can detect flat faces only with an applied “edgesplit modifier”
  • some edges are not correct detected.
  • I need an other algorithm to detect the hard, converx/concave edges. I think I must compare faces.
  • It’s really complicated to combine the UV Faces with the geometry edge detection in the mesh.

In some cases I get what I want, But I think it’s not the smartest way.
Do you have some suggestions?

I don’t really understand the logik with faces, vertices and UV Faces and UV Vertices.
I can iterate throught the UV faces (like in UV Export) or throught the geometry faces (like in the selfAO).
Currently I handle the Mesh coords with the same logik like the UV Coords. With the same face and vertex index. Is this always the same order?

Hmm … I think you don’t have to test the script.
The UV order seems to be different to the face order. :frowning:

A little bit complexer mesh, totally chaos: :frowning:

not bad results for the beginning, although it’s clear that a shader would be more adequate. Maybe could be made as a new pynode! do you know about the pynodes being in svn?

I have heard about pynodes in SVN now, but never tried them. I’ll test them for bevel mapping - thanx for the hint.

The advantage of my first solution would be the possibility to use the maps for realtime, too. (If I can manage the bump logic, the next step would be to generate normal maps)

I’m coming closer to some result. :slight_smile:

  • I can now detect open edges (green lines).
  • I can detect sharp edges (based on angle between the two faces)

But now I’m fighting with the dot product to get the direction of the angle. If it’s concave or convex.

Can someone take a look a this snippet, please?

            no1 = face_users[0].no
            no2 = face_users[1].no
            dot= DotVecs(no1, face_users[0].cent) - DotVecs(no2, face_users[0].cent)
            # dot= DotVecs(no2, no1)
                a= Ang(no1, no2)

            if dot>0:             # Convex
                # a = min(20, a)
                if a > 60: color = 255
                else: color = 127
            elif dot == 0:
                color = 127
                if a > 60: color = 0
                else: color = 127
            print 'no1:',no1,'no2:',no2, 'dot:', dot, 'a:',a

I think my problem is the line “dot= DotVecs(no1, face_users[0].cent) - DotVecs(no2, face_users[0].cent)”.

If I use “dot= DotVecs(no1, face_users[0].cent) - DotVecs(no2, face_users[1].cent)” it looks wrong, too. With the same center it’ looks better, but wrong also.

My tests with pynodes are went in the wrong direction. I don’t understand how to analyse an area around a pixel. So I’ll follow my first idea with the tga.

With some help of macouno the first Alpha is working now!

Will post the first version of a working script in the next days. :eyebrowlift2:

The first test with a bevel map as a normal texture:

The bevel map needs to have really accurate UVs, that’s why I have to optimize the existing UV-map inside the script. :frowning:
I have to move every UV a little bit to a pixel position of the texture. It depends on the bevel map texture size now.
The UV- accuracy of the is far not enought.

In the bevel test I have blured the bevel map a little bit in photoshop, thats all.

Hey Conz3d

I added a small explanation of the convex vs concave solution to the bottom half of this page on my site, if you’re interested:

Let me know if that makes sense :wink:
By the way… for testing purposes I’d try to make something a bit more extreme, to really show people what it does.

well i don’t know how well this would work,
as you’ll have to unwrap it your self but,
you can use: this

macouno: thank you for your help with the math. With your image the logic behind the formula is clear now.

Felix_Kütt: thank you for the object. But I need this function for a specific project. That’s why my testobject has this shape.

I need the bevel mapping for this visualisation:

All elements of the structure are in my test object. The calculation of the bevel map take some time, so I needed a small object with only a few faces/edges.

I have tried the new bevel in Editmode and the bevel modifier, but both have some problems on some corners. :frowning:
And with the bevelmodifier the AAO takes to much time to render. With a bevel map it’s so much faster.

That’s the reason why I will stay at my test object. I’ll make a sample with a bigger bevel to show the function of my script. But than I have to figgure out on which side of the edge the face sits on the uv map. Without this I need a to big margin between the islands on the UV map.

Did this script make any more progress? I could definitely see it being very useful for quickly making normal maps for models for use in games and whatnot, but couldn’t see it mentioned any where other than this thread.

oh, this is a very old thread you found here. o.O

Sorry to say, that I have never finished this script. The project where I had demand for this script was finished before I could get good results with it. There are to many problems with the script and the concept.

  • it needs one big UV for the object ( 4096x4096 was to small for my project)
  • the uv has to be very accurate
  • doesn’t look really good

I don’t know if this is possible, but I would prefer a logic that generates the bump map at rendertime for every face/edge, not one big map for the object. Or faces should share parts of the map. But for that you have to know and work with the size of the face in worldspace.

Maybe with bmesh it’s a little bit easier to build such an addon, now. (there are more informations about the angles in a mesh with bmesh)

I would love to see such a feature in blender. But I’m not the person with enought programming skills to build it.
And AFAIK there is no other script that covers this idea. I only know something similar in 3ds max and vray with “VRayEdgesTex”, but never used it. Years ago there was a plugin for Max with such a feature too, called f-edge)

I would donate some euros to a developer who builds something like f-edge for blender.