RANDOM USER PROJECTS

## Panda3D Manual: Cg Tutorial Part 9

`#Lesson9.py """Like 0.py we restart from scratch and create a basic example with one pointlight, that only supports diffuse lighting. There is no shader attached to thisexample. If you do not understand this sample then open the Panda3D manual andtry to understand e.g. the Disco-Lights sample. When we talk about lighting we talk about an "effect" that we can see with oureyes. Live is a game, just with better graphic. Lighting is still something youmay do your own research and invent a cool new idea, no one had before. We oftenonly approximate lighting and invent new terms that do not exist in reality e.g.there is no specular light in real live. For better lighting we often need topre calculate some values in an often slow pre process. We start here only withone basic lighting model: Diffuse Lighting. The basic idea of diffuse lighting is: The steeper the angle between the lightand a surface, the less light particles can reach the surface. The followingfigures show an example with a directional light and a wall. 1. 100% of the light reaches the wall.2. ~50% of the light reaches the wall.3. 0% of the light reaches the wall.       |           /1. -> |    2. -> /     3. -> ---      |         / If no light reaches a wall, the wall cannot reflect any light particles andtherefore you cannot see anything. This idea is only one basic idea. This ideae.g. says nothing about the fact that if a wall reflects some light, it may bepossible that this light reaches another wall, which may reflect this lightparticles once more. Given that there is one wall behind another wall:    |    |-> |    |   |    | If we translate our idea to this situation, it means, that both walls got thesame amount of light, because the angle between the light surface and the lightsource is equal for both walls. This is of course dead wrong, because the firstwall occludes the second wall, so there is more or less no light at all. The default lighting model the fixed function pipeline offers to Panda3D hastons of flaws, even though it helps to increase realism. Let us stick to thismediocre lighting model for now (better lighting models are often extremely slowand only with tricks you may use them in 60 FPS applications). To calculate how much light reaches our triangle (or wall) we need a tool thathelps us to distinguish if a triangle looks toward a light source or not. Onepossibility to do this is a surface normal. In the preceding examples we assumedthat the surface normal is perpendicular to the surface. This is not alwaystrue, as we see later, therefore we like to define a normal at least for eachtriangle (or face). When you have a look at the cube.egg once more you see thatfor every polygon, a normal is specified. If Panda3D needs to triangulate themodel for the GPU it assigns every triangle, that belongs to the same polygon,the same normal. That is not the whole truth, in fact, the GPU likes to have a normal for everyvertex. Why this is a good idea is shown by another example. Open the enclosedfigures.svg or figures.png and look at figure 8-1. If we have a cube, there areat least two possibilities to assign normals. The visual difference you may seelater is that the left cube has sharp edges, while the right cube has smoothedges (on the right cube, the normals on each corner have a small gap inbetween, this gap is only here to see that every vertex has a normal). Metallike objects may have sharper edges while wooden objects may not. An artist mayinfluence how a model looks like (with lighting enabled) if he/she modifies thenormals. Back to our "whole truth" problem. As you can see it is impossible tocreate a smooth cube if every polygon or triangle only has one normal. We needat least one normal for every vertex. The cube.egg model is an example of cube with sharp edges, while thecube-smooth.egg model is an example of a cube with smooth edges. Try to see thedifference between this two files. The fixed function pipeline of a GPU (that is the pipeline Panda3D uses if thereis no call to setShader or setAutoShader) is not that sophisticated. Better saidthe GPUs were not powerful enough to calculate this very simple lighting modelper fragment/pixel, they only can calculate it per vertex. The larger yourtriangles on your screen, the falser the result. One more definition for diffuse lighting is, that it does not depend on theviewers position. That is not true for all effects e.g. the "output" of a mirrordepends on the viewers position. Specular lighting simulates a mirror likeeffect for lights and therefore depends on the viewers position. Diffuse lighting is especially suited for rough surfaces, because of ourdefinition that the surfaces should distribute light in any direction,independent of any other environmental effects. Back to our problem: We have a surface normal, we have a light position and wesay that only this two things should matter. We now can calculate a directionvector from the light to our triangle. Because it is a point light, thatdistributes light in any direction, this assumption is correct. After this firstoperation we calculate the angle between this direction and surface normal.Based on this angle we can calculate how much diffuse light we have on atriangle.         |          |1. -> <-|    2. -> |->        |          | In example 1. we have 100% diffuse light while in the example 2. there is 0%diffuse lighting. There are two possibilities to calculate this angle. We do some trigonometry, orwe use the dot product. Both ideas are equivalent, but the second is faster tocalculate. Read the following page to get in-depth information. http://en.wikipedia.org/wiki/Dot_product We will later see how to calculate exactly each of this steps. I only like tointroduce some concepts here.""" import sysimport math import direct.directbase.DirectStartfrom direct.interval.LerpInterval import LerpFuncfrom panda3d.core import PointLight base.setBackgroundColor(0.0, 0.0, 0.0)base.disableMouse() base.camLens.setNearFar(1.0, 50.0)base.camLens.setFov(45.0) camera.setPos(0.0, -20.0, 10.0)camera.lookAt(0.0, 0.0, 0.0) root = render.attachNewNode("Root") """We set up a default point light here. You may modify the color of the light, butin the next examples we assume, that the light has no attenuation and is white. There is a dummy model attached to this node, to see where the light should be.Because this light is parented to render, and we only enable light on the cubes,this model does not influence the lighting nor is it lit by the light itself."""pointlight = PointLight("Light")light = render.attachNewNode(pointlight)modelLight = loader.loadModel("misc/Pointlight.egg.pz")modelLight.reparentTo(light) """DIRTYReplace cube.egg with cube-smooth.egg and try to understand why both outputsdiffer."""modelCube = loader.loadModel("cube.egg") cubes = []for x in [-3.0, 0.0, 3.0]:    cube = modelCube.copyTo(root)    cube.setPos(x, 0.0, 0.0)    cube.setLight(light)    cubes += [ cube ] base.accept("escape", sys.exit)base.accept("o", base.oobe) """We move around our light. Because this basic application only supports pervertex lighting you often can see some odd artifacts if only one vertex of aface is lit. The bounding box around all cubes ranges from (-4.0, -1.0, -1.0) to (4.0, 1.0,1.0). Therefore we set the radius of the virtual sphere (the motion path of thelight) to something that is only a little bit larger than 4.0. This helps laterto see the visual difference from per vertex lighting to per pixel lighting."""def animate(t):    radius = 4.3    angle = math.radians(t)    x = math.cos(angle) * radius    y = math.sin(angle) * radius    z = math.sin(angle) * radius    light.setPos(x, y, z) def intervalStartPauseResume(i):    if i.isStopped():        i.start()    elif i.isPaused():        i.resume()    else:        i.pause() interval = LerpFunc(animate, 10.0, 0.0, 360.0) base.accept("i", intervalStartPauseResume, [interval]) def move(x, y, z):    root.setX(root.getX() + x)    root.setY(root.getY() + y)    root.setZ(root.getZ() + z) base.accept("d", move, [1.0, 0.0, 0.0])base.accept("a", move, [-1.0, 0.0, 0.0])base.accept("w", move, [0.0, 1.0, 0.0])base.accept("s", move, [0.0, -1.0, 0.0])base.accept("e", move, [0.0, 0.0, 1.0])base.accept("q", move, [0.0, 0.0, -1.0]) run()`