sub surface scattering script

here’s a little subsurface scattering script i wrote. its in its very early stages, so don’t expect too much.



you can see its effect by doing the following:

  1. creating a mesh and a light.
  2. create a material for the mesh which uses vertex colours for extra light
  3. on the mesh editing panel, toggle on the vertex colours
  4. change line 6 on the python script to the name of the mesh’s data block (not to be confused with the object that owns the mesh)
  5. alt-p and wait…
  6. render

enjoy!

from Blender import *
import math

time1 = sys.time()

# this value may need to be adjusted according to the object's size
scale = 0.005

# change the name to whatever mesh data block you want to use
mesh = NMesh.GetRaw("Suzanne")

# happy sss-ing
faces = mesh.faces
alteredFace = range(len(faces))
for faceNum in range(len(faces)):
	face = faces[faceNum]
	verts = face.v
	cols = face.col
	alteredVert = range(len(verts))
	for vertNum in range(len(verts)):
		vert = verts[vertNum]
		coords = vert.co
		col = cols[vertNum]
		scatter = NMesh.Col(col.r, col.g, col.b, col.a)
		for fn in range(len(faces)):
			f = faces[fn]
			vs = f.v
			cls = f.col
			for vn in range(len(vs)):
				v = vs[vn]
				c = v.co
				cl = cls[vn]
				dist = math.sqrt(pow(c[0]-coords[0], 2) + pow(c[1]-coords[1], 2)+ 

pow(c[2]-coords[2], 2))
				if dist > 0:
					scatter.r = scatter.r + int(cl.r * scale / dist)
					scatter.g = scatter.g + int(cl.r * scale / dist)
					scatter.b = scatter.b + int(cl.r * scale / dist)
		alteredVert[vertNum] = scatter
	alteredFace[faceNum] = alteredVert
	print faceNum

for faceNum in range(len(faces)):
	face = faces[faceNum]
	verts = face.v
	for vertNum in range(len(verts)):
		mesh.faces[faceNum].col[vertNum].r = alteredFace[faceNum][vertNum].r
		mesh.faces[faceNum].col[vertNum].g = alteredFace[faceNum][vertNum].g
		mesh.faces[faceNum].col[vertNum].b = alteredFace[faceNum][vertNum].b
	print faceNum

mesh.update()
Redraw()

time2 = sys.time()
print "calculation time: ", time2 - time1

:o I wish I’d be at home on a computer with a Blender installed in order to test this…

hi there
looks nice. not the first one but I’ll definatly give it a try…

i’ve seen images floating around of various people’s WIP sss routines, and thats what motivated me to give it a try myself :slight_smile: hopefully with enough of us messing around, its a feature thats bound to make itself a permanent resident sometime soon :stuck_out_tongue:

Its slow ( of coruse) but soooooo awsome!

i’ve seen images floating around of various people’s WIP sss routines, and thats what motivated me to give it a try myself :slight_smile: hopefully with enough of us messing around, its a feature thats bound to make itself a permanent resident sometime soon :P[/quote]
we shall rename it from “try” to “successfull try” :wink: nah its superb!

btw:
I’m not @home. so anyone about to try this thing with a makehuman head? :smiley:

very cool

Mhh…good.
Only you must try to optimize it or, at least, to think a way to have a sort of preview of final result. I’ve tested it with MakeHuman mesh, with a catmul-clark applied: it require 90 minutes…and the result was a too strong light…
Of course, I’ must reduce the power of lamp and try again…but…90 min!!

Do you know Xavier is working on other sss script too?

http://perso.wanadoo.fr/blenderic/SSSgui.html

And…it’s “real” sss or a “fake” ? I mean if you use one of official equation or not…

Thanks for sharing, I hope to see some development here…

Ciao,

                  Manuel

This scripts tends to give better results then Xavier’s script. although the GUI Maker wont hert to help to make gui for this script…

WHOA! THIS IS AWESOME!!! :o SO cool… :o

Yes, it seem more “soft”, but Xavier script work on MH mesh in about 10 minutes, while this, on the same machine (2.5 GH) require more of 90 minutes to work on the same mesh.

Of course, the Xavier script is a “fake” mathod, no real sss equations applied. So above I’ve asked if this is real algo or not: if yes, the time computation can be accetable, if not the Xavier script get surely some extra bonus for usability:-)

However a preview is needed. I think it can be obtained using some samples verts instead full mesh…

Psyco - and some minor changes
went from a time of 840 to 680
thats a 23% speed up.


# Psyco
try:
	import psyco
	psyco.full()
except:
	print 'It is advised that you install psyco on an x86 based PC'


from Blender import *
import math

time1 = sys.time()

# this value may need to be adjusted according to the object's size
scale = 0.005

# change the name to whatever mesh data block you want to use
mesh = NMesh.GetRaw("Suzanne")

# happy sss-ing
faces = mesh.faces
alteredFace = range(len(faces))
for faceNum in range(len(faces)):
   face = faces[faceNum]
   verts = face.v
   cols = face.col
   alteredVert = range(len(verts))
   for vertNum in range(len(verts)):
      vert = verts[vertNum]
      coords = vert.co
      col = cols[vertNum]
      scatter = NMesh.Col(col.r, col.g, col.b, col.a)
      for fn in range(len(faces)):
         f = faces[fn]
         vs = f.v
         cls = f.col
         for vn in range(len(vs)):
            v = vs[vn]
            c = v.co
            cl = cls[vn]
            dist = math.sqrt(pow(c[0]-coords[0], 2) + pow(c[1]-coords[1], 2)+ pow(c[2]-coords[2], 2))
            if dist > 0:
               i = int(cl.r * scale / dist)
               scatter.r += i;  scatter.g += i;  scatter.b += i
      alteredVert[vertNum] = scatter
   alteredFace[faceNum] = alteredVert
   print faceNum

for faceNum in range(len(faces)):
   face = faces[faceNum]
   verts = face.v
   for vertNum in range(len(verts)):
      mesh.faces[faceNum].col[vertNum] = alteredFace[faceNum][vertNum]
      #~ mesh.faces[faceNum].col[vertNum].r = alteredFace[faceNum][vertNum].r
      #~ mesh.faces[faceNum].col[vertNum].g = alteredFace[faceNum][vertNum].g
      #~ mesh.faces[faceNum].col[vertNum].b = alteredFace[faceNum][vertNum].b
   print faceNum

mesh.update()
Redraw()

time2 = sys.time()
print "calculation time: ", time2 - time1

i got an 85% speed up thats awsome (but I got a crappy computer)

curiously, has Xavier script been released?

not yet…

After a quick look into your code, I’ve seen it’s can be greatly improved. A lot of lines can be semplified (the access into the faces and verts data can be done without use index) , but the max improvement can be obtained using a radius threshold, for example, I’ve done a very quick test modifying the line
35:

if dist > 0:

replaced by

if dist > 0 and dist < 0.5:

this reduce the times of computation with suzanne (old computer too)

from 207.083676557

to

119.518807837

Of course, instead 0.5 you should put a percentage value, that can be
set by user too…

It’s not a good idea using psyco, in my esperience it’s better to use only blender modules…

MAnual- can you post your changes?

hey guys, the code i posted here was more like a proof of concept, and indeed needs a lot of work, i’ve already overhauled the way it deals with vertex data and i got a 500 face computation from 90 seconds down to 8 on my computer :slight_smile: i’m in the process of moving back to school, but once i get i get settled i’ll post the updated code. for enquiring minds, my method does not use any official equations, just an algorithm that gives a rough calculation. one of its biggest problems is that since it looks to nearby vertices for scattered light, denser areas of the mesh tend to get over-lit.

oh, my other comment is that all my preview images were done by running the script on the mesh BEFORE subdivision. its a good way of keeping things fast, and personally i find the results are better than with overly detailed meshes.

shteeve

p.s. from the preview images i LOVE the looks of some of the other unreleased sss scripts. i greatly doubt that mine will be a perfect solution, but i figured it would give people something to play around with and hopefully spur on the development of an official implementation :slight_smile:

why is the sss script from the other developer taking so long ? Not even a demo for us to play with :frowning:

hi guys! as promised here is the current state of the code. i’m pretty pleased with it.

new to version 0.2:
-restructured the way vertex data is managed… should give a large speed improvement
-now operates on the currently selected mesh so you don’t have to change any code (although it’ll crash if you’ve got nothing selected)
-provided a simple gui

Manuel… i’m interested in this alternate way to access vertices you were mentioning. as it stands, accessing the mesh and getting it into a useful structure still takes over 1/3 the processing time on my system. i will also try adding a threshold like you suggested.

to do:
-Manuel’s suggestions
-An automated radiosity pass for more sophisticated lighting
-animation (is it possible? i dunno!)

p.s. i’ve realized one definite problem with my method: it cannot simulate internal shadowing. consider an opaque ball under a translucent cloth that is backlit. the ball should block light scattering through the cloth, but since this method only looks at 1 object’s geometry, the ball will have no effect on the sss. oh well. :frowning:

shteeve

from Blender import *
import math

scale = 0.01
falloff = 0.5
object = Object.GetSelected()
mesh = object[0].getData()
faces = mesh.faces
vList = mesh.verts
vInf = range(len(vList))

def event(evt, val):
	if evt == Draw.ESCKEY:
		Draw.Exit()
		return

def button_event(evt):
	global scale, falloff
	
	if evt == 1:
		scale = B1.val
	elif evt == 2:
		falloff = B2.val
	elif evt == 3:
		time1 = sys.time()
		print "Gathering mesh data..."
		for faceNum in range(len(faces)):
			face = faces[faceNum]
			verts = face.v
			cols = face.col
			for vertNum in range(len(verts)):
				vert = verts[vertNum]
				col = cols[vertNum]
				for v in range(len(vList)):
					if vert.co[0] == vList[v].co[0] and vert.co[1] == vList[v].co[1] and vert.co[2] == vList[v].co[2]:
						if vInf[v] == v:
							vInf[v] = [[faceNum, vertNum]], col
						else:
							vInf[v][0].append([faceNum, vertNum])
							if vInf[v][1].r &lt; col.r:
								vInf[v][1].r = col.r
							if vInf[v][1].g &lt; col.g:
								vInf[v][1].g = col.g
							if vInf[v][1].b &lt; col.b:
								vInf[v][1].b = col.b
		
		print "Calculating..."
		factor = []
		for x1 in range(len(vList)):
			v1 = vList[x1].co
			factor.append([])
			for x2 in range(x1 + 1, len(vList)):
				v2 = vList[x2].co
				dist = pow(pow(v1[0]-v2[0], 2) + pow(v1[1]-v2[1], 2) + pow(v1[2]-v2[2], 2), falloff)
				if dist &gt; 0:
					factor[x1].append(scale / dist)
				else:
					factor[x1].append(0)
		
		sss = []
		for x1 in range(len(vList)):
			c1 = vInf[x1][1]
			sss.append(NMesh.Col(c1.r, c1.g, c1.b, c1.a))
			for x2 in range(x1):
				c2 = vInf[x2][1]
				sss[x1].r = sss[x1].r + int(factor[x2][x1 - x2 - 1] * c2.r)
				sss[x1].g = sss[x1].g + int(factor[x2][x1 - x2 - 1] * c2.g)
				sss[x1].b = sss[x1].b + int(factor[x2][x1 - x2 - 1] * c2.b)
			for x2 in range(x1 + 1, len(vList)):
				c2 = vInf[x2][1]
				sss[x1].r = sss[x1].r + int(factor[x1][x2 - x1 - 1] * c2.r)
				sss[x1].g = sss[x1].g + int(factor[x1][x2 - x1 - 1] * c2.g)
				sss[x1].b = sss[x1].b + int(factor[x1][x2 - x1 - 1] * c2.b)
			for ref in range(len(vInf[x1][0])):
				mesh.faces[vInf[x1][0][ref][0]].col[vInf[x1][0][ref][1]] = sss[x1]
		mesh.update()
		Redraw()
		
		time2 = sys.time()
		print "calculation time: ", time2 - time1

def gui():
	global B1, B2, B3
	BGL.glClearColor(0.7, 0.7, 0.7, 1)
	BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)

	BGL.glColor3f(0.75, 0.75, 0.75)
	BGL.glRecti(5, 5, 195, 55)
	BGL.glRecti(5, 60, 195, 115)

	BGL.glColor3f(0.65, 0.65, 0.65)
	BGL.glRecti(5, 115, 195, 135)

	BGL.glColor3f(1,1,1)
	BGL.glRasterPos2i(10, 122)
	Draw.Text("Effect Options")
	BGL.glRasterPos2i(10, 150)
	Draw.Text("SSS by shteeve")

	B1 = Draw.Number("Scale: ", 1, 10, 90, 180, 20, scale, 0.001, 1.0, "Intensity of effect (usually small numbers)")
	B2 = Draw.Number("Falloff: ", 2, 10, 65, 180, 20, falloff, 0.001, 1.0, "Falloff of effect")
	B3 = Draw.PushButton("Calculate!", 3, 10, 10, 180, 40, "Calculates effect.")	

Draw.Register(gui, event, button_event)