Hey,
im currently trying to get into procedural mesh generation. So Far I implemented the Panda3D viewer into Tkinter and I am generating a plane-mesh with height offset from perlin-noise. My ultimate Goal is to programm some tool to generate normal/height maps and gloss maps from Textures (with sliders and stuff). To better see specular highlights I would like to add lights to the scene, but except the ambient light nothing seems to work at all. I tried adding for example the pointlight via
plight = PointLight(‘plight’)
plight.setColor(VBase4(0.2, 0.2, 0.2, 1))
plnp = render.attachNewNode(plight)
plnp.setPos(10, 20, 0)
render.setLight(plnp)
but the mesh stays black (i also tried altering the light or even letting it rotate around in the scene, nothing worked). Am I forgetting something in my mesh (normals for example)? I assumed the normals got generated automatically since the view of the mesh seems about right.
Also I’d like to add the shader maps ofcourse (first just the ones that come with Panda3D, im not familiar with GLSL or any shader language). The Texture gets applied currectly (in this case just the x-gradient of the noise, which makes it look a bit like a “fake shadow cast”). But as soon as I change the TextureStage
to use TextureStage.MGloss
(line 111), the Texture vanishes, no error is raised and there is no visible gloss on the mesh.
here my code (its a bit messy, sorry in advance for the comments):
from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
import tkinter
import numpy as np
import random
class app(ShowBase):
def __init__(self,*args,**kwargs):
ShowBase.__init__(self,*args,**kwargs)
self.width = 1600
self.height = 900
self.resolution = 2**8
self.startTk()
self.root = self.tkRoot
self.root.update()
self.root.geometry('{0}x{1}'.format(self.width,self.height))
self.root.columnconfigure(0,weight=1)
self.root.rowconfigure(0,weight=1)
self.canvas_frame = tkinter.LabelFrame(self.root,text='Panda3d canvas')
self.canvas_frame.grid(row=0,column=0,sticky=tkinter.N+tkinter.S+tkinter.W+tkinter.E)
self.menu_frame = tkinter.LabelFrame(self.root,text='Menu')
self.menu_frame.grid(row=0,column=1,sticky='news')
self.button1 = tkinter.Button(self.menu_frame,text='button',command = self.change_mesh)
self.button1.pack()
self.canvas = tkinter.Canvas(self.canvas_frame)
self.canvas.pack(fill=tkinter.BOTH,expand=True)
self.canvas.columnconfigure(0,weight=1)
self.canvas.rowconfigure(0,weight=1)
self.canvas_id = self.canvas.winfo_id()
self.root.update()
self.props = WindowProperties()
self.props.set_parent_window(self.canvas_id)
self.props.setOrigin(0, 0)
self.props.setSize(self.width, self.height)
self.makeDefaultPipe()
self.openDefaultWindow(props=self.props)
self.format = GeomVertexFormat.getV3c4t2() # vertex + color + texcoord
self.init_mesh()
self.disableMouse()
self.camera.setHpr(0, -35, 0 )
self.camera.setPos(0,-40, 25)
self.setBackgroundColor(.2,.2,.2,)
#Add the spinCameraTask procedure to the task manager.
# self. id = self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")
self.start_mainloop()
# Define a procedure to move the camera.
def spinCameraTask(self, task):
angleDegrees = task.time * 6.0
angleRadians = angleDegrees * (np.pi / 180.0)
self.camera.setPos(50 * np.sin(angleRadians), -50.0 * np.cos(angleRadians), 25)
self.camera.setHpr(angleDegrees, -25, 0 )
# self.taskMgr.remove(self.id)
return task.cont
def init_mesh(self):
'generates a vertexprimitive from a perlin noise field and adds its node to the scene'
self.vdata = GeomVertexData('Triangle Mesh', self.format, Geom.UHStatic)
self.vertex = GeomVertexWriter(self.vdata, 'vertex')
self.color = GeomVertexWriter(self.vdata, 'color')
self.texcoord = GeomVertexWriter(self.vdata, 'texcoord')
self.heightmap,lin,n = self.sum_perlin(resolution = self.resolution,seed_nr = random.randint(0,1000))
self.gradientmap = np.gradient(self.heightmap)
self.tex = Texture()
self.tex.setup2dTexture(self.resolution,self.resolution, Texture.T_unsigned_byte, Texture.F_luminance)
self.gradientmap = self.gradientmap - np.amin(self.gradientmap)
self.buff = self.gradientmap[0]*((self.resolution-1)/np.amax(self.gradientmap[0]))
self.buff = (self.resolution-1) - self.buff
self.buff = self.buff.astype(np.uint8).tostring()
self.tex.setRamImage(self.buff)
self.vertices = []
offset = 10
for column,x in zip(self.heightmap,lin):
for value,y in zip(column,lin):
self.vertices.append([x-offset,y-offset,value])
color_offset = np.amin(self.heightmap)
scale = 1/np.amax(self.heightmap-color_offset)
# color_offset = np.amin(self.gradientmap)
# scale = 1/np.amax(self.gradientmap - color_offset)
ii = 0
kk = 0
for vertex in self.vertices:
self.vertex.addData3f(vertex[1], vertex[0], vertex[2])
self.texcoord.addData2f(ii/(self.resolution-1),kk/(self.resolution-1))
ii = ii + 1
if ii == self.resolution:
ii = 0
kk = kk + 1
self.color.addData4f(scale*(vertex[2]-color_offset),scale*(vertex[2]-color_offset),scale*(vertex[2]-color_offset),1)
# self.color.addData4f(scale*(color-color_offset),scale*(color-color_offset),scale*(color-color_offset),1)
self.prim = GeomTriangles(Geom.UHStatic)
n = len(lin)
self.prim.addVertices(0,1,n+1)
for ii in range(1,n*(n-1)):
if (ii)%n == 0:
self.prim.addVertices(ii,ii+1,ii+n)
elif (ii+1)%n == 0:
self.prim.addVertices(ii,ii+n,ii+n-1)
else:
self.prim.addVertices(ii,ii+1,ii+n)
self.prim.addVertices(ii,ii+n,ii+n-1)
self.prim.close_primitive()
self.gloss_ts = TextureStage('ts')
# self.gloss_ts.setMode(TextureStage.MGloss)
self.geom = Geom(self.vdata)
self.geom.addPrimitive(self.prim)
self.node = GeomNode('gnode')
self.node.addGeom(self.geom)
self.nodepath = render.attachNewNode(self.node)
self.nodepath.setTexture(self.gloss_ts,self.tex)
self.nodepath.reparentTo(self.render)
self.nodepath.setShaderAuto()
def change_mesh(self):
'generates new mesh with perlin noise (new seed)'
self.nodepath.removeNode()
self.init_mesh()
def sum_perlin(self,number_of_sums = 2, weights = [3,0.5],noise_sizes = [3,20], seed_nr = 2,resolution=2**8):
'sums different perlin noises to generate a more interesting mesh with more details'
if len(weights) != number_of_sums:
print('number of sums need to equal length of weights')
return
lin = np.linspace(0,noise_sizes[0],resolution,endpoint=False)
n = len(lin)
x,y = np.meshgrid(lin,lin)
sum_perlin = weights[0] * self.perlin(x,y,seed=seed_nr)
ii = 1
while ii < number_of_sums:
lin = np.linspace(0,noise_sizes[ii],resolution,endpoint=False)
x,y = np.meshgrid(lin,lin)
sum_perlin = sum_perlin + (weights[ii] * self.perlin(x,y,seed=seed_nr))
ii = ii + 1
return sum_perlin, lin, n
def start_mainloop(self):
'starts mainloop and hands it over to TK backend'
self.spawnTkLoop()
self.tkRun()
def perlin(self,x,y,seed=0):
'generates perlin noise'
# permutation table
np.random.seed(seed)
p = np.arange(self.resolution,dtype=int)
np.random.shuffle(p)
p = np.stack([p,p]).flatten()
# coordinates of the top-left
xi = x.astype(int)
yi = y.astype(int)
# internal coordinates
xf = x - xi
yf = y - yi
# fade factors
u = self.fade(xf)
v = self.fade(yf)
# noise components
n00 = self.gradient(p[p[xi]+yi],xf,yf)
n01 = self.gradient(p[p[xi]+yi+1],xf,yf-1)
n11 = self.gradient(p[p[xi+1]+yi+1],xf-1,yf-1)
n10 = self.gradient(p[p[xi+1]+yi],xf-1,yf)
# combine noises
x1 = self.lerp(n00,n10,u)
x2 = self.lerp(n01,n11,u) # FIX1: I was using n10 instead of n01
return self.lerp(x1,x2,v) # FIX2: I also had to reverse x1 and x2 here
@staticmethod
def lerp(a,b,x):
"linear interpolation"
return a + x * (b-a)
@staticmethod
def fade(t):
"6t^5 - 15t^4 + 10t^3"
return 6 * t**5 - 15 * t**4 + 10 * t**3
@staticmethod
def gradient(h,x,y):
"grad converts h to the right gradient vector and return the dot product with (x,y)"
vectors = np.array([[0,1],[0,-1],[1,0],[-1,0]])
g = vectors[h%4]
return g[:,:,0] * x + g[:,:,1] * y
if __name__ == "__main__":
application = app(windowType='none')