Help needed calculating vertex normals...
Blitz3D Forums/Blitz3D Programming/Help needed calculating vertex normals...
| ||
Hi Hope it's OK posting here as I'm not sure my problem can be considered 'advanced 3D' but here goes... Basically, I've written a function to do tri sub-division by placing a new vertex at the centre of each side of a tri. The problem I'm having is calculating the normals of the new vertices. The calculation I'm using at the moment to do this is as follows: norm_x_of_centre_vert# = norm_x_of_vert_0 + ((norm_x_of_vert_1-norm_x_of_vert_0)/2) norm_y_of_centre_vert# = norm_y_of_vert_0 + ((norm_y_of_vert_1-norm_y_of_vert_0)/2) norm_z_of_centre_vert# = norm_z_of_vert_0 + ((norm_z_of_vert_1-norm_z_of_vert_0)/2) I think this gives the new normals the right direction but it also 'crops' them so that they don't have a length of 1. I'm pretty new to 3D concepts so don't really understand why normals have to have a unit length. Any help sorting this problem out would be much appreciated! Thanks in advance. |
| ||
You simply need to average the normals of the 3 "old" vertices, like this:norm_x_of_centre_vert# = (norm_x_of_vert_0 + norm_x_of_vert_1 + norm_x_of_vert_2)/3.0 etc.The reason the normals need to have a length of 1, is that when DirectX calculates the lighting on a mesh, it uses the normals straight out the bag, without unifying them first. Therefore if your normals have a different length the lighting will be either dimmer (length<1.0) or more intense (length>1.0). Edit: Ohh, you're doing edge subdivisions...Then it's like this:: norm_x_of_centre_vert# = (norm_x_of_vert_0 + norm_x_of_vert_1)/2.0 etc.And make sure your values are all floats! Fredborg |
| ||
Thanks Fredborg but I think your way of doing the calculations is simply a shorthand version of the way I was doing them (so I'll be using your way from now on ;) ). So, the resulting normal will still not have a length of one. However, I've since discovered a way of unifying the normals using the calc:nl# = sqr(nx*nx + ny*ny + nz*nz) nx = nx / nl ny = ny / nl nz = nz / nl This seems to work fine in setting the new normals to a length of one, unfortunantely it doesn't seemed to have solved my problem of incorrect lighting. Please see demo: Graphics3D 800,600,16 WireFrame 0 AntiAlias 0 SeedRnd MilliSecs() Type tri_linkT Field prev.tri_linkT Field surf Field tri Field dx#,dy#,dz# Field pitch#,yaw#,roll# End Type Type explode_ctrlT Field tri_list.tri_linkT Field mesh Field life% Field fader# Field fade_start% Field exploding% End Type fps_timer = CreateTimer(60) Global cam = CreateCamera() PositionEntity cam,0,0,-7 light = CreateLight() TurnEntity light,0,75,0 ; Scratch pad mesh used by copy_tri_explode() for sub-division. Global sd_mesh = CreateMesh() Global sd_surf = CreateSurface(sd_mesh) HideEntity sd_mesh BB_prim1 = CreateCone(16) BB_prim2 = CopyEntity(BB_prim1) SD_prim1.explode_ctrlT = copy_mesh_explode(BB_prim1,200,1) SD_prim2.explode_ctrlT = copy_mesh_explode(BB_prim1,200,1) PositionEntity BB_prim1,-3,-2,0 PositionEntity BB_prim2,-3,2,0 PositionEntity SD_prim1\mesh,3,-2,0 PositionEntity SD_prim2\mesh,3,2,0 EntityFX BB_prim1,4 EntityFX SD_prim1\mesh,4 ; Main loop While Not KeyHit(1) TurnEntity BB_prim1,1,0,.5 TurnEntity BB_prim2,1,0,.5 TurnEntity SD_prim1\mesh,1,0,.5 TurnEntity SD_prim2\mesh,1,0,.5 UpdateWorld RenderWorld Line 400,0,400,600 Text 400,20," B3D Primitives Sub-divided Primitives",1 Text 400,280,"Smooth Shaded Smooth Shaded",1 Text 400,530,"Flat Shaded Flat Shaded",1 WaitTimer(fps_timer) Flip(1) Wend ClearWorld End ; ; Creates a subdivided (optional), unwelded copy of an existing mesh. ; Explode info for all tris in the new mesh are kept in a separate linked list ; contained in the control variable returned to the caller. ; Note: the source mesh being copied remains completely unaltered. ; ; Params: ; s_mesh - Source mesh to be copied. ; life - Duration of explosion animation (in frames) to use when exploded. ; divs - Number of times to perform x4 sub-division on each source tri. ; keep_surfs - True to keep the same amount of surfaces in ; the copy as in the source mesh. ; False (Default) to copy all tris in the source mesh ; to a single surface in the copy. ; ; Returns: ; Pointer to the control variable (of type explode_ctrlT) for the ; new explodable mesh created. ; Function copy_mesh_explode.explode_ctrlT(s_mesh,life%,divs=0,keep_surfs%=False) tri_list.tri_linkT = Null d_mesh = CreateMesh() For sno = 1 To CountSurfaces(s_mesh) s_surf = GetSurface(s_mesh,sno) nt = CountTriangles(s_surf) If sno = 1 Or keep_surfs Then d_surf = CreateSurface(d_mesh) For tno = 0 To nt-1 tri_list = copy_tri_explode(s_surf,tno,d_surf,divs,tri_list) Next Next ; Create and return this explodable mesh's control variable. ctrl.explode_ctrlT = New explode_ctrlT ctrl\tri_list = tri_list ctrl\mesh = d_mesh ctrl\life = life ctrl\fade_start = Ceil(Float(life)*FADE_START#) ctrl\fader = 1.0/(ctrl\fade_start+1) ctrl\exploding = False Return ctrl End Function ; ; Adds an unwelded copy of a tri from the source surface to the destination ; surface, performing any sub-division requested. ; ; Sub-division is done by calling this function recursively, splitting the ; source tri into 4 unwelded tris each time, like so: ; ; v1 ; /\ ; /____\ ; / \ / \ ; /_____\/_____\ ; v0 v2 ; ; Params: ; s_surf - Source surface containing tri to be copied. ; tri - Index of tri to be copied. ; d_surf - Destination surface to copy the source tri to. ; divs - Number of times to perform x4 sub-division on the source tri. ; tri_list - Current top item in the linked list. ; reset - True (default) to clear scratch pad mesh. ; False to not clear scratch pad mesh (used internally only!). ; ; Returns: ; The current top item in the linked list. ; Function copy_tri_explode.tri_linkT(s_surf,tri,d_surf,divs,tri_list.tri_linkT,reset%=True) If reset Then ClearSurface(sd_surf) sv0 = TriangleVertex(s_surf,tri,0) sv1 = TriangleVertex(s_surf,tri,1) sv2 = TriangleVertex(s_surf,tri,2) ; Get coords of all 3 vertices of source tri. x0# = VertexX(s_surf,sv0) y0# = VertexY(s_surf,sv0) z0# = VertexZ(s_surf,sv0) x1# = VertexX(s_surf,sv1) y1# = VertexY(s_surf,sv1) z1# = VertexZ(s_surf,sv1) x2# = VertexX(s_surf,sv2) y2# = VertexY(s_surf,sv2) z2# = VertexZ(s_surf,sv2) ; Get normals of all 3 vertices of source tri. nx0# = VertexNX(s_surf,sv0) ny0# = VertexNY(s_surf,sv0) nz0# = VertexNZ(s_surf,sv0) nx1# = VertexNX(s_surf,sv1) ny1# = VertexNY(s_surf,sv1) nz1# = VertexNZ(s_surf,sv1) nx2# = VertexNX(s_surf,sv2) ny2# = VertexNY(s_surf,sv2) nz2# = VertexNZ(s_surf,sv2) ; Get tex coords of all 3 vertices of source tri. u0# = VertexU(s_surf,sv0) v0# = VertexV(s_surf,sv0) w0# = VertexW(s_surf,sv0) u1# = VertexU(s_surf,sv1) v1# = VertexV(s_surf,sv1) w1# = VertexW(s_surf,sv1) u2# = VertexU(s_surf,sv2) v2# = VertexV(s_surf,sv2) w2# = VertexW(s_surf,sv2) ; Get colour of all 3 vertices of source tri. r0# = VertexRed(s_surf,sv0) g0# = VertexGreen(s_surf,sv0) b0# = VertexBlue(s_surf,sv0) r1# = VertexRed(s_surf,sv1) g1# = VertexGreen(s_surf,sv1) b1# = VertexBlue(s_surf,sv1) r2# = VertexRed(s_surf,sv2) g2# = VertexGreen(s_surf,sv2) b2# = VertexBlue(s_surf,sv2) If divs ; Calculate coords of the 3 discrete vertices used by sub-division. x3# = (x1+x0)/2.0 y3# = (y1+y0)/2.0 z3# = (z1+z0)/2.0 x4# = (x2+x1)/2.0 y4# = (y2+y1)/2.0 z4# = (z2+z1)/2.0 x5# = (x0+x2)/2.0 y5# = (y0+y2)/2.0 z5# = (z0+z2)/2.0 ; Calculate normals of the 3 discrete vertices used by sub-division. nx3# = (nx1+nx0)/2.0 ny3# = (ny1+ny0)/2.0 nz3# = (nz1+nz0)/2.0 nx4# = (nx2+nx1)/2.0 ny4# = (ny2+ny1)/2.0 nz4# = (nz2+nz1)/2.0 nx5# = (nx0+nx2)/2.0 ny5# = (ny0+ny2)/2.0 nz5# = (nz0+nz2)/2.0 ; Unify. nl# = Sqr(nx3*nx3 + ny3*ny3 + nz3*nz3) nx3 = nx3 / nl ny3 = ny3 / nl nz3 = nz3 / nl nl# = Sqr(nx4*nx4 + ny4*ny4 + nz4*nz4) nx4 = nx4 / nl ny4 = ny4 / nl nz4 = nz4 / nl nl# = Sqr(nx5*nx5 + ny5*ny5 + nz5*nz5) nx5 = nx5 / nl ny5 = ny5 / nl nz5 = nz5 / nl ; Calculate tex coords of the 3 discrete vertices used by sub-division. u3# = (u1+u0)/2.0 v3# = (v1+v0)/2.0 w3# = (w1+w0)/2.0 u4# = (u2+u1)/2.0 v4# = (v2+v1)/2.0 w4# = (w2+w1)/2.0 u5# = (u0+u2)/2.0 v5# = (v0+v2)/2.0 w5# = (w0+w2)/2.0 ; Calculate colour of the 3 discrete vertices used by sub-division. r3# = (r1+r0)/2.0 g3# = (g1+g0)/2.0 b3# = (b1+b0)/2.0 r4# = (r2+r1)/2.0 g4# = (g2+g1)/2.0 b4# = (b2+b1)/2.0 r5# = (r0+r2)/2.0 g5# = (g0+g2)/2.0 b5# = (b0+b2)/2.0 ; Add the 4 unwelded tris comprising the sub-division to the ; temporary, scratch pad mesh surface. tv0 = AddVertex(sd_surf,x0,y0,z0) tv3 = AddVertex(sd_surf,x3,y3,z3) tv5 = AddVertex(sd_surf,x5,y5,z5) tri0 = AddTriangle(sd_surf,tv0,tv3,tv5) VertexNormal sd_surf,tv0,nx0,ny0,nz0 VertexNormal sd_surf,tv3,nx3,ny3,nz3 VertexNormal sd_surf,tv5,nx5,ny5,nz5 VertexColor sd_surf,tv0,r0,g0,b0 VertexColor sd_surf,tv3,r3,g3,b3 VertexColor sd_surf,tv5,r5,g5,b5 VertexTexCoords sd_surf,tv0,u0,v0,w0 VertexTexCoords sd_surf,tv3,u3,v3,w3 VertexTexCoords sd_surf,tv5,u5,v5,w5 tv1 = AddVertex(sd_surf,x1,y1,z1) tv4 = AddVertex(sd_surf,x4,y4,z4) tv3b = AddVertex(sd_surf,x3,y3,z3) tri1 = AddTriangle(sd_surf,tv1,tv4,tv3b) VertexNormal sd_surf,tv1,nx1,ny1,nz1 VertexNormal sd_surf,tv4,nx4,ny4,nz4 VertexNormal sd_surf,tv3b,nx3,ny3,nz3 VertexColor sd_surf,tv1,r1,g1,b1 VertexColor sd_surf,tv4,r4,g4,b4 VertexColor sd_surf,tv3b,r3,g3,b3 VertexTexCoords sd_surf,tv1,u1,v1,w1 VertexTexCoords sd_surf,tv4,u4,v4,w4 VertexTexCoords sd_surf,tv3b,u3,v3,w3 tv2 = AddVertex(sd_surf,x2,y2,z2) tv5b = AddVertex(sd_surf,x5,y5,z5) tv4b = AddVertex(sd_surf,x4,y4,z4) tri2 = AddTriangle(sd_surf,tv2,tv5b,tv4b) VertexNormal sd_surf,tv2,nx2,ny2,nz2 VertexNormal sd_surf,tv5b,nx5,ny5,nz5 VertexNormal sd_surf,tv4b,nx4,ny4,nz4 VertexColor sd_surf,tv2,r2,g2,b2 VertexColor sd_surf,tv5b,r5,g5,b5 VertexColor sd_surf,tv4b,r4,g4,b4 VertexTexCoords sd_surf,tv2,u2,v2,w2 VertexTexCoords sd_surf,tv5b,u5,v5,w5 VertexTexCoords sd_surf,tv4b,u4,v4,w4 tv3c = AddVertex(sd_surf,x3,y3,z3) tv4c = AddVertex(sd_surf,x4,y4,z4) tv5c = AddVertex(sd_surf,x5,y5,z5) tri3 = AddTriangle(sd_surf,tv3c,tv4c,tv5c) VertexNormal sd_surf,tv3c,nx3,ny3,nz3 VertexNormal sd_surf,tv4c,nx4,ny4,nz4 VertexNormal sd_surf,tv5c,nx5,ny5,nz5 VertexColor sd_surf,tv3c,r3,g3,b3 VertexColor sd_surf,tv4c,r4,g4,b4 VertexColor sd_surf,tv5c,r5,g5,b5 VertexTexCoords sd_surf,tv3c,u3,v3,w3 VertexTexCoords sd_surf,tv4c,u4,v4,w4 VertexTexCoords sd_surf,tv5c,u5,v5,w5 divs = divs - 1 tri_list = copy_tri_explode(sd_surf,tri0,d_surf,divs,tri_list,False) tri_list = copy_tri_explode(sd_surf,tri1,d_surf,divs,tri_list,False) tri_list = copy_tri_explode(sd_surf,tri2,d_surf,divs,tri_list,False) tri_list = copy_tri_explode(sd_surf,tri3,d_surf,divs,tri_list,False) Else dv0 = AddVertex(d_surf,x0,y0,z0) dv1 = AddVertex(d_surf,x1,y1,z1) dv2 = AddVertex(d_surf,x2,y2,z2) ; Calculate and add a lone 'centre of tri' ; vertex (needed for per-tri rotations). tx# = (x2+x1)/2.0 ty# = (y2+y1)/2.0 tz# = (z2+z1)/2.0 cvx# = tx - ((tx-x0)/3.0) cvy# = ty - ((ty-y0)/3.0) cvz# = tz - ((tz-z0)/3.0) AddVertex(d_surf,cvx,cvy,cvz) real_tri = AddTriangle(d_surf,dv0,dv1,dv2) VertexNormal d_surf,dv0,nx0,ny0,nz0 VertexNormal d_surf,dv1,nx1,ny1,nz1 VertexNormal d_surf,dv2,nx2,ny2,nz2 VertexColor d_surf,dv0,r0,g0,b0 VertexColor d_surf,dv1,r1,g1,b1 VertexColor d_surf,dv2,r2,g2,b2 VertexTexCoords d_surf,dv0,u0,v0,w0 VertexTexCoords d_surf,dv1,u1,v1,w1 VertexTexCoords d_surf,dv2,u2,v2,w2 ; Add this tri to the linked list. link.tri_linkT = New tri_linkT link\prev = tri_list link\surf = d_surf link\tri = real_tri r# = Rnd(10,80) link\dx = cvx/r link\dy = cvy/r link\dz = cvz/r link\pitch = Rnd(-10,10) link\yaw = Rnd(-10,10) link\roll = Rnd(-10,10) tri_list = link EndIf Return tri_list End Function ; ; Free all mem used by an explodable mesh created with copy_mesh_explode(). ; ; Params: ; ctrl - Control variable of the explodable mesh to be freed. ; Function free_mesh_explode(ctrl.explode_ctrlT) this.tri_linkT = ctrl\tri_list While this <> Null delme.tri_linkT = this this = delme\prev Delete delme Wend FreeEntity ctrl\mesh Delete ctrl End Function Firstly, there seems to be some bizarre shading going on with the bottom of the cone. And in the flat shaded one, I don't understand why each individual tri is being lit differently, even when they're a part of the same facet. Again, any help much appreciated to stop me going insane! :) Thanks again. |
| ||
The "bizarre" shading comes from the fact that in the cone created by blitz, the cap is not made od triangles that share a center vertex, but instead is made of little "slices" (set the wireframe to true to see what I mean). This non-symetry gives artifact with lighting, even for the normal cone. Maube I'm wrong, but your subdivision has only made it more obvious I think. Then, for flat shading, the problem is "from where blitz gets his face normal ???". I mean, there's only vertex normals in the models representation, and for flat shading it's a face normal we need, which is different. I believe that blitz simply takes the first of the 3 vertex normals. This is incorrect of course if your face has different vertex normals (which happens when this face belongs to a curfed surface), because then the "face normal" is different from the vertex normals. You can see this even with the normal cone (without yousr subdivision): you see that the cone's facets are all lit exactly the same way. This is probably because the first vertex of each facet that makes the cone (I don't talk about the cap) is the top one, so the normal used to lit these faces is the same for each one. Again, your subdivision has just made the problem more obvious (making it appear not only at the orginal edges, but also at new created vertices). This function makes all 3 normals the same. Use it to have a real flat shaded mesh: Function MakeFlatShaded(mesh) ; beware: vertices should be already be unwelded surfCount = CountSurfaces(mesh) For i = 1 To surfCount surf = GetSurface(mesh, i) triCount = CountTriangles(surf) For j = 0 To triCount-1 v0 = TriangleVertex(surf, j, 0) v1 = TriangleVertex(surf, j, 1) v3 = TriangleVertex(surf, j, 2) nx# = (VertexNX(surf, v0)+VertexNX(surf, v1)+VertexNX(surf, v2))/3.0 ny# = (VertexNY(surf, v0)+VertexNY(surf, v1)+VertexNY(surf, v2))/3.0 nz# = (VertexNZ(surf, v0)+VertexNZ(surf, v1)+VertexNZ(surf, v2))/3.0 norm# = Sqr(nx*nx + ny*ny + nz*nz) nx = nx / norm ny = ny / norm nz = nz / norm VertexNormal(surf, v0, nx, ny, nz) VertexNormal(surf, v1, nx, ny, nz) VertexNormal(surf, v2, nx, ny, nz) Next Next End Function EDIT: I've seen in fact the top vertex is unwelded, so maybe the problem is each "instance" of this vertex has the same normal (like (0,0,1) for example). It also seems the original normals are not right. So in fact MakeFlatShaded doesn't work very well here. What it should really do is compute the normal from the vertices positions, by doing a cross product. Right now I'm too lazy to do it. |
| ||
Hi Koriolis, I think you must be right about b3d using the vertex normals instead of using a face normal to do flat shading. I did suspect this myself. This is a pity as I would have liked my subdivision routine to have been useable even when flat shading. I see what you're saying about b3d's cone primitive. The strange thing is that b3d's cylinder prim uses the same style end caps as the cone and they light correctly. I also discovered that if I call UpdateNormals on the cone right after creating it, it then seems to light correctly (except when flat shading, of course). Anyway, as long as I know it's nothing I'm doing wrong in the code that's causing the problems, that's something. :) Thanks for responding! |