saving Vertex Colors into UV Texture .... how?

This is the situation:

I got a highpoly mesh, it is UV-unwrapped and it have vertex color information that I would like to bake into a high resolution image.

(The vertex colors came from the RGB normal information, but I guess it doesn’t really matters for the case).

My first impulse was to use the Texture Baker script. So I loaded a totally white image, set its mappint to COL and its mapped from the UV of the mesh.

Then activated Shadeless, VCol Light and VCol Pain. When to the script window, activated the “Texture Baker” script… but the render came out empty, just the background from the world settings.

I said “Crap!” :o and decided to try with the “UV Painter” script. It actually produced the right previsualization… but It seems that the user have almost no control of the produced image size… it looks like it just does a screen capture of the previsualization window… even the window header shows up in the final image !! :o (“Crap again!”

What I need is an square image, and at least 1024 x 1024.

Any ideas?

three things

use vcol paint and shadless [not vcol light] to get the colors perfect

use my texture bake script [setup the render size to be square 1024x1024]
http://www.geocities.com/z3r0_d/files/uvunfold_example2.zip

I don’t like the script that comes with blender also because it doesn’t do anything about the edges of your uvmapping…

well anway, using that techinique I was able to get:
http://home.earthlink.net/~nwinters99/temp/funky2.jpg

z3r0_d, thanks for the Vcol Paint tip… you just saved me about one extra hour of frustration.

About the Texture baking scrip… I decided not to give up with it, and I finally understud what was going wrong:

Things to have in mind when using JMS’s Texture Baking Script

  • It creates 3 objects: a flattened mesh, a cammera and a lamp.

  • all the 3 objects are placed on layer 1, if that layer is not visible while you use the script all you will get a is an empty render.

  • The flattened mesh is really flattend only after frame 100 (if you playback the animation, you can actually see the mesh becoming flat on the first 100 frames).

  • When the script is run, it changes temporary to the new cammera, set the render output to a square (i.e. 1024 x 1024), does the render, and then reset the render output and the active camera to the original ones.

  • Everytime you run the script a new set of objects is created. File size and memory consumption can go pretty high if you are not aware of that (by the time I realised the file was already 59 MB in size and the computer was freezing because "not-enough-memory errors).

*Since the 3 objects are left there after the script runs, if you manually reasing the new camera as the active one and set your render-output settings yourself you can override the rendersize limitations of the script. :wink:

Something that I really liked about z3r0_d’s script is that it tells you all those things beforehand, so you are not navigating blindlfolded on the sea. Z3r0_d’s script doesn’t have exactly the same peculiarities as JMS’s (for example it does it magic on the 2nd frame) but it goes the extra mile informing you what to expect.

Without any further aldo, here are the tests results:

The upper left image is the default suzanne with the normal-map-material, straight from the 2.36 release notes page.

The upper right image is the UV layout, in other words, how I unwrapped Suzzane.

The lower row is what you (finally) get with both scripts. Plese note that the scripts where not applied to the original Suzanne but to a very hight resultion version of her, the one used to generate the normal maps has 31,658 vertex.

At first sight, it looks like Nick’s map is wrong around the eyes area, but if you overlay the UV layout, you’ll see that it is totally fine and usable.

THE VEREDICT: They are both very good on their job. It is now a matter of personal taste witch one you’ll use.

BTW, Nick, I took the liberty of doing a small modification to your script, so that now the instructions show up in the Help-> “Script Help Browser” window, and also for it to show in the UV sub-menu. :slight_smile:


#!BPY
"""
Name: 'Bake Procedural Textures'
Blender: 234
Group: 'UV'
Tooltip: 'Bake procedural textures to image using UV coordinates.'
"""

## public domain
## author: z3r0_d (Nick Winters)
## no warranty (duh)
__author__ = "z3r0_d (Nick Winters)"

__bpydoc__ = """\
z3r0_d's TEXTURE BAKING SCRIPT
(better than jms's, for the moment)

INSTRUCTIONS:
1. select UV Mapped object with Procedural [material] textures

NOTE: only UV, Orco, Stick, Nor and Refl mapped textures will 
work properly

NOTE 2: materials can be linked to object or mesh data, this
script will only use the material linked to mesh data, 
regardless of which is active

2. run script [alt+p in text window, or run it from the 3d view's
Object menu, submenu Scripts]

3. configure render settings for square render:
aspect X and Y at 1, sizes at equal values

4. render [currently any frame other than #1], save, enjoy

5. if you don't want to tweak your material settings, 
feel free to delete the objects that were created:
9 meshes and a camera, furthest on the positive X axis 
[rightmost in top view]

consider using the "Full Osa" material option if your material looks 
aliased [also, you need to turn OSA on]

"""

import Blender
from math import sqrt

UVMULTIPLIER = 8 ## higher values result in shorter uv extensions
UVDECIMALS = 4 ## rounding for uv coordinates doubles testing for corners [remove-doubles like behavior]

# todo:
# allow for materials linked to object instead of ME [mesh is default]
# allow rendering of frames other than #1

def copyVert(v):
	nv = Blender.NMesh.Vert()
	for j in range(3):
		nv.co[j] = v.co[j]
		nv.no[j] = v.no[j]
	# I changed the meaning of the selection
	#nv.sel = v.sel
	for j in range(2): nv.uvco[j] = v.uvco[j]
	return nv

def uvUnfold(obj):
	print "BEGIN UV UNFOLDING NEW OBJECT GOODNESS"
	odata = obj.getData()
	
	# try to copy the mesh into a new object
	nobj = Blender.NMesh.PutRaw(odata,"UF."+odata.name)
	i = 0
	while not nobj:# mesh with that name already exists... [in future I may want to overwrite it]
		nobj = Blender.NMesh.PutRaw(odata,"UF."+odata.name+".%03i"%(i,))
	
	# no longer needed, lets save some memory
	del(odata)
	del(obj)
	
	ndata = nobj.getData()
	ndata.hasVertexColours(1)
	ndata.hasFaceUV(1)
	
	ndata.removeAllKeys()
	ndata.update()
	
	# unselect all verts
	# selected verts will be the new verts on the extruded uv edges
	for v in ndata.verts: v.sel = 0
	
	# create the uv seams, this is an inefficent [vertex wise, it is fast tho] method used here
	
	numStartFaces = len(ndata.faces)
	
	# dictionary of edges, key: tuple of 2 verts, value: list of faces which use this edge
	# used to determine which edges are uv seams
	#
	# the same list object should be used for key (vert1, vert2) as key (vert2, vert1)
	edges = {}
	for f in ndata.faces[:numStartFaces]:
		nv = len(f.v)
		for vi in range(nv):
			if edges.has_key( (f.v[vi], f.v[(vi+1)%nv]) ):
				edges[(f.v[vi], f.v[(vi+1)%nv])].append(f)
			else:
				edges[(f.v[vi], f.v[(vi+1)%nv])] = [f]
				edges[(f.v[(vi+1)%nv], f.v[vi])] = edges[(f.v[vi], f.v[(vi+1)%nv])]
	
	
	# create the seam faces, these are used so the seams on the resulting texture are less visible
	print "CREATING EXTRA UV SEAM FACES STEP 1"
	
	overlapWarning = 0
	
	# key: uv coordinate
	# value: list of faces
	# this will contain all uvs on seams, iff the number of faces is not 2, it is a corner
	uv_corners = {}
	
	for edge in edges.keys():
		faces = edges[edge]
		# this will contain every pair of 2 uv coords used in faces that used the edge
		uvcoords = []
		numUVedgeUsers = []
		for f in faces:
			# add the uvcoords used for edge into the list
			myuvco = (f.uv[f.v.index(edge[0])],f.uv[f.v.index(edge[1])])
			new_uv = 0
			for i in range(len(uvcoords)):
				if uvcoords[i] == myuvco:
					new_uv = 1
					break
			if not new_uv:
				uvcoords.append(myuvco)
				numUVedgeUsers.append(1)
		# I need to create new faces, several faces are on edges
		# if mesh has 3 or more faces on this edge, it is possible only one is at a uv edge
		# however, i will be more flexible than that
		#
		# find uv edges that are only used once
		if len(uvcoords) >= len(faces): # is a uv seam
			for f in faces:
				# should be myuvcos, is two uv coordinates
				myuvco = (f.uv[f.v.index(edge[0])],f.uv[f.v.index(edge[1])])
				# for uv corners, add f to corresponding uv coords values if not already there
				for i in (0,1):
					# rounded uv coordinate...
					ruvco = (round(myuvco[i][0],UVDECIMALS),round(myuvco[i][1],UVDECIMALS))
					if uv_corners.has_key(ruvco) == 0: uv_corners[ruvco] = [f]
					elif uv_corners[ruvco].count(f) == 0: uv_corners[ruvco].append(f)
				
				uvIndex = uvcoords.index(myuvco)
				if numUVedgeUsers[uvIndex] == 1:
					nv1 = copyVert(edge[0])
					nv2 = copyVert(edge[1])
					nv1.sel = 1
					nv2.sel = 1
					ndata.verts.append(nv1)
					ndata.verts.append(nv2)
					# start off the new face as a copy of f
					nf = Blender.NMesh.Face()
					nf.v = [edge[0],edge[1],nv2,nv1]
					## why I can't just set nf.col to [f.col[f.v.index(some vertex),more of the same] is beyond me
					## well, I don't understand why none of these work
					nf.col = [Blender.NMesh.Col(*rgbatuple) for rgbatuple in [(col.r, col.g, col.b, col.a) for col in [f.col[f.v.index(rvert)] for rvert in [edge[0],edge[1],edge[1],edge[0]] ]]]
					nf.flag = f.flag
					if f.image:
						nf.image = f.image
					nf.materialIndex = f.materialIndex
					##nf.no = f.no#doesn't seem to work, shouldn't be necescary anyway
					nf.smooth = f.smooth
					nf.mode = f.mode
					nf.transp = f.transp
					nf.uv = [(0.0,0.0),(0.0,0.0),(0.5,0.5),(0.5,0.5)]
					nf.uv[0] = myuvco[0]
					nf.uv[1] = myuvco[1]
					#
					nf.uv[3] = nf.uv[0]
					nf.uv[2] = nf.uv[1]
					reverse = not (f.v.index(edge[1])+1)%len(f.v) != f.v.index(edge[0])
					if reverse: reverse = -1
					else: reverse = 1
					uvco1 = f.uv[(f.v.index(edge[0])-reverse)%len(f.v)]
					uvco2 = f.uv[f.v.index(edge[0])]
					uvco3 = f.uv[f.v.index(edge[1])]
					uvco4 = f.uv[(f.v.index(edge[1])+reverse)%len(f.v)]
					vec1 = [uvco2[0] - uvco1[0], uvco2[1] - uvco1[1]]
					vec2 = [uvco3[0] - uvco4[0], uvco3[1] - uvco4[1]]
					## the value of UVMULTIPLIER on the next lines seems okay
					## and it will be used for traingles too
					len_vec1 = UVMULTIPLIER* sqrt(vec1[0]*vec1[0] + vec1[1]*vec1[1])
					len_vec2 = UVMULTIPLIER* sqrt(vec2[0]*vec2[0] + vec2[1]*vec2[1])
					if len_vec1 != 0.0: vec1 = [vec1[0] / len_vec1, vec1[1] / len_vec1]
					if len_vec2 != 0.0: vec2 = [vec2[0] / len_vec2, vec2[1] / len_vec2]
					nf.uv[3] =(nf.uv[0][0] + vec1[0], nf.uv[0][1] + vec1[1])
					nf.uv[2] = (nf.uv[1][0] + vec2[0], nf.uv[1][1] + vec2[1])
					# flip the quad, so normals are more right [in case they turn off no v. normal flip]
					if (f.v.index(edge[1])+1)%len(f.v) != f.v.index(edge[0]):
						# the quad needs to be flipped
						for i in [0,2]:
							# swap the verticies
							tv = nf.v[i]
							nf.v[i] = nf.v[i+1]
							nf.v[i+1] = tv
							# swap the uv coordinates
							tuv = nf.uv[i]
							nf.uv[i] = nf.uv[i+1]
							nf.uv[i+1] = tuv
							# swap the colors
							tc = nf.col[i]
							nf.col[i] = nf.col[i+1]
							nf.col[i+1] = tc
							# that ought to be it
					ndata.faces.append(nf)
				elif numUVedgeUsers[uvIndex] > 2 and not overlapWarning:
					print "SHARED UVs!? SHAME ON YOU!!!", numUVedgeUsers[uvIndex], uvIndex
					overlapWarning = 1
		# so I don't create new faces on the edge twice
		edges[edge] = []
		edges[(edge[1],edge[0])] = edges[edge]
	
	print "CREATING EXTRA UV SEAM FACES STEP 2"
	
	# corner method:
	"""
	create a dictionary associating verticies with the faces they are contained in
	[in the above loop?]
	
	something about uv seams... [if that vert exists in a uv seam]
	
	add the triangle extrusion for that vert, on each of the faces that contain it 
	and have the uv coordinate the seams is on
	
	so, perhaps the key should be the NMVert and the Uv Coord?
	"""
	
	# create the corner faces
	for myuv in uv_corners.keys():
		if len(uv_corners[myuv]) == 2: continue # limit unnecescary corner triangles
		for f in uv_corners[myuv]:
			#indx = f.uv.index(myuv) ## can't be used as coordinate was rounded
			indx = 5 # invalid

			for uvenum in enumerate(f.uv):
				if round(uvenum[1][0],UVDECIMALS) == myuv[0] and \
					round(uvenum[1][1],UVDECIMALS) == myuv[1]:
						indx = uvenum[0]
						break
			ntv1 = copyVert(f.v[indx])#copyVert(f.v[(indx+1)%len(f.v)])
			ntv2 = copyVert(f.v[indx])#copyVert(f.v[(indx-1)%len(f.v)])
			ntv1.sel = 1
			ntv2.sel = 1
			ndata.verts.append(ntv1)
			ndata.verts.append(ntv2)
			ntf = Blender.NMesh.Face()
			ntf.v = [f.v[indx], ntv1, ntv2]
			ntf.uv = [myuv, myuv, myuv]
			# offset uv coordinates...
			uvco1 = f.uv[(indx+1)%len(f.v)]
			uvco2 = f.uv[indx]
			uvco3 = f.uv[(indx-1)%len(f.v)]
			#uvco4 = f.uv[(f.v.indx(edge[1])+reverse)%len(f.v)]
			vec1 = [uvco2[0] - uvco1[0], uvco2[1] - uvco1[1]]
			vec2 = [uvco3[0] - uvco2[0], uvco3[1] - uvco2[1]]
			## the value of UVMULTIPLIER on the next lines seems okay
			## and it will be used for traingles too
			len_vec1 = UVMULTIPLIER* sqrt(vec1[0]*vec1[0] + vec1[1]*vec1[1])
			len_vec2 = UVMULTIPLIER* sqrt(vec2[0]*vec2[0] + vec2[1]*vec2[1])
			if len_vec1 != 0.0: vec1 = [vec1[0] / len_vec1, vec1[1] / len_vec1]
			if len_vec2 != 0.0: vec2 = [vec2[0] / len_vec2, vec2[1] / len_vec2]
			ntf.uv[1] =(ntf.uv[1][0] + vec1[0], ntf.uv[1][1] + vec1[1])
			ntf.uv[2] = (ntf.uv[2][0] - vec2[0], ntf.uv[2][1] - vec2[1])
			# more face property copying goodness
			ntf.col = [Blender.NMesh.Col(*rgbatuple) for rgbatuple in [(col.r, col.g, col.b, col.a) for col in [f.col[vindex%len(f.v)] for vindex in [indx,indx+1,indx-1] ]]]
			ntf.flag = f.flag
			if f.image:
				ntf.image = f.image
			ntf.materialIndex = f.materialIndex
			##ntf.no = f.no#doesn't seem to work, shouldn't be necescary anyway
			ntf.smooth = f.smooth
			ntf.mode = f.mode
			ntf.transp = f.transp
			#
			##ndata.verts.extend([ntv1,ntv2])
			ndata.faces.append(ntf)
	
	
	# no longer needed, lets save some memory
	del(edges)
	del(uv_corners)
	
	ndata.update()
	
	# flag if I have done the vert yet 
	# [so I don't end up with unused verts in the resulting mesh]
	donevert = list([0]*len(ndata.verts))
	
	print "CREATING REQUIRED EXTRA FACES AT UV SEAMS STEP 3"
	
	for f in ndata.faces:
		i = 0
		for v in f.v:
			#print v.index, len(ndata.verts)
			if donevert[v.index]:
				# copy the vert
				nv = copyVert(v)
				ndata.verts.append(nv)
				f.v[i] = nv
			else:
				donevert[v.index] = 1
			i += 1
	
	# no longer needed, lets save some memory
	del(donevert)
	
	ndata.update()
	ndata.insertKey(1, 'absolute')
	
	# now move the verts
	
	print "MOVING VERTS TO UV COORDINATES"
	
	for f in ndata.faces:
		i = 0
		for v in f.v:
			v.co[0] = f.uv[i][0]*4.0-2.0
			v.co[1] = f.uv[i][1]*4.0-2.0
			v.co[2] = 0.0
			if v.sel: v.co[2] = -0.01
			i += 1
	
	# just TwoSided
	ndata.setMode("TwoSided")
	
	ndata.update()
	ndata.insertKey(2)
	
	
	#not always necescary
	nobj.makeDisplayList()
	
	
	##Blender.Draw.Redraw()
	
	return nobj

objs = Blender.Object.GetSelected()
if len(objs) > 0 and objs[0].getType()=="Mesh":
	startTime = Blender.sys.time()
	print
	print "FINDING BOUNDS OF ALL OBJECTS", 
	# actually, I just want the highest X val so I can position the objects 
	# I create suitably far away [to the right]
	maxX = -1000000000.0
	objects = Blender.Object.Get()
	for obj in objects:
		# it is somewhat annoying, both empties and cameras should return None 
		# as their bound box, but emptys give errors, and cameras return none
		if obj.getType() == "Empty" or not obj.getBoundBox(): continue
		for vec in obj.getBoundBox(): # was this function introduced 2.33 or earlier?
			if vec[0] > maxX: maxX = vec[0]
	
	center = (maxX+6.0, 0.0, 0.0)
	print "DONE"
	scene = Blender.Scene.getCurrent()
	
	centerobj = uvUnfold(objs[0])
	centerobj.setLocation(*center)
	
	# duplicate objects [for when uv may go off edge, or at least to allow extension to go off edge of tex]
	for offset in [ (-1,1), (0,1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0) ]:
		nobj = Blender.Object.New('Mesh')
		nobj.shareFrom(centerobj)
		nobj.setLocation(center[0] + 4.0*offset[0], center[1] + 4.0*offset[1], center[2])
		scene.link(nobj)
	
	
	
	# create the camera
	cam = Blender.Camera.New('ortho')
	# these values are larger than necescary, apparently the 3d view and render don't line up exactly
	# the error caused by having these larger values isn't significant enough to bother
	cam.setClipStart(1.0)
	cam.setClipEnd(3.0)
	# this is actually necescary for ortho cameras
	cam.setLens(16.0)
	camObj = Blender.Object.New('Camera')
	camObj.setLocation(center[0], center[1], center[2]+2.0)
	camObj.link(cam)
	scene.link(camObj)
	
	scene.setCurrentCamera(camObj)
	
	print "took", Blender.sys.time() - startTime, "seconds"
	print "######## SUCCESS ########"
	
	if Blender.Get('curframe') == 1: Blender.Draw.PupMenu("Warning: Do not render frame number 1")
else:
	if len(objs) == 0: Blender.Draw.PupMenu("Error: Nothing Selected")
	elif objs[0].getType()!="Mesh": Blender.Draw.PupMenu("Error: Active object not a Mesh")

cool, thanks

[I really have to get to putting a website together with updated versions of this stuff]

also, thanks for pointing out [some of] the many things that need to be improved in my script

… and to explain [here] why my script seems to extrude the meshes around uv seams, have a look at this pic: [should it decide to load]

notice the blue seeping in [at uv seams…]. This is because I had antialiasing off and of how textures are interpolated. Although it is probably possible [using key instead of sky?] to get around this for this pic, scaled down versions of the texture [as used in mipmapping] would have the same problem. To compensate I extrude the edges. However, it looks like I do it a bit too far…

Yup! I think the default value for UVMULTIPLIER is too low.

By the way, the whole seams-at-the-end issue can be lessened by a great deal (if not totally solved) by using “Extend” instead of the default “Repeat” on the image texture settings.

Can I borrow these lines ?

By all means, please go ahead! :smiley:

There are some English typos, like “cammera” and maybe “flattened” (is that with double T ?) but I guess it reads clearly.