Looks good now.
One thing with the normals is that once you have added all the face normals together you need to re-normalise the result, instead of dividing by the number of faces added.
A small optimisation too. You do something like
dx = x2-x1
dr = (r2-r1) / dx
dg = (g2-g1) / dx
drb= (b2-b1) / dx
It would be quicker to do
dx = 1.0/(x2-x1)
dr = (r2-r1) * dx
dg = (g2-g1) * dx
drb= (b2-b1) * dx
But while you're learning we might be better off not talking about optimisation just now and more about algorithms.
Jim
oh ok :) In fact, the part your talking about (when dividing NbFaces to TmpNorm), is not for faces normals but for Vertex normals (for shading).
Anyway, replacing the division by a normalization looks much better, the light's much better contrasted ! Thanks :)
Some news :
While I was working on Texture Mapping (I'm still on it, there's a problem with the UV projection, but the "pasting" part works for now :) ), I noticed there is a big problem with the way I globally handle my coordinates. I'm so lost I don't know in which sense are organized my axis anymore (especially the Z one ..)
To be continued .. :P
Well erm.. So after a big verification, my Z-axis IS inversed. In fact, he's in the other direction I thought :p And Vertices must be declared Counter-clockwise ! uhuh So now It's way easier to understand and visualize where my vertices are.
But !
I suppressed my cube and started a new one to make sure there are no errors. Well the cube looks better but there is still a bug : On some faces (the majority in fact), we can see the diagonal line when 2 triangles meet on a face : There should be a gradient but the demarcation is pretty visible (see screenshot) ???
I guess there is a problem in the Vertex Normal Calc but now i need to find it :p If you have any idea of what can cause that ...
edit : Well I found the origin of the problem : If I calc my normals on each loop instead of rotating the precalculated ones, It works perfectly ! So the problem comes from the way I rotate my normals !
Re-edit : O_o in fact, I just have to run the precalc_normals sub twice, and then the rotation is ok.
That's really weird :p But for now it works :D
Re-Re-edit :
aaand, I fouuund it !! Rah what a dumb I am :whack: A simple inattention bug :p Ok so now it works, without any trick behind ^^
Next step : Gouraud shading on texture mapped polys
Update !
Now Texture mapped objects support Gouraud shading :updance:
Code (Needs the texture in the attached Rar file) :
'' =======================---. 3D Stuff .---=======================
''
'' Gouraud Shading + Z-Buffer rendering
''
'' Code : Hezad [ Hezad0 )at( Gmail.com ]
''
''
'' Thanks to Relsoft for his awesome tutorial :
'' [ http://www.phatcode.net/articles.php?id=214 ]
''
''
'' ================================================================
#Define Norm(a) sqr(a.x*a.x + a.y*a.y + a.z*a.z)
Const MAX_TRIANGLES_PER_OBJECT = 500
Const MAX_OBJECTS = 10
Const xRes = 640, yRes = 480
Const SCREEN_MEM = xRes * yRes
Const BGND_COLOR = rgb(50,50,70)
Const FOV = 512, ZOOM = 512
Const MAX_Z = 1000
Const PI = 3.1415926535897932384626
Const PIx2 = 2*PI
Const ToRad = PIx2/360
Const ToDeg = 360/PIx2
Dim shared as integer ptr ScrPtr
ReDim shared as integer ZBuffer(SCREEN_MEM)
Screenres xRes,yRes,32,2
ScrPtr = Screenptr
Dim shared as single FPS_t,CurFPS,FPS_counter
Type Vector
as single x,y,z
End Type
Type ColorT
as integer r,g,b
End Type
Type TextureT
as vector Size
as integer ptr GFX
End Type
Sub Set_Texture_Size(Text as TextureT, x as integer, y as integer)
If Text.GFX <> 0 then deallocate Text.GFX
Text.Size.x = x
Text.Size.y = y-1
Text.GFX = Callocate(x*y,sizeof(integer))
End Sub
Type VertexT
as Vector Pos3d
as Vector Gouraud_Normal
as Vector Pos2d
as ColorT Color
as ColorT OriginalColor
as single U,V
End Type
Type TriangleT
as Vector Normal
as integer idZ
as VertexT Vertex(2)
as Vector Parent_Center
End Type
Type ObjectT
as TriangleT Triangle(MAX_TRIANGLES_PER_OBJECT)
as Vector Center
as integer nb_triangles
End Type
Type LightT
Pos3d as Vector
Intensity as single
End Type
Type CameraT
Pos as Vector
Angle as Vector
End Type
Dim shared as CameraT Camera
Sub Set_Camera(x as integer, y as integer, z as integer)
Camera.Pos.x = x
Camera.Pos.y = y
Camera.Pos.z = z
End Sub
Sub Sort_Object_Z(obj as ObjectT)
For i as integer = 1 to obj.nb_Triangles
obj.triangle(i).IdZ = i
if i>1 then
if obj.triangle(i).vertex(0).pos3d.z + obj.triangle(i).vertex(1).pos3d.z + obj.triangle(i).vertex(2).pos3d.z < obj.triangle(i-1).vertex(0).pos3d.z + obj.triangle(i-1).vertex(1).pos3d.z + obj.triangle(i-1).vertex(2).pos3d.z then
Swap obj.triangle(i), obj.triangle(i-1)
i=1
end if
end if
Next
End Sub
Sub Calc_Triangle_Normal(Tri as TriangleT)
dim as single ax,bx,ay,by,az,bz,NNorm
ax = Tri.Vertex(1).pos3d.x - Tri.Vertex(0).pos3d.x
bx = Tri.Vertex(2).pos3d.x - Tri.Vertex(1).pos3d.x
ay = Tri.Vertex(1).pos3d.y - Tri.Vertex(0).pos3d.y
by = Tri.Vertex(2).pos3d.y - Tri.Vertex(1).pos3d.y
az = Tri.Vertex(1).pos3d.z - Tri.Vertex(0).pos3d.z
bz = Tri.Vertex(2).pos3d.z - Tri.Vertex(1).pos3d.z
Tri.Normal.x = ay * bz - az * by
Tri.Normal.y = az * bx - ax * bz
Tri.Normal.z = ax * by - ay * bx
'' Normalize
NNorm = 1/Norm(Tri.Normal)
Tri.Normal.x *= NNorm
Tri.Normal.y *= NNorm
Tri.Normal.z *= NNorm
End Sub
#Define SamePnt(a,b) iif ((a.pos3d.x = b.pos3d.x) and (a.pos3d.y = b.pos3d.y) and (a.pos3d.z = b.pos3d.z),1,0)
Sub PreCalc_Object_Normals(obj as ObjectT)
dim as single tmpXNorm,TmpYNorm,TmpZNorm
For k as integer = 1 to obj.nb_triangles
'' face normal
Calc_Triangle_Normal(obj.Triangle(k))
Next
for k as integer = 1 to obj.nb_triangles
'' "Vertex normals"
for i as integer = 0 to 2
TmpXNorm = 0
TmpYNorm = 0
TmpZNorm = 0
for j as integer = 1 to obj.nb_triangles
if SamePnt(obj.triangle(k).Vertex(i),obj.triangle(j).Vertex(0)) or _
SamePnt(obj.triangle(k).Vertex(i),obj.triangle(j).Vertex(1)) or _
SamePnt(obj.triangle(k).Vertex(i),obj.triangle(j).Vertex(2)) then
TmpXNorm += obj.triangle(j).Normal.x
TmpYNorm += obj.triangle(j).Normal.y
TmpZNorm += obj.triangle(j).Normal.z
end if
next
dim as single _Normm = 1/sqr(TmpXNorm*TmpXNorm+TmpYNorm*TmpYNorm+TmpZNorm*TmpZNorm)
TmpXNorm *= _Normm
TmpYNorm *= _Normm
TmpZNorm *= _Normm
obj.triangle(k).Vertex(i).Gouraud_Normal.x = TmpXNorm
obj.triangle(k).Vertex(i).Gouraud_Normal.y = TmpyNorm
obj.triangle(k).Vertex(i).Gouraud_Normal.z = TmpzNorm
next
next
End Sub
Function SetLight(x as single,y as single, z as single,intens as single) as LightT
Dim as LightT Tmp
Tmp.pos3d.x = x
Tmp.pos3d.y = y
Tmp.pos3d.z = z
Tmp.Intensity = intens
Return Tmp
End Function
Declare Sub Rotate(obj as objectT,Tx as single, Ty as single, Tz as single)
'' Projection ============================
Sub Project_Object(Obj as ObjectT)
dim as single aX,aY,aZ
if obj.nb_triangles = 0 then exit Sub
For i as integer = 1 to obj.nb_triangles
obj.Triangle(i).Parent_Center = obj.Center
For j as integer = 0 to 2
if obj.Triangle(i).Vertex(j).pos3d.z = Camera.pos.z - obj.Center.z + FOV then obj.Triangle(i).Vertex(j).pos3d.z+=.0001
obj.Triangle(i).Vertex(j).pos2d.x = xRes/2 + (FOV * (-Camera.pos.x + obj.Center.x + obj.Triangle(i).Vertex(j).pos3d.x)) / (ZOOM - (Camera.pos.z + obj.Center.z + obj.Triangle(i).Vertex(j).pos3d.z))
obj.Triangle(i).Vertex(j).pos2d.y = yRes/2 - (FOV * (-Camera.pos.y + obj.Center.y + obj.Triangle(i).Vertex(j).pos3d.y)) / (ZOOM - (Camera.pos.z + obj.Center.z + obj.Triangle(i).Vertex(j).pos3d.z))
Next
Next
End Sub
'' ========================================
Sub GLine(ByVal x1 as integer,ByVal x2 as integer, y as integer,ByVal r1 as integer,ByVal g1 as integer,ByVal b1 as integer,ByVal r2 as integer,ByVal g2 as integer,ByVal b2 as integer,ByVal z1 as integer,ByVal z2 as integer)
dim as single dR,dG,dB, dZ
dim as single R,g,b
dim as integer itx1,itx2,zz1,zz2
Dim as single CurZ,xDiv
dim as single sr1,sr2,sg1,sg2,sb1,sb2
if x1<x2 then
zz1 = z1
zz2 = z2
itx1 = x1
itx2 = x2
sr1 = r1
sr2 = r2
sg1 = g1
sg2 = g2
sb1 = b1
sb2 = b2
else
zz1 = z2
zz2 = z1
itx1 = x2
itx2 = x1
sr1 = r2
sr2 = r1
sg1 = g2
sg2 = g1
sb1 = b2
sb2 = b1
end if
xDiv = 1/(itx2 - itx1)
dR = xDiv * (sr2 - sr1)
dG = xDiv * (sg2 - sg1)
dB = xDiv * (sb2 - sb1)
dZ = xDiv * (zz2 - zz1)
r = sr1
g = sg1
b = sb1
CurZ = zz1
dim as integer it
for i as integer = itx1 to itx2-1
if y>0 and y<yRes and i>0 and i<xRes then
it = y*xRes + i
If (FOV - (-Camera.pos.z + CurZ))>0 then
If Curz > ZBuffer(it) then
ZBuffer(it) = CurZ
ScrPtr[it] = rgb(r,g,b)
'' UnComment to show the ZBuffer Gfx :D
'dim as integer zBufferCol = 255*cast(single,(abs(ZBuffer(it))/MAX_Z))
'ScrPtr[it] = rgb(zBufferCol,zBufferCol,zBufferCol)
End if
end if
end if
curZ += dZ
r += dR
g += dG
b += dB
Next
End Sub
Sub Gouraud_Fill(ByVal Triangle as TriangleT)
'' Sorting Y values
if Triangle.Vertex(1).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(1),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(1).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(1)
end if
dim as integer dx1,dy1,dx2,dy2,dx3,dy3
dim as integer dr1,dg1,db1,dr2,dg2,db2,dr3,dg3,db3
dim as single Slope1,Slope2,Slope3, dYDiv
dim as single RSlope1, GSlope1,BSlope1
dim as single RSlope2, GSlope2,BSlope2
dim as single RSlope3, GSlope3,BSlope3
Dim as integer dz1,dz2,dz3
Dim as single zSlope1,zSlope2,zSlope3
'' interpolate 0 to 1 (pos + color)
dZ1 = Triangle.Vertex(1).pos3d.z - Triangle.Vertex(0).pos3d.z
dX1 = Triangle.Vertex(1).pos2d.x - Triangle.Vertex(0).pos2d.x
dY1 = Triangle.Vertex(1).pos2d.y - Triangle.Vertex(0).pos2d.y
dr1 = Triangle.Vertex(1).color.r - Triangle.Vertex(0).color.r
dg1 = Triangle.Vertex(1).color.g - Triangle.Vertex(0).color.g
db1 = Triangle.Vertex(1).color.b - Triangle.Vertex(0).color.b
if dY1 <> 0 then
dYDiv = 1/dY1
zSlope1 = dZ1 * dYDiv
Slope1 = dX1 * dYDiv
RSlope1 = dR1 * dYDiv
GSlope1 = dG1 * dYDiv
BSlope1 = dB1 * dYDiv
else
zSlope1 = 0
Slope1 = 0
RSlope1 = 0
GSlope1 = 0
BSlope1 = 0
End if
'' interpolate 1 to 2
dZ2 = Triangle.Vertex(2).pos3d.z - Triangle.Vertex(1).pos3d.z
dX2 = Triangle.Vertex(2).pos2d.x - Triangle.Vertex(1).pos2d.x
dY2 = Triangle.Vertex(2).pos2d.y - Triangle.Vertex(1).pos2d.y
dr2 = Triangle.Vertex(2).color.r - Triangle.Vertex(1).color.r
dg2 = Triangle.Vertex(2).color.g - Triangle.Vertex(1).color.g
db2 = Triangle.Vertex(2).color.b - Triangle.Vertex(1).color.b
if dY2 <> 0 then
dYDiv = 1/dY2
zSlope2 = dZ2 * dYDiv
Slope2 = dX2 * dYDiv
RSlope2 = dR2 * dYDiv
GSlope2 = dG2 * dYDiv
BSlope2 = dB2 * dYDiv
else
zSlope2 = 0
Slope2 = 0
RSlope2 = 0
GSlope2 = 0
BSlope2 = 0
End if
'' interpolate 0 to 2
dZ3 = Triangle.Vertex(0).pos3d.z - Triangle.Vertex(2).pos3d.z
dX3 = Triangle.Vertex(0).pos2d.x - Triangle.Vertex(2).pos2d.x
dY3 = Triangle.Vertex(0).pos2d.y - Triangle.Vertex(2).pos2d.y
dr3 = Triangle.Vertex(0).color.r - Triangle.Vertex(2).color.r
dg3 = Triangle.Vertex(0).color.g - Triangle.Vertex(2).color.g
db3 = Triangle.Vertex(0).color.b - Triangle.Vertex(2).color.b
if dY3 <> 0 then
dYDiv = 1/dY3
zSlope3 = dZ3 * dYDiv
Slope3 = dX3 * dYDiv
RSlope3 = dR3 * dYDiv
GSlope3 = dG3 * dYDiv
BSlope3 = dB3 * dYDiv
else
zSlope3 = 0
Slope3 = 0
RSlope3 = 0
GSlope3 = 0
BSlope3 = 0
End if
'' Drawing
'' Top
dim as single CurX1=Triangle.Vertex(0).pos2d.x, CurX2=Triangle.Vertex(0).pos2d.x
dim as single CurR1=Triangle.Vertex(0).Color.r,_
CurG1=Triangle.Vertex(0).Color.g,_
CurB1=Triangle.Vertex(0).Color.b,_
CurR2=Triangle.Vertex(0).Color.r,_
CurG2=Triangle.Vertex(0).Color.g,_
CurB2=Triangle.Vertex(0).Color.b
dim as single CurZ1=Triangle.Vertex(0).pos3d.z, CurZ2=Triangle.Vertex(0).pos3d.z
CurZ1 += Triangle.Parent_Center.z
CurZ2 += Triangle.Parent_Center.z
'BufferIter = Triangle.Vertex(0).pos2d.y * xRes
For y as integer = Triangle.Vertex(0).pos2d.y to Triangle.Vertex(1).pos2d.y-1
GLine(CurX1,CurX2,y,CurR1,CurG1,CurB1,CurR2,CurG2,CurB2, CurZ1, CurZ2)
CurZ1 += zSlope1
CurZ2 += zSlope3
CurX1 += Slope1
CurX2 += Slope3
CurR1 += RSlope1
CurR2 += RSlope3
CurG1 += GSlope1
CurG2 += GSlope3
CurB1 += BSlope1
CurB2 += BSlope3
'BufferIter += xRes
Next
'' down
CurZ1 = Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z
CurX1 = Triangle.Vertex(1).pos2d.x
CurR1 = Triangle.Vertex(1).color.r
Curg1 = Triangle.Vertex(1).color.g
Curb1 = Triangle.Vertex(1).color.b
For y as integer = Triangle.Vertex(1).pos2d.y to Triangle.Vertex(2).pos2d.y-1
GLine(CurX1,CurX2,y,CurR1,CurG1,CurB1,CurR2,CurG2,CurB2, CurZ1, CurZ2)
CurZ1 += zSlope2
CurZ2 += zSlope3
CurX1 += Slope2
CurX2 += Slope3
CurR1 += RSlope2
CurR2 += RSlope3
CurG1 += GSlope2
CurG2 += GSlope3
CurB1 += BSlope2
CurB2 += BSlope3
Next
End Sub
Sub Calc_Gouraud_Shader(obj_ as objectT,L_ as LightT)
Dim as single Dot1,Dot2,Dot3,NL
Dim as objectT obj = obj_
Dim as LightT L = L_
'' normalizing Light vector
NL = 1/Norm(L.pos3d)
L.pos3d.x *= NL
L.pos3d.y *= NL
L.pos3d.z *= NL
'' dot products
For k as integer = 1 to obj.nb_triangles
Dot1 = (obj.triangle(k).Vertex(0).Gouraud_Normal.x * L.pos3d.x) + _
(obj.triangle(k).Vertex(0).Gouraud_Normal.y * L.pos3d.y) + _
(obj.triangle(k).Vertex(0).Gouraud_Normal.z * L.pos3d.z)
if Dot1<0 then Dot1 = 0
if Dot1>1 then Dot1 = 1
Dot2 = (obj.triangle(k).Vertex(1).Gouraud_Normal.x * L.pos3d.x) + _
(obj.triangle(k).Vertex(1).Gouraud_Normal.y * L.pos3d.y) + _
(obj.triangle(k).Vertex(1).Gouraud_Normal.z * L.pos3d.z)
if Dot2<0 then Dot2 = 0
if Dot2>1 then Dot2 = 1
Dot3 = (obj.triangle(k).Vertex(2).Gouraud_Normal.x * L.pos3d.x) + _
(obj.triangle(k).Vertex(2).Gouraud_Normal.y * L.pos3d.y) + _
(obj.triangle(k).Vertex(2).Gouraud_Normal.z * L.pos3d.z)
if Dot3<0 then Dot3 = 0
if Dot3>1 then Dot3 = 1
'' apply new color
obj_.triangle(k).Vertex(0).Color.r = obj_.triangle(k).Vertex(0).OriginalColor.r*L.Intensity*Dot1
obj_.triangle(k).Vertex(0).Color.g = obj_.triangle(k).Vertex(0).OriginalColor.g*L.Intensity*Dot1
obj_.triangle(k).Vertex(0).Color.b = obj_.triangle(k).Vertex(0).OriginalColor.b*L.Intensity*Dot1
obj_.triangle(k).Vertex(1).Color.r = obj_.triangle(k).Vertex(1).OriginalColor.r*L.Intensity*Dot2
obj_.triangle(k).Vertex(1).Color.g = obj_.triangle(k).Vertex(1).OriginalColor.g*L.Intensity*Dot2
obj_.triangle(k).Vertex(1).Color.b = obj_.triangle(k).Vertex(1).OriginalColor.b*L.Intensity*Dot2
obj_.triangle(k).Vertex(2).Color.r = obj_.triangle(k).Vertex(2).OriginalColor.r*L.Intensity*Dot3
obj_.triangle(k).Vertex(2).Color.g = obj_.triangle(k).Vertex(2).OriginalColor.g*L.Intensity*Dot3
obj_.triangle(k).Vertex(2).Color.b = obj_.triangle(k).Vertex(2).OriginalColor.b*L.Intensity*Dot3
next
End sub
'' texturing
Sub TLine(ByVal x1 as integer,ByVal x2 as integer, y as integer,ByVal U1 as single,ByVal V1 as single,ByVal U2 as single,ByVal V2 as single,ByVal z1 as integer,ByVal z2 as integer,byval Texture as TextureT)
dim as single dU,dV,dZ,U,V
dim as single Uu1,Vv1,Uu2,Vv2
dim as integer itx1,itx2,zz1,zz2
Dim as single CurZ,xDiv
if x1<x2 then
zz1 = z1
zz2 = z2
itx1 = x1
itx2 = x2
Uu1 = u1
Uu2 = u2
Vv1 = v1
Vv2 = v2
else
zz1 = z2
zz2 = z1
itx1 = x2
itx2 = x1
Uu1 = u2
Uu2 = u1
Vv1 = v2
Vv2 = v1
end if
Uu1 *= Texture.Size.x
Uu2 *= Texture.Size.x
Vv1 *= Texture.Size.y
Vv2 *= Texture.Size.y
xDiv = 1/(itx2 - itx1)
dZ = xDiv * (zz2 - zz1)
dU = xDiv * (Uu2 - Uu1)
dV = xDiv * (Vv2 - Vv1)
CurZ = zz1
U = Uu1
V = Vv1
dim as integer it
for i as integer = itx1 to itx2-1
if y>0 and y<yRes and i>0 and i<xRes then
it = y*xRes + i
If (FOV - (-Camera.pos.z + CurZ))>0 then
If Curz > ZBuffer(it) then
ZBuffer(it) = CurZ
ScrPtr[it] = Texture.GFX[int(U)+Texture.Size.x*int(V)]
End if
end if
end if
U += dU
V += dV
curZ += dZ
Next
End Sub
Sub Texture_Map(ByVal Triangle as TriangleT, _
ByVal Texture as TextureT) '' UV's saved in the Triangle.Vertex Type
'' Sorting Y values
if Triangle.Vertex(1).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(1),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(1).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(1)
end if
dim as integer dx1,dy1,dx2,dy2,dx3,dy3
dim as single Slope1,Slope2,Slope3, dYDiv
dim as single dU1,dU2,dU3,dV1,dV2,dV3
dim as single USlope1,USlope2,USlope3
dim as single VSlope1,VSlope2,VSlope3
Dim as integer dz1,dz2,dz3
Dim as single zSlope1,zSlope2,zSlope3
'' interpolate 0 to 1 (pos + color)
dZ1 = Triangle.Vertex(1).pos3d.z - Triangle.Vertex(0).pos3d.z
dX1 = Triangle.Vertex(1).pos2d.x - Triangle.Vertex(0).pos2d.x
dY1 = Triangle.Vertex(1).pos2d.y - Triangle.Vertex(0).pos2d.y
dU1 = Triangle.Vertex(1).U - Triangle.Vertex(0).U
dV1 = Triangle.Vertex(1).V - Triangle.Vertex(0).V
if dY1 <> 0 then
dYDiv = 1/dY1
zSlope1 = dZ1 * dYDiv
Slope1 = dX1 * dYDiv
USlope1 = dU1 * dYDiv
VSlope1 = dV1 * dYDiv
else
zSlope1 = 0
Slope1 = 0
USlope1 = 0
VSlope1 = 0
End if
'' interpolate 1 to 2
dZ2 = Triangle.Vertex(2).pos3d.z - Triangle.Vertex(1).pos3d.z
dX2 = Triangle.Vertex(2).pos2d.x - Triangle.Vertex(1).pos2d.x
dY2 = Triangle.Vertex(2).pos2d.y - Triangle.Vertex(1).pos2d.y
dU2 = Triangle.Vertex(2).U - Triangle.Vertex(1).U
dV2 = Triangle.Vertex(2).V - Triangle.Vertex(1).V
if dY2 <> 0 then
dYDiv = 1/dY2
zSlope2 = dZ2 * dYDiv
Slope2 = dX2 * dYDiv
USlope2 = dU2 * dYDiv
VSlope2 = dV2 * dYDiv
else
zSlope2 = 0
Slope2 = 0
USlope2 = 0
VSlope2 = 0
End if
'' interpolate 0 to 2
dZ3 = Triangle.Vertex(0).pos3d.z - Triangle.Vertex(2).pos3d.z
dX3 = Triangle.Vertex(0).pos2d.x - Triangle.Vertex(2).pos2d.x
dY3 = Triangle.Vertex(0).pos2d.y - Triangle.Vertex(2).pos2d.y
dU3 = Triangle.Vertex(0).U - Triangle.Vertex(2).U
dV3 = Triangle.Vertex(0).V - Triangle.Vertex(2).V
if dY3 <> 0 then
dYDiv = 1/dY3
zSlope3 = dZ3 * dYDiv
Slope3 = dX3 * dYDiv
USlope3 = dU3 * dYDiv
VSlope3 = dV3 * dYDiv
else
zSlope3 = 0
Slope3 = 0
USlope3 = 0
VSlope3 = 0
End if
'' Drawing
'' Top
dim as single CurX1=Triangle.Vertex(0).pos2d.x, CurX2=Triangle.Vertex(0).pos2d.x
dim as single CurU1=Triangle.Vertex(0).U, CurU2=Triangle.Vertex(0).U,_
CurV1=Triangle.Vertex(0).V, CurV2=Triangle.Vertex(0).V
dim as single CurZ1=Triangle.Vertex(0).pos3d.z, CurZ2=Triangle.Vertex(0).pos3d.z
CurZ1 += Triangle.Parent_Center.z
CurZ2 += Triangle.Parent_Center.z
'BufferIter = Triangle.Vertex(0).pos2d.y * xRes
For y as integer = Triangle.Vertex(0).pos2d.y to Triangle.Vertex(1).pos2d.y-1
TLine(CurX1,CurX2,y,CurU1,CurV1,CurU2,CurV2, CurZ1, CurZ2, Texture)
CurZ1 += zSlope1
CurZ2 += zSlope3
CurU1 += USlope1
CurU2 += USlope3
CurV1 += VSlope1
CurV2 += VSlope3
CurX1 += Slope1
CurX2 += Slope3
'BufferIter += xRes
Next
'' down
CurZ1 = Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z
CurU1 = Triangle.Vertex(1).U
CurV1 = Triangle.Vertex(1).V
CurX1 = Triangle.Vertex(1).pos2d.x
For y as integer = Triangle.Vertex(1).pos2d.y to Triangle.Vertex(2).pos2d.y-1
TLine(CurX1,CurX2,y,CurU1,CurV1,CurU2,CurV2, CurZ1, CurZ2, Texture)
CurZ1 += zSlope2
CurZ2 += zSlope3
CurX1 += Slope2
CurX2 += Slope3
CurU1 += USlope2
CurU2 += USlope3
CurV1 += VSlope2
CurV2 += VSlope3
Next
End Sub
'' Texture Mapping + Gouraud shader
'' texturing
Sub TSLine(ByVal x1 as integer,ByVal x2 as integer, y as integer,ByVal U1 as single,ByVal V1 as single,ByVal U2 as single,ByVal V2 as single,ByVal z1 as integer,ByVal z2 as integer,byval Texture as TextureT, r1 as integer,r2 as integer,g1 as integer,g2 as integer,b1 as integer,b2 as integer)
dim as single dR,dG,dB
dim as single R,g,b
dim as single sr1,sr2,sg1,sg2,sb1,sb2
dim as single dU,dV,dZ,U,V
dim as single Uu1,Vv1,Uu2,Vv2
dim as integer itx1,itx2,zz1,zz2
Dim as single CurZ,xDiv
if x1<x2 then
zz1 = z1
zz2 = z2
itx1 = x1
itx2 = x2
Uu1 = u1
Uu2 = u2
Vv1 = v1
Vv2 = v2
sr1 = r1
sr2 = r2
sg1 = g1
sg2 = g2
sb1 = b1
sb2 = b2
else
zz1 = z2
zz2 = z1
itx1 = x2
itx2 = x1
Uu1 = u2
Uu2 = u1
Vv1 = v2
Vv2 = v1
sr1 = r2
sr2 = r1
sg1 = g2
sg2 = g1
sb1 = b2
sb2 = b1
end if
Uu1 *= Texture.Size.x
Uu2 *= Texture.Size.x
Vv1 *= Texture.Size.y
Vv2 *= Texture.Size.y
xDiv = 1/(itx2 - itx1)
dR = xDiv * (sr2 - sr1)
dG = xDiv * (sg2 - sg1)
dB = xDiv * (sb2 - sb1)
dZ = xDiv * (zz2 - zz1)
dU = xDiv * (Uu2 - Uu1)
dV = xDiv * (Vv2 - Vv1)
CurZ = zz1
U = Uu1
V = Vv1
r = sr1
g = sg1
b = sb1
dim as integer it
Dim as integer CurTex,Cr,Cg,Cb
for i as integer = itx1 to itx2-1
if y>0 and y<yRes and i>0 and i<xRes then
it = y*xRes + i
If (FOV - (-Camera.pos.z + CurZ))>0 then
If Curz > ZBuffer(it) then
ZBuffer(it) = CurZ
CurTex = Texture.GFX[int(U)+Texture.Size.x*int(V)]
dim as single _255 = 1/255
Cb = CurTex and 255
Cg = (CurTex shr 8) and 255
Cr = (CurTex shr 16) and 255
ScrPtr[it] = rgb(r*Cr*_255,g*Cg*_255,b*Cb*_255)
End if
end if
end if
U += dU
V += dV
r += dR
g += dG
b += dB
curZ += dZ
Next
End Sub
Sub Texture_Map_Shaded(ByVal Triangle as TriangleT, _
ByVal Texture as TextureT) '' UV's saved in the Triangle.Vertex Type
'' Sorting Y values
if Triangle.Vertex(1).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(1),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(1).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(1)
end if
dim as integer dx1,dy1,dx2,dy2,dx3,dy3
dim as single Slope1,Slope2,Slope3, dYDiv
dim as single dU1,dU2,dU3,dV1,dV2,dV3
dim as single USlope1,USlope2,USlope3
dim as single VSlope1,VSlope2,VSlope3
Dim as integer dz1,dz2,dz3
Dim as single zSlope1,zSlope2,zSlope3
dim as integer dr1,dg1,db1,dr2,dg2,db2,dr3,dg3,db3
dim as single RSlope1, GSlope1,BSlope1
dim as single RSlope2, GSlope2,BSlope2
dim as single RSlope3, GSlope3,BSlope3
'' interpolate 0 to 1 (pos + color)
dZ1 = Triangle.Vertex(1).pos3d.z - Triangle.Vertex(0).pos3d.z
dX1 = Triangle.Vertex(1).pos2d.x - Triangle.Vertex(0).pos2d.x
dY1 = Triangle.Vertex(1).pos2d.y - Triangle.Vertex(0).pos2d.y
dU1 = Triangle.Vertex(1).U - Triangle.Vertex(0).U
dV1 = Triangle.Vertex(1).V - Triangle.Vertex(0).V
dr1 = Triangle.Vertex(1).color.r - Triangle.Vertex(0).color.r
dg1 = Triangle.Vertex(1).color.g - Triangle.Vertex(0).color.g
db1 = Triangle.Vertex(1).color.b - Triangle.Vertex(0).color.b
if dY1 <> 0 then
dYDiv = 1/dY1
zSlope1 = dZ1 * dYDiv
Slope1 = dX1 * dYDiv
USlope1 = dU1 * dYDiv
VSlope1 = dV1 * dYDiv
RSlope1 = dR1 * dYDiv
GSlope1 = dG1 * dYDiv
BSlope1 = dB1 * dYDiv
else
zSlope1 = 0
Slope1 = 0
USlope1 = 0
VSlope1 = 0
RSlope1 = 0
GSlope1 = 0
BSlope1 = 0
End if
'' interpolate 1 to 2
dZ2 = Triangle.Vertex(2).pos3d.z - Triangle.Vertex(1).pos3d.z
dX2 = Triangle.Vertex(2).pos2d.x - Triangle.Vertex(1).pos2d.x
dY2 = Triangle.Vertex(2).pos2d.y - Triangle.Vertex(1).pos2d.y
dU2 = Triangle.Vertex(2).U - Triangle.Vertex(1).U
dV2 = Triangle.Vertex(2).V - Triangle.Vertex(1).V
dr2 = Triangle.Vertex(2).color.r - Triangle.Vertex(1).color.r
dg2 = Triangle.Vertex(2).color.g - Triangle.Vertex(1).color.g
db2 = Triangle.Vertex(2).color.b - Triangle.Vertex(1).color.b
if dY2 <> 0 then
dYDiv = 1/dY2
zSlope2 = dZ2 * dYDiv
Slope2 = dX2 * dYDiv
USlope2 = dU2 * dYDiv
VSlope2 = dV2 * dYDiv
RSlope2 = dR2 * dYDiv
GSlope2 = dG2 * dYDiv
BSlope2 = dB2 * dYDiv
else
zSlope2 = 0
Slope2 = 0
USlope2 = 0
VSlope2 = 0
RSlope2 = 0
GSlope2 = 0
BSlope2 = 0
End if
'' interpolate 0 to 2
dZ3 = Triangle.Vertex(0).pos3d.z - Triangle.Vertex(2).pos3d.z
dX3 = Triangle.Vertex(0).pos2d.x - Triangle.Vertex(2).pos2d.x
dY3 = Triangle.Vertex(0).pos2d.y - Triangle.Vertex(2).pos2d.y
dU3 = Triangle.Vertex(0).U - Triangle.Vertex(2).U
dV3 = Triangle.Vertex(0).V - Triangle.Vertex(2).V
dr3 = Triangle.Vertex(0).color.r - Triangle.Vertex(2).color.r
dg3 = Triangle.Vertex(0).color.g - Triangle.Vertex(2).color.g
db3 = Triangle.Vertex(0).color.b - Triangle.Vertex(2).color.b
if dY3 <> 0 then
dYDiv = 1/dY3
zSlope3 = dZ3 * dYDiv
Slope3 = dX3 * dYDiv
USlope3 = dU3 * dYDiv
VSlope3 = dV3 * dYDiv
RSlope3 = dR3 * dYDiv
GSlope3 = dG3 * dYDiv
BSlope3 = dB3 * dYDiv
else
zSlope3 = 0
Slope3 = 0
USlope3 = 0
VSlope3 = 0
RSlope3 = 0
GSlope3 = 0
BSlope3 = 0
End if
'' Drawing
'' Top
dim as single CurX1=Triangle.Vertex(0).pos2d.x, CurX2=Triangle.Vertex(0).pos2d.x
dim as single CurU1=Triangle.Vertex(0).U, CurU2=Triangle.Vertex(0).U,_
CurV1=Triangle.Vertex(0).V, CurV2=Triangle.Vertex(0).V
dim as single CurR1=Triangle.Vertex(0).Color.r,_
CurG1=Triangle.Vertex(0).Color.g,_
CurB1=Triangle.Vertex(0).Color.b,_
CurR2=Triangle.Vertex(0).Color.r,_
CurG2=Triangle.Vertex(0).Color.g,_
CurB2=Triangle.Vertex(0).Color.b
dim as single CurZ1=Triangle.Vertex(0).pos3d.z, CurZ2=Triangle.Vertex(0).pos3d.z
CurZ1 += Triangle.Parent_Center.z
CurZ2 += Triangle.Parent_Center.z
'BufferIter = Triangle.Vertex(0).pos2d.y * xRes
For y as integer = Triangle.Vertex(0).pos2d.y to Triangle.Vertex(1).pos2d.y-1
TSLine(CurX1,CurX2,y,CurU1,CurV1,CurU2,CurV2, CurZ1, CurZ2, Texture,CurR1,CurR2,CurG1,CurG2,CurB1,CurB2)
CurZ1 += zSlope1
CurZ2 += zSlope3
CurU1 += USlope1
CurU2 += USlope3
CurV1 += VSlope1
CurV2 += VSlope3
CurX1 += Slope1
CurX2 += Slope3
CurR1 += RSlope1
CurR2 += RSlope3
CurG1 += GSlope1
CurG2 += GSlope3
CurB1 += BSlope1
CurB2 += BSlope3
'BufferIter += xRes
Next
'' down
CurZ1 = Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z
CurU1 = Triangle.Vertex(1).U
CurV1 = Triangle.Vertex(1).V
CurX1 = Triangle.Vertex(1).pos2d.x
CurR1 = Triangle.Vertex(1).color.r
Curg1 = Triangle.Vertex(1).color.g
Curb1 = Triangle.Vertex(1).color.b
For y as integer = Triangle.Vertex(1).pos2d.y to Triangle.Vertex(2).pos2d.y-1
TSLine(CurX1,CurX2,y,CurU1,CurV1,CurU2,CurV2, CurZ1, CurZ2, Texture,CurR1,CurR2,CurG1,CurG2,CurB1,CurB2)
CurZ1 += zSlope2
CurZ2 += zSlope3
CurX1 += Slope2
CurX2 += Slope3
CurU1 += USlope2
CurU2 += USlope3
CurV1 += VSlope2
CurV2 += VSlope3
CurR1 += RSlope2
CurR2 += RSlope3
CurG1 += GSlope2
CurG2 += GSlope3
CurB1 += BSlope2
CurB2 += BSlope3
Next
End Sub
Sub Rotate(obj as objectT,Tx as single, Ty as single, Tz as single)
dim as single tmpX,tmpY,TmpZ,normX,normY,normZ
dim as single VnormX,VnormY,VnormZ
For i as integer = 1 to obj.NB_triangles
For j as integer = 0 to 2
'' Points
TmpY = obj.Triangle(i).Vertex(j).pos3d.y * cos(Tx) - obj.Triangle(i).Vertex(j).pos3d.z * sin(Tx)
TmpZ = obj.Triangle(i).Vertex(j).pos3d.z * cos(Tx) + obj.Triangle(i).Vertex(j).pos3d.y * sin(Tx)
obj.Triangle(i).Vertex(j).pos3d.y = TmpY
obj.Triangle(i).Vertex(j).pos3d.z = TmpZ
TmpZ = obj.Triangle(i).Vertex(j).pos3d.z * cos(Ty) - obj.Triangle(i).Vertex(j).pos3d.x * sin(Ty)
TmpX = obj.Triangle(i).Vertex(j).pos3d.x * cos(Ty) + obj.Triangle(i).Vertex(j).pos3d.z * sin(Ty)
obj.Triangle(i).Vertex(j).pos3d.x = TmpX
TmpX = obj.Triangle(i).Vertex(j).pos3d.x * cos(Tz) - obj.Triangle(i).Vertex(j).pos3d.y * sin(Tz)
TmpY = obj.Triangle(i).Vertex(j).pos3d.y * cos(Tz) + obj.Triangle(i).Vertex(j).pos3d.x * sin(Tz)
obj.Triangle(i).Vertex(j).pos3d.x = TmpX
obj.Triangle(i).Vertex(j).pos3d.y = TmpY
obj.Triangle(i).Vertex(j).pos3d.z = TmpZ
'' Vertex normals
VnormY = obj.Triangle(i).Vertex(j).Gouraud_Normal.y * cos(Tx) - obj.Triangle(i).Vertex(j).Gouraud_Normal.z * sin(Tx)
VnormZ = obj.Triangle(i).Vertex(j).Gouraud_Normal.z * cos(Tx) + obj.Triangle(i).Vertex(j).Gouraud_Normal.y * sin(Tx)
obj.Triangle(i).Vertex(j).Gouraud_Normal.y = VnormY
obj.Triangle(i).Vertex(j).Gouraud_Normal.z = VnormZ
VnormZ = obj.Triangle(i).Vertex(j).Gouraud_Normal.z * cos(Ty) - obj.Triangle(i).Vertex(j).Gouraud_Normal.x * sin(Ty)
VnormX = obj.Triangle(i).Vertex(j).Gouraud_Normal.x * cos(Ty) + obj.Triangle(i).Vertex(j).Gouraud_Normal.z * sin(Ty)
obj.Triangle(i).Vertex(j).Gouraud_Normal.x = VnormX
VnormX = obj.Triangle(i).Vertex(j).Gouraud_Normal.x * cos(Tz) - obj.Triangle(i).Vertex(j).Gouraud_Normal.y * sin(Tz)
VnormY = obj.Triangle(i).Vertex(j).Gouraud_Normal.y * cos(Tz) + obj.Triangle(i).Vertex(j).Gouraud_Normal.x * sin(Tz)
obj.Triangle(i).Vertex(j).Gouraud_Normal.x = VnormX
obj.Triangle(i).Vertex(j).Gouraud_Normal.y = VnormY
obj.Triangle(i).Vertex(j).Gouraud_Normal.z = VnormZ
Next
'' normals
NormY = obj.Triangle(i).Normal.y * cos(Tx) - obj.Triangle(i).Normal.z * sin(Tx)
NormZ = obj.Triangle(i).Normal.z * cos(Tx) + obj.Triangle(i).Normal.y * sin(Tx)
obj.Triangle(i).Normal.y = NormY
obj.Triangle(i).Normal.z = NormZ
NormZ = obj.Triangle(i).Normal.z * cos(Ty) - obj.Triangle(i).Normal.x * sin(Ty)
NormX = obj.Triangle(i).Normal.x * cos(Ty) + obj.Triangle(i).Normal.z * sin(Ty)
obj.Triangle(i).Normal.x = NormX
NormX = obj.Triangle(i).Normal.x * cos(Tz) - obj.Triangle(i).Normal.y * sin(Tz)
NormY = obj.Triangle(i).Normal.y * cos(Tz) + obj.Triangle(i).Normal.x * sin(Tz)
obj.Triangle(i).Normal.x = NormX
obj.Triangle(i).Normal.y = NormY
obj.Triangle(i).Normal.z = NormZ
Next
End sub
Function FPS() as integer
dim as integer _curfps
If Timer - FPS_t <= 1 then
FPS_counter += 1
Return -1
else
_CurFPS = FPS_Counter/(Timer - FPS_t)
FPS_counter = 0
FPS_t = Timer
Return _CurFPS
End if
End Function
Function GenerateCube(x0 as integer,y0 as integer,z0 as integer,Size as integer) as objectT
Dim as ObjectT obj
Dim as Single Size_2 = Size/2
obj.Center.x=x0
obj.Center.y=y0
obj.Center.z=z0
obj.nb_triangles = 12 '' 12 !
'' Defining color white/grey
For i as integer = 1 to obj.nb_triangles
for j as integer = 0 to 2
obj.triangle(i).Vertex(j).originalColor.r = 255
obj.triangle(i).Vertex(j).originalColor.g = 255
obj.triangle(i).Vertex(j).originalColor.b = 255
next
next
'' Face 1
obj.triangle(1).Vertex(0).U = 1
obj.triangle(1).Vertex(0).V = 0
obj.triangle(1).Vertex(0).pos3d.x = Size_2
obj.triangle(1).Vertex(0).pos3d.y = Size_2
obj.triangle(1).Vertex(0).pos3d.z = Size_2
obj.triangle(1).Vertex(2).U = 0
obj.triangle(1).Vertex(2).V = 0
obj.triangle(1).Vertex(2).pos3d.x = -Size_2
obj.triangle(1).Vertex(2).pos3d.y = Size_2
obj.triangle(1).Vertex(2).pos3d.z = Size_2
That looks really, really, really nice.
Well done :)
thanks a lot :)
Here's just a little pic resulting from a glitch I had when coding a GenerateSphere() sub, I like it :p
(http://freefile.kristopherw.us/uploads/hezad/glitchnice.jpg)
Update :
Vector Light replaced by Omni Light
(http://freefile.kristopherw.us/uploads/hezad/screen_gouraud_omnilight.jpg)
~20 rotating cubes with an omni light at the center of the screen
image hosted on Kristopher Windsor's image hosting service :p
Where's the file? :) Looks nice but I want to see it in action :D
uhuh oki :D
here it is ^^
and the code :
edit : erm the code must be too long, the code marks cut it :S but if anyone wants to see it, it's in the .rar :)
That's working really nicely :)
This computer is a P4 3.0 Ghz, but the computer is quite old and the frame rate is perfectly ok here, it does a fairly steady 29 - 32 fps.
I suspect that it would be a lot faster without the lightsourcing :) The lightsourcing looks very nice though.
thanks :) Well yeah, if I get off the light calculations and do only a texture mapping i get ~40FPS instead of 30 normally :)
But if cubes are a bit larger or nearer, it's getting worse lol I get ~10 FPS and even 3/4 FPS if they fill the whole screen :S
optimization .. :p
Optimization.. Well, this is coming I guess. and there are a few things that you can do to optimise it.
I think that what you have done there is achieved by less than 1% of people who learn programming, you've progressed a lot and I bet Relsoft will be really happy when he sees what you made since you read his tutorial, he is a member here and I know he likes to show people how to code things.
Anyway onto your demo, the two things that are a problem are the speed and texture warping.
Texture warping is unavoidable with affine texture mapping like you are doing.
It is more noticeable when there is a lot of perspective on the surface you are drawing, for example, some vertex is deep into the screen and other vertices are close, the texture seems to slide around the polygon.
There are solutions, firstly one of the fastest ways is to subdivide the surface into several smaller triangles and subdivide the UV's too, so in practice you might render
/\
/ \
------
Like this;
*
/_\
/ \/ \
------
(apologies for the dodgy ascii), this means that the warping effect is less noticeable.
Or you could use a perspective correct algorithm, with this algorithm and as a side effect you get the z-co-ordinate of each pixel for your Z-Buffer.
Another thing you will probably want to look into is clipping and definately mip-mapping.
Both will increase your speed, clipping algorithms will enable you to chop up polygons and thereby discard geometary that is not visible.
Mip mapping you will generate a set of texture maps ranging from small size to large for your polygon and perform the cross-product equation on your polygon to determine it's area so that you can select the texture map that best fits the area of the polygon you are about to draw.
Smaller texture maps are faster, using mip mapping will also help avoid "dancing pixels" which are typically visible on small polygons where the routine is accessing a large texture map. These artifacts appear as flickering dots.
Anyway that's probably more than enough to be going along with, I reccomend that you start off with perspective correct textures :) Please do post if you need help or just to let us know how you are getting along. I for one am really enjoying this topic!
Hey :) I was coding something to enjoy my engine start (:p) but I didn't read what you just written while I was doing that, so the thing i'll attach with this post hasn't any of the optimizations you just told me.
So first, i'll answer you :)
QuoteI think that what you have done there is achieved by less than 1% of people who learn programming, you've progressed a lot and I bet Relsoft will be really happy when he sees what you made since you read his tutorial, he is a member here and I know he likes to show people how to code things.
Well, thanks ! this kind of comments surely puts me on the motivation track ! And Relsoft already commented it on the freebasic forums.
Even if I know it's far from a perfect thing, I have to admit I'm pretty proud (yeah auto-satisfaction .. :p) of what I have done so far but you know, sometimes you're so glad about some self-made stuff that you don't see crucial flaws. Until now, I think I saw them and you (and other people) really helped me to fix them.
So once again, thanks !!
QuoteTexture warping is unavoidable with affine texture mapping like you are doing.
It is more noticeable when there is a lot of perspective on the surface you are drawing, for example, some vertex is deep into the screen and other vertices are close, the texture seems to slide around the polygon.
(...)
Totally agree, I really need to code the corrected perspective stuff. It's already on my todo list and I spent some time with my friend "internet" to understand it :p Anyway, your explications are totally helpful and I think it'll help me to work on it !
Oh ! And don't worry about the ASCII diagram, It totally makes sense :)
QuoteAnother thing you will probably want to look into is clipping
Right ! But before, I need to make sure I totally understand/recode my coordinates system :the Z-buffer algo + the projection routine work but use some really weird Z constants and/or subtractions. So I really need to see what's going on in there before thinking about clipping :p
(By clipping, I guess you're speaking about not calculating poly's outside the viewing frustum ?)
Quoteand definately mip-mapping.
QuoteMip mapping you will generate a set of texture maps ranging from small size to large for your polygon and perform the cross-product equation on your polygon to determine it's area so that you can select the texture map that best fits the area of the polygon you are about to draw.
Smaller texture maps are faster, using mip mapping will also help avoid "dancing pixels" which are typically visible on small polygons where the routine is accessing a large texture map. These artifacts appear as flickering dots.
!! I heard of mip-mapping so often .. I didn't understand in which way it could be helpful.. Now I do ! It has to be on my todo list :D Once again, Really clear explanation :)
QuoteAnyway that's probably more than enough to be going along with, I reccomend that you start off with perspective correct textures Smiley Please do post if you need help or just to let us know how you are getting along. I for one am really enjoying this topic!
perspective correct texture mapping is the first item on my list :D
I will post if I have questions (and I'm pretty sure i'll have :p), thanks !
And I'm glad you like this topic, really ! :)
To finish, I'll post the thing I spoke about at the beginning of this post, the Plasma Cube ! :D Well I already saw that kind of stuff pretty often but whatever ^^
Attached : Screenshot + Rar(Src+Exe)
thanks again Shockwave !
Bye :)
The other thing you need to look at speedwise is you are doing an incredible number of Single->Int conversions in your code, potentially a dozen per pixel. That is not a fast thing to do, so you might want to think about how you can do it all in integers.
If you backface cull, you won't see all the insides of the objects too - that will halve the number of faces you are considering.
Jim
Quoteyou might want to think about how you can do it all in integers
Yeah sure ! thanks I didn't think about that.
QuoteIf you backface cull, you won't see all the insides of the objects too
hm ? I don't understand :p Since faces are only drawn once, If I backface cull a face, it won't be calculated anyway, no ?
Quote from: Hezad on August 27, 2008
QuoteIf you backface cull, you won't see all the insides of the objects too
hm ? I don't understand :p Since faces are only drawn once, If I backface cull a face, it won't be calculated anyway, no ?
You can either use the normal to backface cull (is it pointing in or out) or just use the cross-product equation to determine if the points are clockwise or counter clockwise (clue: the result is either positive or negative), then you can decide whether to draw the polygon or not.
This means that for a cube which is made from 12 triangles, potentially you will only need to draw a maximum of 6 of them.
Rotating the vertices and doing a cross product calc is cheap when compared to drawing a texture mapped polygon, so what Jim is saying is that the calcs of rotating the object and doing that simple check to see if you should draw the polygon will speed things up a lot.
Quote(By clipping, I guess you're speaking about not calculating poly's outside the viewing frustum ?)
I mean not drawing polys or parts of polys that lie outside the view frustrum. :)
Btw, the plasma cube looks really great! Nice colour choice.
If you clip the polygons, either in 3d (better) or in 2d screen space, then you can remove ALL the If checks that test if the triangle starts off the screen or if a pixel is off the screen. Doing all that stuff up front makes the hard bit of drawing triangles simpler.
Jim
Yeah very cool plasma cube dude.
Welldone on your efforts,
Clyde
QuoteRotating the vertices and doing a cross product calc is cheap when compared to drawing a texture mapped polygon, so what Jim is saying is that the calcs of rotating the object and doing that simple check to see if you should draw the polygon will speed things up a lot.
QuoteIf you clip the polygons, either in 3d (better) or in 2d screen space, then you can remove ALL the If checks that test if the triangle starts off the screen or if a pixel is off the screen. Doing all that stuff up front makes the hard bit of drawing triangles simpler.
So if I understand correctly (and sorry if it's not the case :p), I should verify if the current poly (or pixel?) is in the viewing frustum (in 3d) and then, calculate (Poly Normal [Dot] View Normal) to make sure the poly is visible (and if it's not, I "forget" it for the projection/lighting/rendering calculations) ?
thanks for your answers and for the comments about the plasma cube :)
Wow, this is some seriously impressive work and from what I gather by reading through the posts, you are a literal sponge for information. You're able to keep progressing by answering sensible questions and acting on the responses. Something to be encouraged and celebrated.
:clap:
Quote
If you clip the polygons, either in 3d (better) or in 2d screen space, then you can remove ALL the If checks that test if the triangle starts off the screen or if a pixel is off the screen. Doing all that stuff up front makes the hard bit of drawing triangles simpler.
QuoteSo if I understand correctly (and sorry if it's not the case :p), I should verify if the current poly (or pixel?) is in the viewing frustum (in 3d) and then, calculate (Poly Normal [Dot] View Normal) to make sure the poly is visible (and if it's not, I "forget" it for the projection/lighting/rendering calculations) ?
Not really.
In 2d clipping, you do the whole process that you are currently doing, but just before you render the triangle you work out whether any of it is off screen. If it is, then you can clip the triangle against the screen's rectangle and that will give you a set of triangles which can be rendered without any If testing.
In 3d clipping, you do that process in 3d against the pyramid which is your viewing frustum (imagine a square-based pyramid with the point at the camera and the rectangular base being your far-clip plane). You can work out, before projection, which polygons are inside that volume. The advantage of this is that you don't get range error problems when you do the perspective calculation - and you can't do 2d clipping without doing perspective - so you save yourself some real problems.
Jim
Xalthorn > thanks :D Well I ask questions so if people answer me, I have to take their answers into account :)
Jim > ok thanks, I start to understand your point :p But sorry for this last question but since I am not sure about it, I prefer to ask :
when you say
Quoteyou can clip the triangle against the screen's rectangle and that will give you a set of triangles which can be rendered without any If testing.
Do you mean I discard all the triangle if a part of it must be clipped (but it could appear suddenly in the screen if so, no ?) or shall I "just" don't draw the pixels out of screen ?
Because right now, Just before drawing the pixel, I do a "basic" 2D clipping :
If x>0 and x<xRes and y>0 and y<yRes then putPixelSo do you mean doing this but in 3D ? Or Did I totally misunderstand once again ? Sorry for insisting with it but in fact I guess I don't understand how I could verify whether the poly is in screen/viewing frustum or not without any if testing ..
:)
3D clipping means that you will have to chop up some triangles, it's different to just simple clipping to the viewport, you need to be able to calculate where your triangles intersect the view frustrum and possibly split them into more triangles.
For example consider this triangle;
|\
| \
| \
| \
| \
| \
|_______\
and it intersects the view frustrum;
|\
_|_\_______________
| \
| \
| \
| \
|_______\
Can be chopped up like this;
___________________
| / \
| / \
|_____\
The part of the triangle that is outside the viwe area is discarded and two triangles are computed, includin new uv co-ordinates.
Not as hard as it sounds, a clue is you need to work out where the edges of the triangle intersect the clipping planes so that you can chop up the triangle :)
If you need more detailed help, ask away :)
aah okay now I got it, thanks ^^ I didn't understand the fact that I have to "create" new triangles when the current triangle have a part clipped.
Okay, so now I got it, i'll work on it. I still didn't coded the perspective correct mapping (I had a few tries but I didn't interpolate 1/z,u/z and v/z in a good way).
Anyway, I won't be at home for a week so I won't be able to work on it but once i'll get back, correct texture mapping and 3D clipping will be the first things i'll work on. And then, working on reducing the single>integer conversions. And then, maybe mip mapping :D
Still, while I were trying the perspective correct texture mapping, I reorganized a bit the code. So, a little update before the big stuff :
- It supports any number of lights (Add_Light, Set_Light)
- The projection/Z-buffer now recoded with a comprehensible Z-axis handling (It was needed for the 3d clipping and the correct texture mapping)
- New handling of shader calc and filling with user-friendly subs (Set_shader(), Set_Texture()) : if no texture is assigned to an object, it will automatically be rendered with the selected shader)
Thanks for your explanations everyone and see ya in a week or so !
It's cool that you continue to work on this :) It's been a nice topic so far, hopefully we'll see you implimenting clipping and mip mapping soon.
You will definately need to clip by chopping up triangles because you'll need to clip the triangles according to their depth as well as the x,y position :)
QuoteIf x>0 and x<xRes and y>0 and y<yRes then putPixel
If I had 2 tris filling the screen, that would mean I did this test 640x480 times with every single pixel passing he test, but if I knew the two tris were on screen before I tried drawing them I could remove all these 4 checks from every pixel. That would be a massive speed up! The chopping up might sound complicated, but it's worth wroking it out.
Jim
PS. btw, you want
QuoteIf x>=0 and x<xRes and y>=0 and y<yRes then putPixel
otherwise you won't be able to plot anything on the first scanline or the left column of pixels.
Hey :) I'm back :D
Well, I wanted to post a perspective correct mapping to celebrate the thing but nothing to do, I tried at least 4 or 5 times to rewrite the sub, it doesn't work...
With the advices provided here and the documentation on the internet, I understood that I should interpolate u/z and v/z (and 1/z but since I already know Z from the Z-Buffer calc, I also tried to use it instead of 1/z), and then multiply by the current Z to get the good uv coords. But erm.. It doesn't work >< It makes the program to crash (I guess the uv coords are too large for the texture ptr). Here is the code used in the subs, I hope it's readable :
Sub TSLine(ByVal x1 as integer,ByVal x2 as integer, y as integer,ByVal U1 as single,ByVal V1 as single,ByVal U2 as single,ByVal V2 as single,ByVal z1 as integer,ByVal z2 as integer,byval Texture as TextureT, r1 as integer,r2 as integer,g1 as integer,g2 as integer,b1 as integer,b2 as integer,_z1 as single,_z2 as single)
dim as single dR,dG,dB
dim as single R,g,b
dim as single sr1,sr2,sg1,sg2,sb1,sb2
dim as single dU,dV,dZ,U,V
dim as single Uu1,Vv1,Uu2,Vv2
dim as integer itx1,itx2,zz1,zz2
Dim as single CurZ,xDiv,Cur_Z,_zz1,_zz2,d_Z
if x1<x2 then
zz1 = z1
zz2 = z2
_zz1 = _z1
_zz2 = _z2
itx1 = x1
itx2 = x2
Uu1 = u1
Uu2 = u2
Vv1 = v1
Vv2 = v2
sr1 = r1
sr2 = r2
sg1 = g1
sg2 = g2
sb1 = b1
sb2 = b2
else
zz1 = z2
zz2 = z1
_zz1 = _z2
_zz2 = _z1
itx1 = x2
itx2 = x1
Uu1 = u2
Uu2 = u1
Vv1 = v2
Vv2 = v1
sr1 = r2
sr2 = r1
sg1 = g2
sg2 = g1
sb1 = b2
sb2 = b1
end if
'Uu1 *= Texture.Size.x
'Uu2 *= Texture.Size.x
'Vv1 *= Texture.Size.y
'Vv2 *= Texture.Size.y
xDiv = 1/(itx2 - itx1)
dR = xDiv * (sr2 - sr1)
dG = xDiv * (sg2 - sg1)
dB = xDiv * (sb2 - sb1)
dZ = xDiv * (zz2 - zz1)
d_Z = xDiv * (_zz2 - _zz1)
dU = xDiv * (Uu2 - Uu1)
dV = xDiv * (Vv2 - Vv1)
CurZ = zz1
Cur_Z = _zz1
U = Uu1
V = Vv1
r = sr1
g = sg1
b = sb1
dim as single ValU,ValV
dim as integer it
Dim as integer CurTex,Cr,Cg,Cb
it = y*xRes + itx1
for i as integer = itx1 to itx2-1
if y>=0 and y<yRes and i>=0 and i<xRes then
If (FOV - (-Camera.pos.z + CurZ))>0 then
If Curz < ZBuffer(it) then
ZBuffer(it) = CurZ
'ValU = U*CurZ '' (U/Z)*Z
'ValV = V*CurZ '' (V/Z)*Z
ValU = U/Cur_Z '' (U/Z)/(1/Z)
ValV = V/Cur_Z '' (V/Z)/(1/Z)
CurTex = Texture.GFX[int(ValU*Texture.Size.x)+Texture.Size.x*int(ValV*Texture.Size.y)]
Cb = ((CurTex and 255))*b*0.003921 '' 1/255
Cg = ((CurTex shr 8) and 255)*g*0.003921
Cr = ((CurTex shr 16) and 255)*r*0.003921
ScrPtr[it] = rgb(Cr,Cg,Cb)
end if
end if
end if
it+=1
U += dU
V += dV
Cur_Z += d_Z
r += dR
g += dG
b += dB
curZ += dZ
Next
End Sub
Sub Texture_Map_Shaded(ByVal Triangle as TriangleT, _
ByVal Texture as TextureT) '' UV's saved in the Triangle.Vertex Type
'' Sorting Y values
if Triangle.Vertex(1).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(1),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(0).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(0)
end if
if Triangle.Vertex(2).Pos2d.y < Triangle.Vertex(1).Pos2d.y then
swap Triangle.Vertex(2),Triangle.Vertex(1)
end if
dim as integer dx1,dy1,dx2,dy2,dx3,dy3
dim as single Slope1,Slope2,Slope3, dYDiv
dim as single dU1,dU2,dU3,dV1,dV2,dV3
dim as single USlope1,USlope2,USlope3
dim as single VSlope1,VSlope2,VSlope3
Dim as integer dz1,dz2,dz3,d_z1,d_z2,d_z3
Dim as single zSlope1,zSlope2,zSlope3
Dim as single _zSlope1,_zSlope2,_zSlope3
dim as integer dr1,dg1,db1,dr2,dg2,db2,dr3,dg3,db3
dim as single RSlope1, GSlope1,BSlope1
dim as single RSlope2, GSlope2,BSlope2
dim as single RSlope3, GSlope3,BSlope3
'' interpolate 0 to 1 (pos + color)
dZ1 = Triangle.Vertex(1).pos3d.z - Triangle.Vertex(0).pos3d.z
d_z1 = 1/(Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z) - 1/(Triangle.Vertex(0).pos3d.z+Triangle.Parent_Center.z)
dX1 = Triangle.Vertex(1).pos2d.x - Triangle.Vertex(0).pos2d.x
dY1 = Triangle.Vertex(1).pos2d.y - Triangle.Vertex(0).pos2d.y
dU1 = Triangle.Vertex(1).U/(Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z) - Triangle.Vertex(0).U/(Triangle.Vertex(0).pos3d.z+Triangle.Parent_Center.z)
dV1 = Triangle.Vertex(1).V/(Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z) - Triangle.Vertex(0).V/(Triangle.Vertex(0).pos3d.z+Triangle.Parent_Center.z)
dr1 = Triangle.Vertex(1).color.r - Triangle.Vertex(0).color.r
dg1 = Triangle.Vertex(1).color.g - Triangle.Vertex(0).color.g
db1 = Triangle.Vertex(1).color.b - Triangle.Vertex(0).color.b
if dY1 <> 0 then
dYDiv = 1/dY1
zSlope1 = dZ1 * dYDiv
_zSlope1 = d_Z1 * dYDiv
Slope1 = dX1 * dYDiv
USlope1 = dU1 * dYDiv
VSlope1 = dV1 * dYDiv
RSlope1 = dR1 * dYDiv
GSlope1 = dG1 * dYDiv
BSlope1 = dB1 * dYDiv
else
zSlope1 = 0
_zSlope1 = 0
Slope1 = 0
USlope1 = 0
VSlope1 = 0
RSlope1 = 0
GSlope1 = 0
BSlope1 = 0
End if
'' interpolate 1 to 2
dZ2 = Triangle.Vertex(2).pos3d.z - Triangle.Vertex(1).pos3d.z
d_Z2 = 1/(Triangle.Vertex(2).pos3d.z+Triangle.Parent_Center.z) - 1/(Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z)
dX2 = Triangle.Vertex(2).pos2d.x - Triangle.Vertex(1).pos2d.x
dY2 = Triangle.Vertex(2).pos2d.y - Triangle.Vertex(1).pos2d.y
dU2 = Triangle.Vertex(2).U/(Triangle.Vertex(2).pos3d.z+Triangle.Parent_Center.z) - Triangle.Vertex(1).U/(Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z)
dV2 = Triangle.Vertex(2).V/(Triangle.Vertex(2).pos3d.z+Triangle.Parent_Center.z) - Triangle.Vertex(1).V/(Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z)
dr2 = Triangle.Vertex(2).color.r - Triangle.Vertex(1).color.r
dg2 = Triangle.Vertex(2).color.g - Triangle.Vertex(1).color.g
db2 = Triangle.Vertex(2).color.b - Triangle.Vertex(1).color.b
if dY2 <> 0 then
dYDiv = 1/dY2
zSlope2 = dZ2 * dYDiv
_zSlope2 = d_Z2 * dYDiv
Slope2 = dX2 * dYDiv
USlope2 = dU2 * dYDiv
VSlope2 = dV2 * dYDiv
RSlope2 = dR2 * dYDiv
GSlope2 = dG2 * dYDiv
BSlope2 = dB2 * dYDiv
else
zSlope2 = 0
_zSlope2 = 0
Slope2 = 0
USlope2 = 0
VSlope2 = 0
RSlope2 = 0
GSlope2 = 0
BSlope2 = 0
End if
'' interpolate 0 to 2
dZ3 = Triangle.Vertex(0).pos3d.z - Triangle.Vertex(2).pos3d.z
d_Z3 = 1/(Triangle.Vertex(0).pos3d.z+Triangle.Parent_Center.z) - 1/(Triangle.Vertex(2).pos3d.z+Triangle.Parent_Center.z)
dX3 = Triangle.Vertex(0).pos2d.x - Triangle.Vertex(2).pos2d.x
dY3 = Triangle.Vertex(0).pos2d.y - Triangle.Vertex(2).pos2d.y
dU3 = Triangle.Vertex(0).U/(Triangle.Vertex(0).pos3d.z+Triangle.Parent_Center.z) - Triangle.Vertex(2).U/(Triangle.Vertex(2).pos3d.z+Triangle.Parent_Center.z)
dV3 = Triangle.Vertex(0).V/(Triangle.Vertex(0).pos3d.z+Triangle.Parent_Center.z) - Triangle.Vertex(2).V/(Triangle.Vertex(2).pos3d.z+Triangle.Parent_Center.z)
dr3 = Triangle.Vertex(0).color.r - Triangle.Vertex(2).color.r
dg3 = Triangle.Vertex(0).color.g - Triangle.Vertex(2).color.g
db3 = Triangle.Vertex(0).color.b - Triangle.Vertex(2).color.b
if dY3 <> 0 then
dYDiv = 1/dY3
zSlope3 = dZ3 * dYDiv
_zSlope3 = d_Z3 * dYDiv
Slope3 = dX3 * dYDiv
USlope3 = dU3 * dYDiv
VSlope3 = dV3 * dYDiv
RSlope3 = dR3 * dYDiv
GSlope3 = dG3 * dYDiv
BSlope3 = dB3 * dYDiv
else
zSlope3 = 0
_zSlope3 = 0
Slope3 = 0
USlope3 = 0
VSlope3 = 0
RSlope3 = 0
GSlope3 = 0
BSlope3 = 0
End if
'' Drawing
'' Top
dim as single CurX1=Triangle.Vertex(0).pos2d.x, CurX2=Triangle.Vertex(0).pos2d.x
dim as single CurU1, CurU2,_
CurV1, CurV2
dim as single CurR1=Triangle.Vertex(0).Color.r,_
CurG1=Triangle.Vertex(0).Color.g,_
CurB1=Triangle.Vertex(0).Color.b,_
CurR2=Triangle.Vertex(0).Color.r,_
CurG2=Triangle.Vertex(0).Color.g,_
CurB2=Triangle.Vertex(0).Color.b
dim as single CurZ1=Triangle.Vertex(0).pos3d.z, CurZ2=Triangle.Vertex(0).pos3d.z
dim as single Cur_Z1, Cur_Z2
CurZ1 += Triangle.Parent_Center.z
CurZ2 += Triangle.Parent_Center.z
CurU1 = Triangle.Vertex(0).U/CurZ1
CurV1 = Triangle.Vertex(0).V/CurZ1
CurU2 = Triangle.Vertex(0).U/CurZ1
CurV2 = Triangle.Vertex(0).V/CurZ1
Cur_Z1 = 1/CurZ1
Cur_Z2 = 1/CurZ2
'BufferIter = Triangle.Vertex(0).pos2d.y * xRes
For y as integer = Triangle.Vertex(0).pos2d.y to Triangle.Vertex(1).pos2d.y-1
TSLine(CurX1,CurX2,y,CurU1,CurV1,CurU2,CurV2, CurZ1, CurZ2, Texture,CurR1,CurR2,CurG1,CurG2,CurB1,CurB2,Cur_Z1,Cur_Z2)
CurZ1 += zSlope1
CurZ2 += zSlope3
Cur_Z1 += _zSlope1
Cur_Z2 += _zSlope3
CurU1 += USlope1
CurU2 += USlope3
CurV1 += VSlope1
CurV2 += VSlope3
CurX1 += Slope1
CurX2 += Slope3
CurR1 += RSlope1
CurR2 += RSlope3
CurG1 += GSlope1
CurG2 += GSlope3
CurB1 += BSlope1
CurB2 += BSlope3
'BufferIter += xRes
Next
'' down
CurZ1 = Triangle.Vertex(1).pos3d.z+Triangle.Parent_Center.z
Cur_Z1 = 1/CurZ1
CurU1 = Triangle.Vertex(1).U/CurZ1
CurV1 = Triangle.Vertex(1).V/CurZ1
CurX1 = Triangle.Vertex(1).pos2d.x
CurR1 = Triangle.Vertex(1).color.r
Curg1 = Triangle.Vertex(1).color.g
Curb1 = Triangle.Vertex(1).color.b
For y as integer = Triangle.Vertex(1).pos2d.y to Triangle.Vertex(2).pos2d.y-1
TSLine(CurX1,CurX2,y,CurU1,CurV1,CurU2,CurV2, CurZ1, CurZ2, Texture,CurR1,CurR2,CurG1,CurG2,CurB1,CurB2,Cur_Z1,Cur_Z2)
CurZ1 += zSlope2
CurZ2 += zSlope3
Cur_Z1 += _zSlope2
Cur_Z2 += _zSlope3
CurX1 += Slope2
CurX2 += Slope3
CurU1 += USlope2
CurU2 += USlope3
CurV1 += VSlope2
CurV2 += VSlope3
CurR1 += RSlope2
CurR2 += RSlope3
CurG1 += GSlope2
CurG2 += GSlope3
CurB1 += BSlope2
CurB2 += BSlope3
Next
End Sub
Can you see what's wrong in there ?
In the code, variables like _z or d_z or Cur_Z means 1/z (respectively 1/z, d(1/z) and Current1/Z) and I kept the name U and V to contain U/z and V/z.
Thanks in advance :)
(update in code)
You have almost certainly answered your own question, because you are not interpolating 1/z, but instead using your own z-buffer calculation and then multiplying that by a factor to use in your equation, then it' more than likely trying to fetch texels from way outside your texture map.
I sat down to read through the source, I am sorry, it's blurry to me with a lot of similar variables that I can't seem to keep track of easily. Anyway..
My advice to you would be to stick to interpolating 1/z.
In a nutshell, you should be doing something similar to this;
Interpolate U/Z, interpolate V/Z and interpolate 1/Z
To get your texels;
((U/Z) / (1/Z)) * Z = U
((V/Z) / (1/Z)) * Z = V
Something like that. It's been a long time since I did it, but I think that's about right.
It's not the fastest way of doing this but you should probably get it working this way first.
Some notes:
if x1<x2 then
zz1 = z1
zz2 = z2
_zz1 = _z1
_zz2 = _z2
itx1 = x1
itx2 = x2
Uu1 = u1
Uu2 = u2
Vv1 = v1
Vv2 = v2
sr1 = r1
sr2 = r2
sg1 = g1
sg2 = g2
sb1 = b1
sb2 = b2
else
zz1 = z2
zz2 = z1
_zz1 = _z2
_zz2 = _z1
itx1 = x2
itx2 = x1
Uu1 = u2
Uu2 = u1
Vv1 = v2
Vv2 = v1
sr1 = r2
sr2 = r1
sg1 = g2
sg2 = g1
sb1 = b2
sb2 = b1
end if
better use a vertexstructure and just swap the pointers.
you don't even have to care about this if all polygons are defined in equal orientation and backface-culling is used to remove all polygons not facing the viewer (so the orientation of x1/x2 will never change).
xDiv = 1/(itx2 - itx1)
dR = xDiv * (sr2 - sr1)
dG = xDiv * (sg2 - sg1)
dB = xDiv * (sb2 - sb1)
dZ = xDiv * (zz2 - zz1)
these gradients across your polygon are linear, this means they are the same for every scanline.
just calculate them once for the longest scanline (biggest deltax: highest precision).
this also means you just have to keep track of u,v,z of the left side of your polygon.
if y>=0 and y<yResyou don't have to do this for every pixel of a scanline.
check this before you enter the inner loop.
if i>=0 and i<xRes thendon't do this for every pixel either.
instead just test how many pixels are "out" on the left side and update u,v,z (eg. deltaU*pixelsToSkip).
the pixels on the right can just be skipped.
when you interpolating x along your polygon edges, the starting "x" for each scanline is actually a floating-point value.
you should take this into account and update the starting u,v,z of each scanline by a fraction of your deltas (how much is missing from the actual floating-point "x" to the rounded integer-pixel-position).
this is necessary for "y", too and can incorporate clipping on the x/y-axes for free.
If (FOV - (-Camera.pos.z + CurZ))>0 thenif this is some approach of z-clipping, drop it. it doesn't work.
you must perform z-clipping before doing perspective division.
when z gets near 0 (and even flips sign right afterwards) your 2d-coordinates break.
ValU = U/Cur_Z
ValV = V/Cur_Zthis won't result in useful framerates because division is expensive.
instead calculate 1/z (and respective u,v) every 8 or 16 pixels and interpolate linearly between two pairs of perspective correct u,v. if you manage the latter with integer-precision only, you can get the division for free (fpu and cpu run in parallel), but you'll probably need some assembly here.
You can even interpolate u,v bi-linearly (across scanlines, for eaxmple between 4 points of a 8x8-grid):
on an average you just need to calculate two (depending on deltax) additional u,v on a scanline and can skip perspective divide for the next 7 scanlines.
If you want to look deeper into how certain division-algorithms (http://en.wikipedia.org/wiki/Division_(digital)) work, you can even approximate the next 1/z (since deltaz is usually small) by a single successive iteration and using the previous result as a starting point.
Cb = ((CurTex and 255))*b*0.003921 '' 1/255
Cg = ((CurTex shr 8) and 255)*g*0.003921
Cr = ((CurTex shr 16) and 255)*r*0.003921as jim pointed out already, the float-to-integer conversion is expensive, too (and the precision is not necessary).
keep the interpolated r,g,b as integers with some additional fixed-point-precision (for example multiply all values by 2^16):
Cb = ((CurTex and 255) * (b shr 16) shr 8(but this is essentially what they invented mmx (http://www.tommesani.com/MMXPrimer.html) for)
CurTex = Texture.GFX[int(ValU*Texture.Size.x)+Texture.Size.x*int(ValV*Texture.Size.y)]Same here (interpolate in integers). You can also try to make your textures' width a power of two, so you can replace the multiply by a shift.
Quoteuse the cross-product equation to determine if the points are clockwise or counter clockwise
Rotating the vertices and doing a cross product calc is cheap [...]
The cross-product of the
rotated vertices of a triangle gives you the
transformed normal-vector of the face.
you can compare it to the view-vector (by a dot-product) to see if it's facing in the same direction.
In camera-space the view-vector is simply (0,0,1) so you just have to look at the sign of the z-component of your (transformed) face-normal (which you can get by just a single dot-product).
Hey :)
Shockwave : Well, sorry for the blurry source ^^ Concerning the 1/z interpolation, the point is I also tried it :/ and several times ! But I don't know, maybe I just have to start again this sub..
hellfire : Thanks for all those useful advises/optimizations, I note them all for a future optimization session :p (for now I'm stuck with the perspective correct mapping stuff).
Just a note concerning the number of divisions in the sub : It's just a thing to test the texture mapping ; once it'll work, i'll reduce them drastically :)
Thanks ! I continue my peregrinations in the world of texture mapping :p
edit : Well, I started again the Sub coding but even if I payed attention to the u/z,v/z and 1/z interpolation, there's nothing to do, the result is .. Well let's say it's still not what I was expecting :p Here is a zip with the code and the compiled stuff. So after some reflexion, here is my thoughts :
- Maybe there's a problem with the world origin (Z origin is almost on the cubes line in what you can see in the exe), I guess signs are inverting somewhere ..
- I'm interpolating u/z,v/z and 1/z exactly like I was interpolating u,v, and z (excepted the fact that calculations are based on u/z,v/z and 1/z instead of u,v and z of course) and maybe I shouldn't, I don't know.
Anyway, I'm still on it and I can't wait to start optimizing and to code the clipping with the view frustum :)
edit-edit : In the exe, you can see the floor is rotating, it's totally normal :p it's here to test the mapping.
Code review looks OK, all except this bit
CurZ1 += Triangle.Parent_Center.z
CurZ2 += Triangle.Parent_Center.z
which I don't understand at all. You need to make sure the z you have divided everything by is the z you use when you interpolate.
ValU = (U_z/Cur_Z) '' ((U/Z)/(1/Z))
ValV = (V_z/Cur_Z) '' ((V/Z)/(1/Z))
Good optimization, but you can still do
ooz = 1/z
ValU = U_z * ooz
ValV = V_z * ooz
Jim
Seems like you're nearly there :) Keep going!
A bit offtopic but might be handy if you start using meshes with more polygons:
You don't need O(n^2) for calculating per-vertex-normals.
Here's a short snippet from a C++ source:
// set vertex-normals to 0
for (i = 0; i < numVertices; i++)
vertices[i].normal.set(0.0, 0.0, 0.0);
for (i = 0; i < numTriangles; i++)
{
// 3 indices of triangle i into the vertex array
int idx1= face[i][0];
int idx2= face[i][1];
int idx3= face[i][2];
// get coordinates
const Vector& v1= vertices[ idx1 ].pos;
const Vector& v2= vertices[ idx2 ].pos;
const Vector& v3= vertices[ idx3 ].pos;
// calculate normal vector
Vector n= (v2-v1) % (v3-v1) // cross product
// add face-normal to all adjacent vertices
vertices[idx1].normal+= n;
vertices[idx2].normal+= n;
vertices[idx3].normal+= n;
// store the face normal
faceNormals[i]= n.normalize();
}
// normalize the vertex-normals
for (i = 0; i < numVertices; i++)
vertices[i].normal.normalize();
Quote from: Jim on September 08, 2008
Code review looks OK, all except this bit
CurZ1 += Triangle.Parent_Center.z
CurZ2 += Triangle.Parent_Center.z
which I don't understand at all. You need to make sure the z you have divided everything by is the z you use when you interpolate.
well in fact when I start my interpolation calcs, I use :
dZ2 = Triangle.Vertex(2).pos3d.z - Triangle.Vertex(1).pos3d.zEven if I was using
dZ2 = (Triangle.Vertex(2).pos3d.z+TriangleCenter) - (Triangle.Vertex(1).pos3d.z+TriangleCenter)it'd be the same result.
But I have to start interpolation on the good Z value by adding the objectCenter to it : each object vertex have their coordinates in object Space (Origin = object Center)
I tested to add triangleCenter to the vertices before calculating slopes but it's worse :S But strangely (or not?), for the 1/z interpolation, if I use 1/(z+objCenter), it doesn't work at all
Quote
ValU = (U_z/Cur_Z) '' ((U/Z)/(1/Z))
ValV = (V_z/Cur_Z) '' ((V/Z)/(1/Z))
Good optimization, but you can still do
ooz = 1/z
ValU = U_z * ooz
ValV = V_z * ooz
ah yeah, didn't noticed this one :p thanks
Shockwave : I'm still on it ! ;D
hellfire : ah cool :) as far as I can understand, it calculates the normal on a vertex only 1 time instead of NbAdjacentTriangles times ?
I just compiled your source, added a texture and it looks fine.
What's the problem?...
o_O well, Its way better looking for you, it's strange. Anyway, look on the cubes side, we can see a non-well-textured triangle :S
Still, I can't understand why your floor's good looking while mine is completely distorted in the middle... you only changed your texture ? Same Size ?
When I compile and run it on a different pc, the floor is black.
My guess to that, division by 0 somewhere, or division overflow.
Jim
- division by zero is really probable ! But since it wasn't making my computer to crash, I though the compiler was handling this. But as I see, it really depends about the computer .. grah :S
- division overflow ?
Thanks for the feedback, step by step, i'm sure i'll find where the bug comes from :)
YEEEEEYYYYYY !!!
Well I got something I think :D
I added an arbitrary number while dividing by Z :
_z1 = 1/(Triangle.Vertex(0).pos3d.z+triangle.parent_center.z+1000)
_z2 = 1/(Triangle.Vertex(1).pos3d.z+triangle.parent_center.z+1000)
_z3 = 1/(Triangle.Vertex(2).pos3d.z+triangle.parent_center.z+1000)
Since you spoke about division by 0, I expected it to be in the middle of the plane (its origin) so I added a number (here, 1000) to the divisor and it seems to work for me. The perspective mapping is still not perfect though :/ Anyway, it's way better :D
Here is a screen and a zip(code+exe+textures)
edit : Anyway, it's not a good solution since I could have large objects but far from the observer, And for those objects, I'd need to change the +1000 to a bigger value (it's a totally arbitrary number).. So in fact, i'm just moving the problem here .. I need to find a solution really resolving the problem :/
Well done!
"z=0" should be at the position of the camera.
anything that's behind it will be clipped anyway and your problem is solved.
yeah I though about it but my projection equation is :
zDiv = 1/(ZOOM + obj.Triangle(i).Vertex(j).ViewPos.z)
obj.Triangle(i).Vertex(j).pos2d.x = xRes*.5 + FOV * obj.Triangle(i).Vertex(j).ViewPos.x * zDiv
obj.Triangle(i).Vertex(j).pos2d.y = yRes*.5 - FOV * obj.Triangle(i).Vertex(j).ViewPos.y * zDiv
Do you think I can get out the ZOOM term without any distortion in some big objects ?
(Well, I just tested, And geting off the zoom term is way better in fact, FOV is much more effective)
A last question about the mapping though : Did you notice there are still some texture deformations when the triangles are close ? Is it normal (an effect I can't avoid) or is there still something not working here ?
thanks again !
welldone dude. :)
Just something I noticed, is when you run the exe straight from the archive, without extracting everything, on certain parts of your cubes theres a cyan pixel,
thanks :)
QuoteJust something I noticed, is when you run the exe straight from the archive, without extracting everything, on certain parts of your cubes theres a cyan pixel,
huh ? lol it's weird, I DL'ed the archive from here and I don't see any cyan pixels. But it looks like there are some surprises on different computers :S
Possibly Clyde has a fucked up pixel on his monitor, I'd guess that he is using an LCD screen and it might be time to buy a new one :)
The mapping works really well by the way :)
Well done!
QuoteDid you notice there are still some texture deformations when the triangles are close?
Yes, you definatly have some serious pixelshaking going on.
I can't tell if it's because of missing precision or if there's something wrong with your interpolation - your code is a bit "cryptic" to read there ;)
But it's easy to find out: Use only a single polygon for the floor-plane (of course the shading won't work properly then).
To be honest, you should seriously clean and structure your code a bit, use meaningful names for variables, encapsulate parts and split the source-files.
Shockwave : thanks ^^ Well I hope for clyde it's a bug in the code but I hope for my code it's a dead pixel on his monitor ... Strange situation :p
hellfire : Yeah sorry about the interpolation code, I wrote it a bit like a draft and I didn't organized and/or rewritten it yet. I'll try the "single polygon for the floor-plane" :p (in fact I used several planes for the floor to tile the texture since I didn't write the tiletexture routine yet :p)
To be honest, you should seriously clean and structure your code a bit, use meaningful names for variables, encapsulate parts and split the source-files.
I totally agree -_- the point is, organizing code is my main problem ! I often get into some inextricable mess with my code !
For the 3d engine, I though about a new type : SceneType in which I could store all the objects in the current Scene, and use it to render the whole scene. (for now, I must specify in my subs which object I wanna render... not very practical). Do you think it could improve the organization/readability of my code ?
Anyway, before working on this, I have red a lot of stuff here I have to code (optimizations mainly)
*Working on the frustum clipping*
edit : Just cheated the clipping to test something, here's a preview :) aah the 20 FPS are coming back ^^
I dug out some old software-rendering code this evening to check how I did things myself back then, especially the perspective mapping (I attached a little example, you can use the mouse to rotate the object).
QuoteI though about a new type in which I could store all the objects in the current Scene, and use it to render the whole scene.
I would separate rendering and scene-management.
For the latter I use a simple tree to manage objects with hierachical-structures.
There's one base-type for all different kinds of objects (eg. camera, light, mesh, etc) which basically contains a current transformation-matrix.
Each mesh is assigned to a material-object which manages vertex-processing and rendering.
Each material has an own vertex-structure (and stores or generates all vertex-parameters, eg. texture-coordinates for environment mapping) and a polyfiller to draw the resulting polygons.
Clipping is done via Sutherland/Hodgeman in 3D Camera-Space as a template-class, so it can process different vertex-types.
To perform the actual rendering, I just have to iterate a list of materials (actually there two, one for opaque and one for transparent objects) and everything is done.
Some more technical aspects:
When performing clipping, your triangle will become a convex polygon with an arbitray number of vertices. For the beginning you'll probably just split it back into triangles.
However, most of the time your meshes will contain a reasonable number of quads (usually 3d-modelling is done by deforming grids). You should consider taking this into account in your polyfiller, so you don't have to draw two "half quads" all the time. Handling arbitrary convex polygons sounds difficult, but your polyfiller will become much more straight forward.
Here's some pseudo-code that might be helpful (based on Mats' article (http://www.exaflop.org/docs/fatmap)):
struct Vertex
{
float x,y,z;
float u,v;
};
void drawPoly(Vertex *vtx, int vertices)
{
// we want to draw the polygon from top to bottom:
// find the vertices with minimum and maximum y-coordinate
Vertex *min_vtx = &vtx[0];
Vertex *max_vtx = &vtx[0];
for(int n=1; n<vertices; n++, vtx++)
{
if (vtx[n].y < min_vtx->y) min_vtx = vtx;
if (vtx[n].y > max_vtx->y) max_vtx = vtx;
}
int min_y = ceil(min_vtx->y);
int max_y = ceil(max_vtx->y);
// calculate deltas (on first triangle)
float delta1= vtx[1].y - vtx[2].y;
float delta2= vtx[0].y - vtx[2].y;
float d = 1.0 / ((vtx[0].x - vtx[2].x) * delta1 - (vtx[1].x - vtx[2].x) * delta2);
delta1*= d;
delta2*= d;
float deltau= (vtx[0].u - vtx[2].u)*delta1 - (vtx[1].u - vtx[2].u)*delta2;
float deltav= (vtx[0].v - vtx[2].v)*delta1 - (vtx[1].v - vtx[2].v)*delta2;
float deltaz= (vtx[0].z - vtx[2].z)*delta1 - (vtx[1].z - vtx[2].z)*delta2;
// we are now tracing the left and right sides of the polygon
// initially start both sides at the top vertex
Vertex *left_vtx = min_vtx;
Vertex *right_vtx = min_vtx;
// the height of the left and right slopes (initially 0, forces calculation when entering the loop)
int right_height=0;
int left_height=0;
// left and right slopes
float left_x, left_dx; // x-coordinate
float left_z, left_dz; // z-coordinate
float left_u, left_du; // texture U
float left_v, left_dv; // texture V
float right_x, right_dx;
// draw polygon top->bottom
// the loop isn't really required here since we terminate in the slope-calculation when reaching the bottom-vertex.
for(int y=min_y; y<max_y; y++)
{
// at the end of right slope: calculate next one
while (left_height <= 0)
{
// at the bottom: done.
if(left_vtx == max_vtx) return;
// the polygon is expected to be counter-clockwise
// starting from the top, the left side has increasing vertices, right side has decreasing vertices
Vertex *v1= left_vtx;
left_vtx++;
// if we're at the end of the vertex-list, wrap to the front
if (left_vtx >= vtx+vertices) left_vtx= vtx;
Vertex *v2= left_vtx;
// the actual number of scanlines for this slope
left_height = ceil(v2->y) - ceil(v1->y);
// can be 0 which means the next vertex lies in the same scanline: go straight to the next slope
if (left_height == 0)
continue;
// calculate slope v1 -> v2
// this is the delta for all vertex-attributes to get to the enxt scanline
float inv_height= 1.0 / (v2->y - v1->y);
left_dx = (v2->x - v1->x) * inv_height;
left_dz = (v2->z - v1->z) * inv_height;
left_du = (v2->u - v1->u) * inv_height;
left_dv = (v2->v - v1->v) * inv_height;
// subpixel-correction:
// the vertex' y-coordinate is some floating-point number, while the scanline is at an integer y-coordinate (rounded up)
// so we are already a bit further on the y-axis and the vertex-attributes should be updated accordingly:
// add a fraction of the y-delta to compensate
float prestep= ceil(v1->y) - v1->y;
left_x = v1->x + prestep * left_dx;
left_z = v1->z + prestep * left_dz;
left_u = v1->u + prestep * left_du;
left_v = v1->v + prestep * left_dv;
}
// right slope: same procedure
while (right_height <= 0)
{
if(right_vtx == max_vtx) return;
// walk backwards through the vertex array
Vertex *v1= right_vtx;
right_vtx--;
if(right_vtx < vtx) right_vtx= vtx+vertices-1;
Vertex *v2= right_vtx;
right_height = ceil(v2->y) - ceil(v1->y);
if (right_height == 0)
continue;
float inv_height= 1.0 / (v2->y - v1->y);
right_dx = (v2->x - v1->x) * inv_height;
float prestep = ceil(v1->y) - v1->y;
right_x = v1->x + prestep * right_dx;
}
// round x-coordinates to whole pixels
int x1 = ceil(left_x);
int x2 = ceil(right_x);
// subtexel correction:
// since we rounded to the next integer coordinate, we are already a bit further on the x-axis
// update attributes accordingly:
float prestep = x1 - left_x;
float u = left_u + (prestep * deltau);
float v = left_v + (prestep * deltav);
float z = left_z + (prestep * deltaz);
// draw the scanline:
for (int x=x1;x<x2;x++)
{
// ...
u+=deltau;
v+=deltav;
z+=deltaz;
}
// interpolate left and right slopes
left_height--;
left_x += left_dx;
left_u += left_du;
left_v += left_dv;
left_z += left_dz;
right_height--;
right_x += right_dx;
}
}
hey nice cube :D Thanks a lot for the code :clap: I'm reading it right now :) I haven't a lot of C/C++ knowledge but I should understand the code after several readings. But there's something I don't understand :
AFAIK, Sutherland/Hodgman algorithm works on 2d polygons, so I'll need to project all polygons before using it, no ? So, should I code a quick clip with the view frustum (getting off the list all polys lying totally outside the view) and THEN, using the sutherland/hodgman algo on the remaining triangles to process the ones which are ON the frustum ( eg: two vertices inside, another one outside) ?
Sorry for asking so many questions ;o but before attacking optimizations and code organization with all the tips I red there, I'd like to have a good clipping code.
Oh and while I'm there, my perspective correct mapping is definitely not as correct as I'd like it to be .. The mapping on cubes is totally ok, but the one on the floor really looks like an affine texture mapping in fact (however, it's exactly the same sub used for the cubes and the floor ... strange o_O)
So my current ToDo list :
- Fixing the floor mapping bug
- Coding clipping
Future todo list :
- Optimize code/reorganize it
- TileTexture routine
- Mip-Mapping
Sutherland/Hodgeman is usually applied in 2D-Coordinates, but it works just as well in 3D-Coordinates.
What I do is to transform all vertices into camera space, perform backface-culling and clip all remaining polygons to the view-frustum; in camera space this is extremely easy:
What's still left transformation-wise is just the perspective division:
x'= x / z
y'= y / z
Coordinates (x', y') in the range -1..+1 are inside the view frustum; thus:
x > z : out on right
-x> z: out on left
y > z: out on bottom
-y> z: out on top
near > z: out on front
This resembles the clip-flags typically used by Sutherland/Hodgeman and is actually cheaper than performing clipping previous to transformation (which results in an additional dot-product per clipping-plane).
Division by 0...
1/0 = +infinity
-1/0 = -infinity
in fact
p/0 = +infinity
-p/0 = -infinity
Once you get an infinity in the fpu things will screw up. Happens when your z is 0. Often you can see if this is the case by adding
asm { finit }
to the inner loop in your code. That resets the fpu and clears up the error, but it's slow, and you don't want to do this.
Division overflow...
With integers this can be a big problem...when you divide a 64bit number by a 32bit number, the result is expected to fit in 32bits, so...
00000001:00000000/1 is a division overflow, because the results is 00000001:00000000 which is over 32 bits long.
With float, the problem can still occur. It happens when the z is very near 0 but the x or y are large, so
z = 0.000000001 x = 10000, y = 10000.
That would give x/z = 10000000000000000, which is possible to represent as a float, but probably not what you're expecting in your calcs. And if you fed that to your triangle routine as an x coordinate you're going to take a very long time to draw a span!!
So, as hellfire has been trying to push you, you need to look at (at the very least) cohen sutherland hodgeman clipping to clip your polygons at z= some sensible value like 1 (near plane clip), and then to 3d clip (or at least 2d clip) after that. That keeps the x,y,z in a sensible range, z will never be 0 or tiny, and you avoid all the the precision and range problems :)
Jim
PS, Yes, ZOOM should be removed from your perspective calc. It has no real life analogy, it's a fudge to get round the above problems.
Thanks for those informations :) I got it now ! Sorry for the time taken before responding but I wanted to finish clipping before posting a new message. But erm, since it's will take more time I expected, the less I can do is answering ! Just for info, what I got for the "clipping order" :
- First clipping (What poly is totally inside the view frustum ?)
- Backface culling on those polys (not implemented yet)
- Sutherland/hodgeman clipping for the triangles crossing the frustum (I'm pulling my hair out on this :p)
k++ to shockwave, hellfire and Jim. Thanks a lot !
Quoteclipping [...] I'm pulling my hair out on this :p
Actually it's quite easy ;)
Let's assume your vertices are already transformed into camera-space and all that's left is perspective division.
You first check for all vertices if it's outside of any planes of the view frustum and store the result in a bitmask.
As described above, the vertex/plane-test is simple in camera-space:
#define CLIP_TOP 1
#define CLIP_LEFT 2
#define CLIP_RIGHT 4
#define CLIP_BOTTOM 8
#define CLIP_FRONT 16
#define CLIP_BACK 32
clip=0
if -x>z then clip = clip or CLIP_LEFT
if x>z then clip= clip or CLIP_RIGHT
if y>z then clip= clip or CLIP_BOTTOM
if -y>z then clip= clip or CLIP_TOP
if z<znear then clip= clip or CLIP_FRONT
Note: I assume the negative y-axis going up
"And'ing" the clipflags of all vertices of a polygon tells if it's completely outside of any clip-plane (result is non-zero: skip it).
"Or'ing" the clipflags tells if the polygon needs any clipping at all (result is zero: draw it without clipping)
Now you go through the vertices of your polygon and check each edge (adjacent pair of vertices) if they are on different sides of a frustum-plane:
(I was too lazy to convert the syntax to freebasic)
// dst: list of pointers to output vertices
// src: list of pointers to input vertices
// srcNum: number of input vertices
int clipLeft(Vertex **dst, Vertex **src, int srcNum)
{
Vertex *v1, *v2;
int n,num=0;
int c1,c2;
v1= src[srcNum-1]; // start with the last vertex so we don't have to handle array-wrap
for (n=0;n<srcNum;n++) // go through the whole polygon-array
{
v2= src[n]; // second vertex to check
// extract the clip-information for the current plane (here: left)
c1= v1->clip & CLIP_LEFT;
c2= v2->clip & CLIP_LEFT;
// if v1 is inside the frustum, just make a copy (pointer: cheap)
if (c1==0) dst[num++]= v1;
// if the two vertices are on different sides, create a new vertex on the plane
if (c1!=c2)
{
// v'=v1+t*(v2-v1) intersects the clip-plane:
// here: -v'.x=v'.z (see clipflags)
float t= (v1->x + v1->z) / (v1->z + v1->x - v2->x - v2->z)
// linear interpolation
*scratch= v1 + t*(v2-v1);
// update clipmask - remember that only the remaining and previously-set clip-planes are necessary to be checked
scratch->clip= scratch->clipmask();
dst[num++]= scratch++; // add to output (and point "scratch" to the next free vertex)
}
v1= v2; // next edge
}
return num; // number of output-vertices
}
the output-array "dst" just stores pointers to the actual vertices because most of them just get copied and we just have to store the pointer then (some linked-list might work well, too).
"scratch" is a pool of temporary vertices so we don't have to allocate new memory (which is costly).
Just repeat the process for every clipping-plane. You just have to work out the edge/plane intersection-points (parameter "t"):
right: v.x = v.z
bottom: v.y= v.z
top: -v.y= v.z
For the resulting vertices all that's left to do is perspective transformation:
z= 1.0 / z
x= x*z
y= y*z
// the clipping-process ensured that all x,y are now in a range of -1..+1
// we just have to scale them to the screen-dimensions:
x= (1.0 + x) * xres * 0.5
y= (1.0 + y) * yres * 0.5
hey :)
QuoteYou first check for all vertices if it's outside of any planes of the view frustum and store the result in a bitmask.
As described above, the vertex/plane-test is simple in camera-space:
The method with bitmask is really enjoyable, I guess I'll use it ! Thanks :)
QuoteNow you go through the vertices of your polygon and check each edge (adjacent pair of vertices) if they are on different sides of a frustum-plane:
(I was too lazy to convert the syntax to freebasic)
The code is self explaining, even in .. C++ I guess ? Well, it will be more than useful for me, k++ to you for that !
by the way, Won't there be any problem for the triangles crossing 2 or 3 planes at the same time ? (eg : A very big poly crossing Left, ZFar and Up planes)
QuoteWon't there be any problem for the triangles crossing 2 or 3 planes at the same time ?
No, because you will always clip against one plane at a time.
In the "worst" case you clip so much off on one side that the polygon doesn't overlap a neighbouring side anymore.
So it's just important to recheck the flags after clipping.
Well okay, so I have work to do now ^^
thanks again !
Just a little message while i'm working on it :
I just implemented the clipping part without chopping new triangles. I was expecting nothing special when I ran it to see if there were any problem ...
I have between 18 and 24 FPS !!! :updance:
(yeah it's good news for me ^^ I had between 1 and 9 FPS when testing clipping before hellfire's snippets). And I didn't even implemented the triangles chopping nor the backface culling :p
On those words, I return to FBide ! :D
thanks !
You should be able to get some really useful framerates when you are further along with the backface culling, and expect another speed up when you impliment mip maps too, not to mention a big improvement in image quality.
Shockwave > Well, You won't see this message soon since you may not come here for some time 'cause of your leg but anyway, I have to finish the clipping part and the optimizations in code. And then, I'll work on mip-mapping :)
hellfire > Just saw the updates you made in your post about clipping, thanks :) But even if I really understood how to get the new vertices thanks to your code (I even implemented it), I don't see how I will "transform" those vertices into triangles. In the interpolation part of the clipping, I interpolated U and V and 1/Z coordinates too so the filling part can work. But I don't know how to select 3 good vertices to build a new triangle :/
I tried a naive way :
Newtriangle=( newvert(i),newvert(i+1),newvert(i+2) )
but of course it don't work.
If you need to see the code, I'll post it :)
thanks again for your implication.
Let's assume you have some mesh (vertices in random order):
0 1
+---+
|\ |
| \ |
| \|
+---+
3 2
And a list of indices building triangles from the vertcies:
2,0,3 1,0,2
The order of the indices is counter-clockwise for all triangles (or the other way round, doesn't matter).
Since you use the same order to traverse the edges in the clipping-process, the resulting polygon is defined in counter-clockwise order, too:
0 +
/ \
/ \
1 + + 5
| |
2 + |
\ |
3 +---+ 4
A possible (no-overlapping) triangulation would be:
0,1,2 0,2,3 0,3,4 0,4,5
Does that already look like an algorithm? :)
for i = 0 to numVertices-2
drawTriangle v(0), v(i), v(i+1)
This always works as long as your polygon is convex (which it is).
So you were pretty close actually :D
Hey :)
QuoteDoes that already look like an algorithm? Smiley
hell yeah ^^
Well, I putted all this in my mind and I coded ... well, erm..
the point is ...
Well, It seems like it don't work lol
I set down the FOV to see where the triangles were chopped but it seems like they are not at all chopped :/ However, the triangles Count is much higher so there do are chopped triangles (much more : ~400 without chopping and 800/900 when chopped o_O)
I join the code + exe, you can move the camera with keyboard arrows. Once again, the perspective is deformed but it's normal, I set down the FOV to see the "borders" of the clipping.
I guess there is a problem in my perspective transformation so the x/z clipping doesn't work ?
here is just the perspective transformation routine :
'' projection
for j as integer = 0 to 2
obj.Triangle(i).Vertex(j).ViewPos.x = obj.Center.x + obj.Triangle(i).Vertex(j).pos3d.x - Camera.pos.x
obj.Triangle(i).Vertex(j).ViewPos.y = obj.Center.y + obj.Triangle(i).Vertex(j).pos3d.y - Camera.pos.y
obj.Triangle(i).Vertex(j).ViewPos.z = obj.Center.z + obj.Triangle(i).Vertex(j).pos3d.z - Camera.pos.z
zDiv = FOV/(TriangleList(i).Vertex(j).ViewPos.z)
TriangleList(i).Vertex(j).pos2d.x = xRes*.5 + TriangleList(i).Vertex(j).ViewPos.x * zDiv
TriangleList(i).Vertex(j).pos2d.y = yRes*.5 - TriangleList(i).Vertex(j).ViewPos.y * zDiv
nextSome posts ago, you spoke about [-1;1] coords after Camera space transformation (Which is sorta the Vertex.ViewPos = triangle.center+vertex-Camera stuff you see in the code above, I have not implemented the camera rotation yet) but in my code, it's not in [-1;1] ..
In fact I'm starting to wonder seriously if I'm not totally lost between the camera space transformation, the 2d projection and the clipping ..
Of course, I'm not asking for you to look at the code in the zip, it's just to give some news. Still, if you see something wrong in the perspective code I posted, please tell me :)
thanks again .. and sorry for all those repeating questions :/
ps : Anyway, If I really don't manage to handle all those problems (Clipping, perspective correct mapping which still don't work, Code not well organized, ..), I may start again from scratch and force myself to use matrices transformation and organize way better the code.
There's a logical error in your "Clip" function.
The principle is to take the input polygon (triangle) and clip it against the first clipping plane.
This results in a new polygon (vertices stored in "DestList").
Then you take the *new* (partially clipped) polygon and clip it against the next plane (and so on).
At the moment you're always clipping the *source* triangle and somehow collect all resulting vertices in "DestList".
Doesn't really make sense.
Your usage of "FOV" is a bit obscure to me.
You should try to understand that the field-of-view (commonly known as "zoom") is nothing else than a 2D (x,y) scaling in camera-space.
I expect that this scaling (including aspect-ratio, too) is already applied (contained in the transformation-matrix) when entering clipping. The clipflag-test assumes that the frumtum-planes are at x=z / y=z:
I'm not transforming the view-frustum according to the fov (which would result in arbitrary clipping planes and a more expensive clip-test). Instead I use a default frustum (which matches a fov of 90 degrees) and transform the vertices accordingly (they need to be transformed anyway).
I recommend to completely remove "FOV" and simply integrate it into the transformation:
obj.Triangle(i).Vertex(j).ViewPos.x = (obj.Center.x + obj.Triangle(i).Vertex(j).pos3d.x - Camera.pos.x) * zoom
obj.Triangle(i).Vertex(j).ViewPos.y = (obj.Center.y + obj.Triangle(i).Vertex(j).pos3d.y - Camera.pos.y) * zoom * aspectratio
obj.Triangle(i).Vertex(j).ViewPos.z = (obj.Center.z + obj.Triangle(i).Vertex(j).pos3d.z - Camera.pos.z)
Since you clipped your polygons in normalized space (-1..+1), you must also scale them to screen-size when performing perspective transformation:
zDiv = 1.0 / TriangleList(i).Vertex(j).ViewPos.z
TriangleList(i).Vertex(j).pos2d.x = xRes*.5 + TriangleList(i).Vertex(j).ViewPos.x * zDiv * xRes*0.5
TriangleList(i).Vertex(j).pos2d.y = yRes*.5 - TriangleList(i).Vertex(j).ViewPos.y * zDiv * yRes*0.5
hey :)
ok, I see the point, I changed the FOV in zoom and aspect ratio and "putted" them in the camera space transformation. I understood the point with the logical error too. big k++ to you for all your help !! I continue to work on it and I'll post stuff once there'll be new stuff :)
thanks again !!
It may not be there yet Hezad but the speed increase already is nice, it was running at around 20fps before, it's not at about 36fps (default zoom), it gets much faster if I zoom it out and crashes if I zoom it in ;)
hehe cool for the speed increase :P And 'glad to see you back shockwave :D
Some news : I'm almost there !!
After days of trying to find a bug making my program to continue to crash, I think I got something ! Now the program don't crash anymore, and it even looks like the Left plane clipping works perfectly. Here is the program if you wanna test (You can still move with the keyboard arrows) :D
Well, there are still glitches with the bottom, right and top clipping .. But I'm glad, I thought I were really stuck here :p
EDIT :
Another good new !! I fixed the Perspective correct mapping bug, now the texture mapping works perfectly :D It looks ... so much better hehe ! The attached file is a version without this correction, i'll post this version later. Just have to finish the clipping, to code the backface culling and to optimize/reorganize a bit the code and then ... Mip Mapping ! :D (or maybe the camera handling before :p)
You're really making great progress!
I'm just wondering why you sometimes start your arrays at index 0 and sometimes at index 1 ?
uhuh I edited my message at the same time ^^
thanks !
Concerning the arrays, some are starting at index 1 because I used an iteration variable the fill the array which starts to be incremented before the assigning values to the array.
eg : Iter+=1 : Array(iter) = stuff
so the array is used from 1 instead of 0 :P
Quotesome are starting at index 1
and you don't find that confusing?
Well ... lol not really, I'm pretty habituated, I do it all the time (yeah it's not a good habit :s ..)
hum.. But it'd be a good thing to change this habit .. let's start right now :p
Hi again folks :)
well, I know capital letters are not really welcome on boards but .. this time it's so awesome ..
CLIPPING IS NOW FULLY WORKING !!!! YEYHOO !
Dudes, I don't know how many hours I spent to find what wasn't working, I was almost desperate about it ^^ So it is really, really a liberation to see those cubes moving without any glitches and at ~15 FPS :updance: :updance: :updance:
Oh and like I said in a previous recent post, I found what wasn't working in perspective mapping too ! So The clipping works, but the perspective correct texture mapping also works perfectly (on my computer though .. your turn to say me if it works on yours :D )
Once again, thanks A LOT to hellfire who spent a lot of time to help me with clipping.
Well done! (http://www.c-plusplus.de/forum/images/smiles/live.gif)
Runs at ~30fps (Intel Core2 3Ghz) and ~20fps (Core2 2Ghz), I guess you can easily make it 60 without even touching assembler.
But before you get your hands dirty in optimization, please take some time to think about conception and class design.
look great....run 25FPS ( mine is E6750) The checkboard look great.
welldone
:cheers:
hellfire :
thanks :D So what would you advise me ? Using kind of a pipeline ?
eg :
- Objects Declaration (User Side)
- Objects Characteristics [texture, shader, rotation, translations] (User Side)
- Store everything in a Global Render List (Engine side)
- Calculate Shaders (Engine Side)
- Calculate Clipping + Projection (Engine Side)
- Render
and/or do you speak about totally changing the way I use my UDT's ?
Concerning the modularization, I thought about putting all engine subs in a .bi file and putting all the objects creation helping subs in another one.
I remember you spoke about materials but I thought I can create functions that create textures from procedural materials and a special type which could handle materials layers handling
e.g. in pseudo code :
Type Single_Material
Tex as Texture
Opacity as integer
MapType as integer '' (BumpMap, TextureMap, ColorMap, etc?)
End Type
Type Material
Layers(NBLAYERS) as Single_Material
End type
CurrentObject = Apply_Material(Current_Material)
And then using the MapType variable before rendering to know which kind of Material to calculate.
Don't know if it's a good idea though :p
HotShot :
thanks :D
Works really nice here too, no clipping bugs at all.
When Hellfire talks about classes he does mean types, yep. I will suggest once you have that implimented that you add backface culling and mip mapping which will give you a further speed boost.
It looks bloody good thought Hezad.
Fair play to you.
Some notes:
In software-rendering, poly- and pixel-throughput are precious, so you'll usually have many special-case-optimized polyfillers.
In my renderer I used a "Material" class which creates all required vertex-data from a mesh and contains the polygon-rasterization-code.
The 3D-models (or complete scenes including animation) are usually loaded from files (exported from your favourite 3d-package).
For testing & debugging reasons my "Mesh" class has some static methods to create some simple objects like cube, cylinder, sphere or torus:
Mesh *mesh= Mesh::createTorus(100, 40);Here "Mesh" is one of several object-types (all derived from one base-class) that are stored in a scene-tree (the hierarchical structure might not be required for you yet) and basically contains all information that can be applied by the 3d-software.
To get things rendering, I just need to assign it to a material:
Material *mat= new TextureGouraud(scenetree, Texture("texturefilename") );
mat->addMesh(mesh);
Here "Texture" can either be a bitmap loaded from a file or something procedurally generated (it just needs to derive from the same interface-class).
For rendering I just parse the material-list (stored in the scene-tree) and draw everything that got assigned.
The material has a "transform"-method to put the object where it belongs (multiply vertices by mesh' and camera's matrix) and to create all vertex-attributes; here: calculating rgb-values from vertexnormals and lights (which are also stored in the scene-tree, accessible by the material).
When that's done, polygon's are culled, clipped (each material has a vertex-class with a method for interpolation) and drawn.
For animation, each object has several animation-tracks which store position, rotation, scaling at certain timestamps (and interpolate in between).
"Procedural" animation (like deforming vertices) is job of the "Material", too (since it resambles the vertex-shader part).
Other approaches would just manually updated the mesh' transformation-matrix.
QuoteWhen Hellfire talks about classes he does mean types
True. "Classes" were mentioned a couple of times in the freebasic documentation, but I just realized they don't exist: :-\
QuoteThis keyword is supported in the latest fbc compiler, however, its usefulness is limited since classes and inheritance are not yet supported.
This is a knockout for most structured design strategies...
Hey :)
Shockwave :
QuoteWorks really nice here too, no clipping bugs at all.
When Hellfire talks about classes he does mean types, yep. I will suggest once you have that implimented that you add backface culling and mip mapping which will give you a further speed boost.
Glad to hear it works :) Backface culling was implemented just after the previous release :) Mip-Mapping is on the todo list, right after the pure code optimization part :) I'll start optimization with using Integers instead of singles most of the time. Then, I'll read once again all the thread to remind myself stuff people told me about optimization but forgotten ;D and then, MIPMAPPING :D
Quote
It looks bloody good thought Hezad.
thanks a lot shockwave :cheers:
hellfire :
QuoteIn software-rendering, poly- and pixel-throughput are precious, so you'll usually have many special-case-optimized polyfillers.
In my renderer I used a "Material" class which creates all required vertex-data from a mesh and contains the polygon-rasterization-code.
The 3D-models (or complete scenes including animation) are usually loaded from files (exported from your favourite 3d-package).
For testing & debugging reasons my "Mesh" class has some static methods to create some simple objects like cube, cylinder, sphere or torus:
Code:
Mesh *mesh= Mesh::createTorus(100, 40);
Here "Mesh" is one of several object-types (all derived from one base-class) that are stored in a scene-tree (the hierarchical structure might not be required for you yet) and basically contains all information that can be applied by the 3d-software.
To get things rendering, I just need to assign it to a material:
Code:
Material *mat= new TextureGouraud(scenetree, Texture("texturefilename") );
mat->addMesh(mesh);
Here "Texture" can either be a bitmap loaded from a file or something procedurally generated (it just needs to derive from the same interface-class).
Thanks for that, I just implemented a start for a material UDT which I can edit in a similar way of OpenGL surface creation (I'll put here an example at the end of the post). Added a naive bmp loader for textures too. I also replaced the objects creation functions with Object Type subs.
QuoteFor animation, each object has several animation-tracks which store position, rotation, scaling at certain timestamps (and interpolate in between).
"Procedural" animation (like deforming vertices) is job of the "Material", too (since it resambles the vertex-shader part).
Other approaches would just manually updated the mesh' transformation-matrix.
Animation is not planned for now :p But I didn't know I can use materials to implement it :)
QuoteQuote
When Hellfire talks about classes he does mean types
True. "Classes" were mentioned a couple of times in the freebasic documentation, but I just realized they don't exist: Undecided
Badly true :P But Freebasic's UDTs permits already some interesting stuff (Type Subs/functions, Constructors, destructors, properties, Operators), it's totally sufficient for what I wanna do now :)
QuoteThis keyword is supported in the latest fbc compiler, however, its usefulness is limited since classes and inheritance are not yet supported.
This is a knockout for most structured design strategies...
Well I guess so, but since I don't really know all classes stuff, UDT's are fine for me now ^^
Engine News :
Before starting optimizations, I wanted to modularize and reorganize a bit my code to help comprehension. So here are the news :
- Modularization : code broken into smaller header files (Engine.bi, DataTypes.bi, Materials.bi)
- Texture BMP loader
- Backface Culling
- /!\ New material Type to handle .. well, materials :p
+ Effective way (I hope) to create/edit materials in code
- Texture tiling routine
- Changed textures of the example for realistic ones
An example of material editing :
Dim as ObjectT MyObject
MyObject.GenerateCube(x0,y0,z0,SideSize)
Dim as TextureT CubeTex
CubeTex.LoadBMP "Texture_Cube.bmp"
Dim as MaterialT CubeMaterial
CubeMaterial.Edit
Mat_Shader GOURAUD_SHADER
Mat_Texture CubeTex,tileX,tileY
Mat_Diffuse r,g,b
Mat_ambient r,g,b
CubeMaterial.End_Edit
MyObject.Assign_Material CubeMaterialAnd a screenshot with the new textures attached
Everything sounds plausible, I'm just wondering what this does:
Quote- Texture tiling routine
Usually you would make sure your texture-width/height is a power of two, scale your uv-coordinates and and mask the appropriate bits ("and" instead of modulo).
Quotea material UDT which I can edit in a similar way of OpenGL
It's a good idea to follow standards, but remember that OpenGL's material-model was build around sgi's hardware-capabilities and is sometimes rather unflexible and you'll often want to take some shortcuts in order of optimization.
The important point is to offer the relevant features required to achieve certain effects.
If you want to get some ideas about how to use materials & lighting professionally, I can recommend this book:
(http://pixhost.eu/avaxhome/gfxworld/2006-12-06/1562059548.jpg) (http://www.3drender.com/light/index.html)
QuoteEverything sounds plausible, I'm just wondering what this does:
Quote
- Texture tiling routine
Usually you would make sure your texture-width/height is a power of two, scale your uv-coordinates and and mask the appropriate bits ("and" instead of modulo).
I multiply (U,V) values of each vertex by NbTileX and NbTileY and then, while rendering, I check if final(u,v) is > 1. If it's the case, I do this :
if ValU>1 then ValU = ValU-int(ValU)
if ValU<0 then ValU=0
if ValV>1 then ValV = ValV-int(ValV)
if ValV<0 then ValV=0It gives me ValU after int(ValU) repetitions.
QuoteIt's a good idea to follow standards, but remember that OpenGL's material-model was build around sgi's hardware-capabilities and is sometimes rather unflexible and you'll often want to take some shortcuts in order of optimization.
The important point is to offer the relevant features required to achieve certain effects.
Well, in fact I asked myself "what would I like to do for editing a material when coding with a 3d library ?" and I thought about this way. I have to admit I really didn't think about standards, This engine is not supposed to follow the standards since it surely won't be used excepted by myself ^^
QuoteIf you want to get some ideas about how to use materials & lighting professionally, I can recommend this book:
Some ideas, yep ! But I really don't want to code a professional product, I'd be incapable of doing that :p Just a personal learning tool and -hopefully- a nice gadget tool for people demos and/or code wanderings in the future :) But if I plan to buy a 3d book, I'll firstly look for this one :)
Quote from: Hezad on September 25, 2008
QuoteIf you want to get some ideas about how to use materials & lighting professionally, I can recommend this book:
Some ideas, yep ! But I really don't want to code a professional product, I'd be incapable of doing that :p
I don't know about that.. All you need to do is what you've done. Break it down into bits and work on it a bit at a time, even appreciating that it's not a professional product you would surely find some useful things in that book to help you improve the engine even more :)
Quote from: Hezad on September 25, 2008
But if I plan to buy a 3d book, I'll firstly look for this one :)
Enter a competition and win it and I'll buy the book for you :)
The prize fund for the remakes comp is £50 because it's joint sponsored by me and Rbz :) Normally they are half that amount.
Quoteeven appreciating that it's not a professional product you would surely find some useful things in that book to help you improve the engine even more Smiley
Yeah i'm sure about it :) I just wanted to underline the fact that my objectives are not professionals at all : They are just "let's make something beautiful (hopefully) and functional".
It doesn't change the fact this book looks bloody interesting :P
QuoteEnter a competition and win it and I'll buy the book for you Smiley
The prize fund for the remakes comp is £50 because it's joint sponsored by me and Rbz Smiley Normally they are half that amount.
:D Neat idea ! I'm not sure I will enter the comp since I don't have a good demo-scene culture, but i'll look for demos not remade yet ; maybe there will be one I want to work on ^^
Hey :) It's me again !
I'm trying to write a GenerateSphere Procedure ; having a curved surface is pretty useful for testing shaders/lighting ^^ (yeah, I wanted to relax a bit before entering the frightening world of optimization :D)
The routine works almost well : the sphere shape is well generated, the U,V values too. But there's a problem with the shading ! I use the Gouraud shader and some parts are ok, but some other parts looks like it was shaded with a flat shader :S or worse : it can look like the vertices color are inverted on some triangles (a dark vertice becomes bright and vice versa). Here is a screenshot to illustrate what I'm saying.
Here is the procedure :
Sub ObjectT.GenerateSphere(x0 as integer, y0 as integer, z0 as integer,Radius as integer,nbfaces as integer)
'' x = r sin(theta) cos(phi)
'' z = r sin(theta) sin(phi)
'' y = r cos(theta)
this.Center.x = x0
this.Center.y = y0
this.Center.z = z0
dim as single dTheta, dPhi,UAng,Vang,UAng2,Vang2
dim as integer ActualTri = 1
dTheta = PI/nbFaces : dPhi = PIx2/nbFaces
for theta as single = 0 to PI step dTheta
for Phi as single = 0 to PIx2 step dPhi
Uang = phi/PIx2
Vang = Theta/PI
Uang2 = (phi+dPhi)/PIx2
Vang2 = (Theta+dTheta)/PI
if theta<>0 and theta<>PI then
triangle(ActualTri).Vertex(0).pos3d.x = Radius * sin(theta) * cos(Phi)
triangle(ActualTri).Vertex(0).pos3d.y = Radius * cos(theta)
triangle(ActualTri).Vertex(0).pos3d.z = Radius * sin(theta) * sin(phi)
triangle(ActualTri).Vertex(0).U = Uang
triangle(ActualTri).Vertex(0).V = Vang
triangle(ActualTri).Vertex(1).pos3d.x = Radius * sin(theta+dTheta) * cos(Phi)
triangle(ActualTri).Vertex(1).pos3d.y = Radius * cos(theta+dTheta)
triangle(ActualTri).Vertex(1).pos3d.z = Radius * sin(theta+dTheta) * sin(phi)
triangle(ActualTri).Vertex(1).U = Uang
triangle(ActualTri).Vertex(1).V = Vang2
triangle(ActualTri).Vertex(2).pos3d.x = Radius * sin(theta) * cos(Phi+dPhi)
triangle(ActualTri).Vertex(2).pos3d.y = Radius * cos(theta)
triangle(ActualTri).Vertex(2).pos3d.z = Radius * sin(theta) * sin(phi+dPhi)
triangle(ActualTri).Vertex(2).U = Uang2
triangle(ActualTri).Vertex(2).V = Vang
end if
ActualTri +=1
If Phi <> PIx2 then
triangle(ActualTri).Vertex(0).pos3d.x = Radius * sin(theta) * cos(Phi+dPhi)
triangle(ActualTri).Vertex(0).pos3d.y = Radius * cos(theta)
triangle(ActualTri).Vertex(0).pos3d.z = Radius * sin(theta) * sin(phi+dPhi)
triangle(ActualTri).Vertex(0).U = Uang2
triangle(ActualTri).Vertex(0).V = Vang
triangle(ActualTri).Vertex(1).pos3d.x = Radius * sin(theta+dTheta) * cos(Phi)
triangle(ActualTri).Vertex(1).pos3d.y = Radius * cos(theta+dTheta)
triangle(ActualTri).Vertex(1).pos3d.z = Radius * sin(theta+dTheta) * sin(phi)
triangle(ActualTri).Vertex(1).U = Uang
triangle(ActualTri).Vertex(1).V = Vang2
triangle(ActualTri).Vertex(2).pos3d.x = Radius * sin(theta+dTheta) * cos(Phi+dPhi)
triangle(ActualTri).Vertex(2).pos3d.y = Radius * cos(theta+dTheta)
triangle(ActualTri).Vertex(2).pos3d.z = Radius * sin(theta+dTheta) * sin(phi+dPhi)
triangle(ActualTri).Vertex(2).U = Uang2
triangle(ActualTri).Vertex(2).V = Vang2
end if
ActualTri +=1
next
next
this.nb_triangles = ActualTri
End sub
Any idea of what could make this procedure to bug ?
Thanks :D
It looks flat shaded tbh, it's probably a problem of normals.
Unit normals for a sphere are easy to generate fortunately, just generate a sphere with a radius of 1 for them :)
I'll try to calculate the normals by myself while generating the sphere then. But those normals should be okay since I have a procedure which precalculate automatically all normals :p
thanks for the advice anyway, I'll try it :)
Hazad, you are creating 3 independant vertices for each triangle.
Later you're calculating the per-vertex-normal by averaging the normals of all triangles adjacent to the a vertex.
But there is just one triangle (because there are no shared vertices) and it's the same for each vertex of triangle.
So you got exactly the same normal for each vertex of a face - which results in perfect flatshading :)
You probably do not want to store three (unshared) vertices per triangle, but instead store each vertex only once and, additionally, create a triangle-table storing three indices into the list of shared vertices.
This will save you quite a lot of transformation-work because in the average case a mesh has only half as many vertices as triangles.
Since you're now creating 3 vertices per triangle, you have six times as many vertices as are actually required...
Example:
Vertices:
Vector vertices[12]= {
0, 32.647, 0,
29.2, 14.6, 0,
9.0233, 14.6, -27.771,
-23.623, 14.6, -17.163,
-23.623, 14.6, 17.163,
9.0233, 14.6, 27.771,
23.623, -14.6, -17.163,
-9.0233, -14.6, -27.771,
-29.2, -14.6, 0,
-9.0233, -14.6, 27.771,
23.623, -14.6, 17.163,
0, -32.647, 0
};
Faces:
int triangles[20*3]= {
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 5,
0, 5, 1,
1,10, 6,
2, 6, 7,
3, 7, 8,
4, 8, 9,
5, 9,10,
6, 2, 1,
7, 3, 2,
8, 4, 3,
9, 5, 4,
10, 1, 5,
11, 7, 6,
11, 8, 7,
11, 9, 8,
11,10, 9,
11, 6,10
};
Hey :)
I totally understood this :
QuoteYou probably do not want to store three (unshared) vertices per triangle, but instead store each vertex only once and, additionally, create a triangle-table storing three indices into the list of shared vertices.
thanks to the examples, and I like it :p
But there's something I don't understand :
QuoteLater you're calculating the per-vertex-normal by averaging the normals of all triangles adjacent to the a vertex.
But there is just one triangle (because there are no shared vertices) and it's the same for each vertex of triangle.
Why is there no shared vertices ? I mean, even if I don't use a vertex list/triangle table for now, there's a routine in my precalc_normals procedure which finds which vertex is shared with another (The way I do this is surely not the best way to do it, but I was expecting it to work at least :p)
And a last question, about the vertex list and the triangle table : Do you recommend me to use this approach in all the engine ? Or just while generating objects ? Because using it all along the code scares me a bit : I should change almost all the code when it comes to vertex handling !
My mistake, in my explanating I forget that you don't have indices at all.
In your
PreCalc_Object_Normals function you check for each vertex of a triangle, if there's any other triangle with the same vertex-position. In theory this should work - but it doesn't because in
SamePnt you're comparing two floating-point values for
equality - which fails.
That's because the fpu internally calculates at a precision of 80bit while you're storing values at single(32bit) precision, so you can assume every assignment to a variable as rounding.
Reordering two iteration of your loop results in something like this:
u1 = round (phi/PIx2)
u2 = round ((phi+dPhi)/PIx2)
phi= round (phi+dPhi)
...
u3 = round (phi/PIx2)
Here
u1 and
u3 are not equal because of different rounding (the difference is rather small though).
That's why you can't find any adjacent triangle when calculating the normals.
The usual aproach for comparing floating-point values is something like this:
if abs(a-b)<EPS then equalQuoteabout the vertex list and the triangle table:
Do you recommend me to use this approach in all the engine ?
Absolutely yes because you have much few vertices to transform and require less memory.
When you're at some point going for 3d-acceleration, this approach is also supported in hardware (index-buffers).
Another thing I noticed is that your vertex-structure is rather big. I recommend to split it into multiple arrays because you're now piping a lot of useless data through the cache when accessing a vertex (the polyfiller for example is not interessted in untransformed 3d-coordinates or normals).
rah supa k++ to you ^^ it works perfectly ! thanks a lot for finding the problem. I didn't know this stuff about rounding, I'll remember that !!
QuoteAbsolutely yes because you have much few vertices to transform and require less memory.
When you're at some point going for 3d-acceleration, this approach is also supported in hardware (index-buffers).
Another thing I noticed is that your vertex-structure is rather big. I recommend to split it into multiple arrays because you're now piping a lot of useless data through the cache when accessing a vertex (the polyfiller for example is not interessted in untransformed 3d-coordinates or normals).
Noted, I'll work on that then :)
Nice image :)
I thought it was a normals problem, glad you got it sorted out :) Can't wait to see it working!
Hey :D Well, I just finished to implement Mip Mapping :D
QuoteNice image Smiley
thanks ^^ I hope you'll like this version !
Well it's not filtered yet (or yes it is, but just an average of 4 colors on each stage) and there is still some flickering (not much though, excepted on the checkered sphere, it's awful oO but I think it's a problem with my MipMap generation routine).
I decided to set version numbers from now on, it'll be easier (even for me) to see what's new and when. Oh and I already heard some complaints (not here ^^) about the fact I don't respect the version number norms but whatever :P
So, here is the changes list :D :
Quotev0.02 Changes List
MAJOR :
- Added MipMapping handling !
> Set_Mipmapping(ON/OFF) to set it.. well.. ON or OFF :D
> LOD bias constant for now
> No filtering yet (Bilinear planned)
- Added GenerateMipMap() procedure.
> Generates MipMap GFX or loads it if file exists.
- Added Light Fall Off coefficient.
> Permits Light intensity to fall off with distance between objects and lights.
- Added Object.GenerateSphere() procedure.
> GenerateSphere (x0, y0, z0, Radius, Subdivisions)
Minor :
- TSLine and GLine subs replaced with macros.
- Some minor optimization.
I should work on optimization and on the vertex list / triangle table hellfire spoke about but I don't know yet how to start (I tried the vertex list/triangle table, but when I do this, I lost all information about U,V coordinates, normals, etc.. but I'm thinking about it :) ).
And concerning pure optimization, I still have lot of work to do, I haven't converted singles to integers, and I surely have some divisions/multiplications I could precalculate ...
edit : This version crashes on some computers ..
It locked up here after opening the screen unfortunately mate.
Illegal array access? Drawing something off screen?
Argh, problems coming ^^
Hum .. Could you compile the code without using Mip mapping please ? (beginning of test_gouraud.bas, change Set_MipMapping ON to MipMapping OFF). If you can't compile, I'll compile a version without mipmapping.
QuoteDrawing something off screen?
Hum .. Well, normally, it shouldn't be possible thanks to clipping
QuoteIllegal array access?
or pointer.
That would be much more probable since the mipmap processing uses different textures sizes, it could use a wrong size and look for a color outside the pointer/array .. But now, why does it work for me then ?
thanks for feedback :)
edit : aargh wait a minute ! I just remembered the fact that the mipmap pointers are not deallocated !! I forgot :P It could be the problem source I guess ? I fix it and I repost the .rar here :)
I cant compile it unfortunately since I have an old version of FB here, but what you said about pointers sounds likely to be the cause.
Something else that you should look into are any new floating point stuff you've got in there too.
I once had a bug whereby I was trying to convert a floating point varialbe into a colour value eg;
rgb (float,float,float)
I hadnt realised they were floats and it was crashing, it took me a while to figure it out.
PCs are strange like that.
I can code super illegal shit on my notebook, writing past buffer limits etc and it just runs it happily. On here it dies straight away.
I'll test your program on the laptop in a while too :)
QuoteI cant compile it unfortunately since I have an old version of FB here, but what you said about pointers sounds likely to be the cause.
No problem, I'll compile a "without Mipmapping" version :)
QuoteSomething else that you should look into are any new floating point stuff you've got in there too.
I once had a bug whereby I was trying to convert a floating point varialbe into a colour value eg;
rgb (float,float,float)
I hadnt realised they were floats and it was crashing, it took me a while to figure it out.
PCs are strange like that.
I can code super illegal shit on my notebook, writing past buffer limits etc and it just runs it happily. On here it dies straight away.
ah computers.. :P I'll check for the floats and colors stuff :)
QuoteI'll test your program on the laptop in a while too Smiley
cool :) grazie ^^
Now, I think I have something concerning the bug .. I wrote a destructor for my texture type to deallocate all the previously allocated pointers at the end but strangely, it makes the program to crash. It must be a sign ><
Something I noticed in your source you are using reserved keywords for commands etc, eg color in your type, and also pos. It's a good idea to call them something else, as the compiler may act up on you.
Also not sure if this'll work.
#include "fbgfx.bi" ' for constants
I'd put the comment part above the include. Also I tend to use #Include Once "includefile_example.bi".
Also, im not sure if it matters and theres a default, for calls to subs, with using ByVal before your variable name and its definition. eg Declare Sub Yo( ByVal PosX As Integer, ByVal PosY As Integer ). As in older versions there was either ByVal ( By Value ) or ByRef ( By Reference ). not 100% sure if that's been done away with in the newer FreeBASIC version.
Hey :)
About the reserved keywords, I think they can be used in user defined types as variables but not as subs (unless you undef them). But you're right, I should have good habits :p
About passing variables ByVal or ByRef, I think its ByRef by default on FB 0.18.xx (the version I use), but since other people could compile with other versions, and since the default may evolve in the next versions, I should always specify ByVal or Byref (what I don't do everywhere actually)
These are reasonable advices and I'll follow them right now, it may help for debugging ;)
Concerning the comment on the include statement, I only use fbfgx.bi for the scancodes constants and for now I only use them in the test source file (and this part works).
Thanks for (having take ? taken ? taking? arg I don't know ><) a look in the source :)
No worries mate, my few pennies worth that may assist you dude.
I can run the binary but when i press esc, visual studio asks me to select a debugger ;D
When I recompile the source (with FreeBasic 0.2) it crashes right away,
when I disable mipmapping it works fine.
Error checking (-exx) reports:
QuoteAborting due to runtime error 12 ("segmentation violation" signal) in "materials.bi::LOADMIPMAP()"
Some notes about mipmapping:
What I've understood from a quick look at your source-code, you're now selecting a mipmap-level depending on the depth of a polygon.
Generally it's true that if a polygon is further away, it's also smaller and needs less texture-precision - but it's not a very clever approach (but some ps2 games use this way because the hardware doesn't support mipmapping and it's easy to do).
If you, for example, look from a steep angle at one side of a cube, this side is very narrow and only needs a low texture resolution - although the polygon might be very near to the viewer.
So let's assume you're interpolating texture-coordinates linearly (which is inevitable when you stop doing perspective divide for every pixel), a better approach is to look at the deltas for interpolating the texture-coordinates u & v across the scanline.
For example if one delta is >= 2.0 you'll always skip one texel when advancing to the next pixel. In such case you can savely use a mipmap with half the size.
For generating the mipmaps, you can simply scale the "current" mipmap to half the size on-the-fly (a simple 2x2 filter) so there's no reason to save all mip-levels.
hey hellfire :) thanks for the report !!! I tried to run with -exx but (I must be lame ^^) I never found the report :P
Okay, so it confirms what I was thinking, a segmentation violation. And in LoadMipmap. erm....
erm ...
*brain on fire* ^^
Okay I recap :
- A segmentation fault in LoadMipMap (so, surely pointer error or array error ... you have to know I use an array of pointers, in the new Texture Type (which handle MipMapping) in this way :
Texture.MipMap(NbLODS) as integer ptr
in which I keep the mipmaps image ptr
- A crash once the gfx screen opens (even on my computer this time) when I set the destructor of TextureT to destroy all the GFX contained in this type (Original size + MipMaps)
erm.. I continue to search in LoadMipmap :P
edit :
another thing you have to know, I store my images in integer ptrs. So to load bmps, since bload don't work with this kind of pointers as-is, I must do that :
Bload FileName, MipMap(CurrLOD)-8
If I get off the "-8", it don't work. It may come from here too since I use a cheat to load the bmps. But if it's the case, why did it work before and not now ?
Quotewhy did it work before and not now ?
Luck? ;)
.. In those cases, I totally hate luck ><
:P Well so I guess the next step is a custom Bmp Loader ..
In your mipmap code, I think you need
_pow2 = 1/(2^(CurrentLOD+1))
instead of
_pow2 = 1/(2^CurrentLOD)
And remove the -8 from the bload
And change this bit
MipMap(CurrLod) = Callocate(CurrX*CurrY-1+1000,Sizeof(integer))
I think the bload needs some more space for some reason.
Works for me then.
<edit>Look at the sample codes for bload and imagecreate
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgBload (http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgBload)
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgImagecreate (http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgImagecreate)
Seems you should use ImageCreate instead of Callocate and let FB worry about the image metadata size that it needs.
Jim
Ah ! Thanks a lot for that ! I'll upload the new exe with this post since I can't debug it at home (how to debug something which is working on your computer ?.. ;D)
strangely, getting off the "-8" on :
Bload fileN,this.GFX-8makes my program to crash but inversely, the one you specified :
Bload MIPMAP_FOLDER+filen+"_MipMap"+str(CurrLOD)+".bmp",MipMap(CurrLOD)-8doesn't make me crash if I get "-8" off.
Quote<edit>Look at the sample codes for bload and imagecreate
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgBload
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgImagecreate
Seems you should use ImageCreate instead of Callocate and let FB worry about the image metadata size that it needs.
Well in fact I totally agree, I should use ImageCreate with Any ptrs instead of Callocate with integer ptrs, but I started to code the engine using integer ptrs since I use them to directly write on the screen buffer. (And since the pointers returned with imageCreate have some headers and/or data before the image data, I don't know how to deal with them, I don't really want to use pset or put excepted on pre-rendering operations (like mipmap files writing))
Here are the new exes with those modifications (2 versions)
(too large to be attached here, links below)
- Engine With Mip Mapping (http://freefile.kristopherw.us/uploads/hezad/engine3d_with_mipmapping.rar)
- Engine without Mip Mapping (http://freefile.kristopherw.us/uploads/hezad/engine3d_with_no_mipmapping.rar)
In those versions, the GFX destructor is commented since it stills make me crash.
It's working pretty well now, but when you're approaching the checkered sphere from a distance, there's one point (very probably where you change from one mipmap to another) where the texture-coordinates "jump".
You have to get rid of all the -8 stuff. There's no logic behind it. You are effectively avoiding overwriting the end of your allocation and using -8 is making it overwrite the beginning instead. Neither of these is good news at all, and hence the random crashes on different PCs.
The problem is Bload not just loads the pixels but it stores some other data too, like the x,y size and bpp.
My suggestion is you use ImageCreate to create a temp image for Bload to load into, then copy the image pixels into the memory you've Callocated.
Jim
QuoteIt's working pretty well now, but when you're approaching the checkered sphere from a distance, there's one point (very probably where you change from one mipmap to another) where the texture-coordinates "jump".
thanks ^^
About the "texture jump", there's no filtering yet so it could explain that I guess. I plan to code a bilinear filter :)
QuoteYou have to get rid of all the -8 stuff. There's no logic behind it. You are effectively avoiding overwriting the end of your allocation and using -8 is making it overwrite the beginning instead. Neither of these is good news at all, and hence the random crashes on different PCs.
The problem is Bload not just loads the pixels but it stores some other data too, like the x,y size and bpp.
My suggestion is you use ImageCreate to create a temp image for Bload to load into, then copy the image pixels into the memory you've Callocated.
Jim
... OF COURSE !! arf why didn't I think about it ? it's a neat idea, thanks ! I'll code that tomorrow once I get up from bed ^^
It's a real treat to see this working now :)
It's a real treat to hear that ^^
Bload stuff is now fixed :D I'll post the next version once mipmap filtering is coded. I found a very useful link about little optimization tricks, I may also optimize a bit before the next version.
the link (lot of stuff for QB but lots of ideas useable with FB) :
http://www.phatcode.net/articles.php?id=232 (http://www.phatcode.net/articles.php?id=232)
An example :
I didn't know writing (a) instead of (b) was possible ! In fact it's pretty logical since logical operations return -1 if true and 0 if not in FB but I never thought about that :)
(a)
a=5
if a>2 then
b=6
end if
(b)
b=-(a>2)*6hellfire > I just noticed your edit in the previous page :
QuoteSome notes about mipmapping:
What I've understood from a quick look at your source-code, you're now selecting a mipmap-level depending on the depth of a polygon.
Generally it's true that if a polygon is further away, it's also smaller and needs less texture-precision - but it's not a very clever approach (but some ps2 games use this way because the hardware doesn't support mipmapping and it's easy to do).
If you, for example, look from a steep angle at one side of a cube, this side is very narrow and only needs a low texture resolution - although the polygon might be very near to the viewer.
So let's assume you're interpolating texture-coordinates linearly (which is inevitable when you stop doing perspective divide for every pixel), a better approach is to look at the deltas for interpolating the texture-coordinates u & v across the scanline.
For example if one delta is >= 2.0 you'll always skip one texel when advancing to the next pixel. In such case you can savely use a mipmap with half the size.
I was precisely wondering about the fact that my "Z-based" mipmapping was wrong, thanks for pointing me on the deltas, I'll work on that for the next release.
QuoteFor generating the mipmaps, you can simply scale the "current" mipmap to half the size on-the-fly (a simple 2x2 filter) so there's no reason to save all mip-levels.
Won't it be really slow to iterate through all texels and "2x2-filter" them in the main loop ?
QuoteWon't it be really slow to iterate through all texels and "2x2-filter" them in the main loop ?
What I meant is that there's no need to store the mipmaps to *disk* because generating the mipmaps at *startup* is pretty fast.
At the moment you're scaling the original texture to create each mipmap; instead you can simply scale the previous level to half the resolution.
Quotethanks for pointing me on the deltas
You're welcome :)
You also might want to consider creating anistropic mipmaps to take the ratio of the deltas into account.
This results in a matrix of textures with the "basic" mipmaps on the diagonal.
Example for a 256x256 map:
256x256, 256x128, 256x64, 256x32, 256x16, 256x8, 256x4, 256x2, 256x1
128x256, 128x128, 128x64, 128x32, 128x16, 128x8, 128x4, 128x2, 128x1
64x256, 64x128, 64x64, 64x32, 64x16, 64x8, 64x4, 64x2, 64x1
32x256, 32x128, 32x64, 32x32, 32x16, 32x8, 32x4, 32x2, 32x1
16x256, 16x128, 16x64, 16x32, 16x16, 16x8, 16x4, 16x2, 16x1
8x256, 8x128, 8x64, 8x32, 8x16, 8x8, 8x4, 8x2, 8x1
4x256, 4x128, 4x64, 4x32, 4x16, 4x8, 4x4, 4x2, 4x1
2x256, 2x128, 2x64, 2x32, 2x16, 2x8, 2x4, 2x2, 2x1
1x256, 1x128, 1x64, 1x32, 1x16, 1x8, 1x4, 1x2, 1x1
Looks like a lot of data at first, but "just" requires 4x as much space as the original texture.
QuoteWhat I meant is that there's no need to store the mipmaps to *disk* because generating the mipmaps at *startup* is pretty fast.
oh okay :P I misunderstood you ^^ but for now I must say I like the idea of storing files the first time, and loading them after. However, I can't really tell you why .. Just a preference.
Quoteinstead you can simply scale the previous level to half the resolution.
Done some minutes ago ^^ totally rewritten the GenerateMipMap sub.
QuoteYou also might want to consider creating anistropic mipmaps to take the ratio of the deltas into account.
This results in a matrix of textures with the "basic" mipmaps on the diagonal.
Example for a 256x256 map:
Yep I red about it :) Still, I'll start by trying to finish the "basic" mip-mapping before ^^
And about it, I already have a new question troubling me :S
Every generated textures mipmap seem okay (reduced, blurred) excepted ... the checker one !
It's pretty normal, the box filter iterate every 2 pixels and since each checker square has a side proportional to 2, every new mipmap keep the same geometry.
But it's a problem !!
Since it's not blurred at all (excepted for the 2x2 mip-map and 1x1 mip-map which are simply grey), the texture looks always the same, whatever the distance/angle/dU/dV is !! And therefore, the Moiré effect stays here (and it's terrible, especially on floor) but worse : the limit between 4x4 and 2x2 mip-maps in 3D world is awful !
Is thought about using a texture with 3 squares per side instead of 2 but
1) it's not tilable and,
2) Anyway, it's not proportional to 2, so it doesn't fit in any texture size ><
Now I'm really wondering, I saw Mip-mapped checkered floors everywhere on the web, how do they do ?!!
Must I use some kind of 4*4 box filter ? or even odd*odd box filter ?
I guess bilinear filtering would solve the problem but I'm pretty sure (but I can be wrong) I saw some of those checkered floors with good mip-mapping using box filter on the net ..
edit : the attached images may seems to be actually blurred, but it's the jpg compression which do that.
I must admit your question bothered me for a moment, but the mipmaps of your checker-texture are perfectly okay.
You are using a texture which basically consists of 2x2 pixels (just scaled up to 64x64).
It's no surprise that you reach exactly this 2x2 texture when scaling it down again.
In the distance you can imaging each pixel of your polygon being filled by a rather big area of your texture. All texels in this area are summed up to a single color. Mipmapping reduces the size of this area to 2x2 texels but still your texture only contains black and white pixels.
At this point the bilinear filtering sums up the weighted black & white areas.
Since you're not yet doing bilinear filtering and just pick the nearest of the 4 surrounding pixels (which is either black or white), you still get usual sampling-artifacts just as if there wasn't any mipmapping.
hum .. So I can't pass through bilinear filtering :D Working on it right now then :)
At some point your checker will go to grey. This is GOOD, because it's the way the sparkles are avoided when your object moves a tiny amount when it is very far away. If your scene goes grey where you don't want it to, then you're probably not calculating the mipmap level to use correctly. The 1 pixel grey texture shouldn't be used until your polygon is 1 pixel in size or mapping only 1 texel's worth of uv area.
You probably should bilinear filter your texture while calculating your mipmaps. But it's simple, every 2x2 in the source texture becomes 1 pixel in the destination one, so very simply you can just add them all together and divide by 4.
Sometimes it depends. I remember a problem I had with a texture which was supposed to be a chain link fence. At full size, it works fine, grey criss-cross with transparent holes. The next mipmap layer down though, it just went solid. So mipmaps are not for every texture, and if you use alpha for transparency or punch-through you might want to change the way the averaging/bilinear works.
Jim
QuoteBut it's simple, every 2x2 in the source texture becomes 1 pixel in the destination one, so very simply you can just add them all together and divide by 4.
Well that's exactly what I'm doing now ^^ And it's the reason the Checker doesn't blur himself while generating mipmaps :)
QuoteYou probably should bilinear filter your texture while calculating your mipmaps.
while generating ? Shouldn't I apply the filter while rendering the pre-generated mipmap ?
and while i'm here, sorry but I have *again* a question about the LOD. I wanted to use the texel deltas method to apply the correct mipmap. But whatever I do (I tried looots of different ways to access the dU and dV values since I interpolate U/z and V/z instead of U and V), the rendering is weird (especially on spheres, the flat surfaces seem almost okay). I can see I'm almost there, but it's still far from perfect.
I attach a screenshot of the colored LOD rendering : The greener, the higher is the LOD (Do you see how the LOD "spreads" on the middle sphere ?).
And that's the code I'm actually using to find the deltas :
dim as single LODCOEF = 128
dU = dU_z*tmp_Z*LODCOEF
dV = dV_z*tmp_Z*LODCOEFwith dU_z = (u/z2 - U/z1) / (x2 - x1)
and tmp_Z = 1/CurrentZ
(I tried several values for the LOD coef)
I also tried to replace tmp_Z by the z2 and z1 values and/or 1/z2 and 1/z1 values.
So, you have interpolated x, u/z, v/z, 1/z along the left & right edges of your polygon:
lx= left x-coordinate of your scanline
lu= u/z on left side
lv= v/z on left side
lz= 1/z on left side
rx= right x-coordinate of your scanline
ru= u/z on right side
rv= v/z on right side
rz= 1/z on right side
What you want to find is the delta from one pixel's perspective correct texture-coordinates to the next:
mipu= log2( lu/lz - (lu+deltau)/(lz+deltaz) )
mipv= log2( lv/lz - (lv+deltau)/(lz+deltaz) )
Now that's computationally a bit expensive to do per pixel.
So let's do things properly:
Split the scanline into pieces of, let's say, 8 pixels and calculate deltas to get from one set of (u/z, v/z, 1/z) to the one eight pixels further:
inv= 8.0 / (rx-lx)
du= (ru-lu) * inv
dv= (rv-lv) * inv
dz= (rz-lz) * inv
Calculate one pair of perspective correct texture-coordinates for the left and right side of the 8-pixel-span. Let's also make them integers (we multiply by 2^16 to keep some precision):
u1= lu/lz*65536.0
v1= lv/lz*65536.0
advance to the end of 8-pixel-span:
lu+=du
lv+=dv
lz+=dz
u2= lu/lz*65536.0
v2= lv/lz*65536.0
you can now interpolate linearly across the coordinate-pair:
ldu= (u2-u1) shr 3
ldv= (v2-v1) shr 3
(beware of freebasic's shift function)
here you can read the mipmap-level from the upper-word of the deltas (find fast ways for log2 here (http://www-graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup)).
Adjust the texture-coordinates depending on the miplevel (level 0 is the original texture).
Texture-tiling is for free with a simple bitmask.
masku= (1 shl (log2(texture_width) - miplevel))-1
maskv= (1 shl (log2(texture_height) - miplevel))-1
texshift= log2(texture_width) - miplevel
for x=0 to 7
u= (u1 shr (16+miplevel)) and masku
v= (v1 shr (16+miplevel)) and maskv
dest(x)= mipmap[ (v shl texshift) + u ]
u1+=ldu
v1+=ldv
(all without warranty)
Rah how could I thank you for your implication ?! Thank you so much for all that. I didn't understand everything from the tiling part but don't worry, i'll work on that :)
Concerning the Log2 calculus algorithm, I guess it'd be preferable to use the one for evenly distributed inputs values ? Anyway, the listing says :
if (tt = v >> 24)
{
r = 24 + LogTable256[tt];
}
else if (tt = v >> 16)
{
r = 16 + LogTable256[tt];
}
else if (tt = v >> 8)
{
r = 8 + LogTable256[tt];
}
else
{
r = LogTable256[v];
}
but tt is not defined before.. it's declared as a "register unsigned int" so I guess the "register" thing has to do with that since it can't be used if always equaling 0 ..
"register" is a c++ keyword which proposes to keep the variable in a register.
Modern compilers are usually clever enough to decide that on their own ;) so just make "tt" an integer.
Notice that the expression inside the if() is an assignment and not a compare:
tt = v shr 24
if t<>0 then
...
okay thanks :) I have lots to learn about C/C++ lol ...
I thought
if (tt = v >> 24)
{ ...
were equivalent to
if tt = v shr 24 then ...
instead of
tt = v shr 24
if t<>0 then ...
><
Hey :D Just a quick post since I'm in a hurry. Here is a temp release of the Engine with the Mipmapping finally working thanks to hellfire.
The optimization brought is not implemented yet, I wanted to try quickly the "expensive" method before to ensure I wasn't making any mistakes with the rest of the code. That's why this release is a "temp" one. The real 0.03 will have the optimizations hellfire spoke about and bilinear filtering. So here it is :)
Changes list :
Quotev0.03 temp Changes List
MAJOR :
- Mip Mapping fixed, now totally workind !
> A gigantic thanks to hellfire (from dbf/gvy boards) which gave me the
> code to find the current Level Of Details to use.
- Image loading (bmp loading with Bload) is fixed.
> No more weird tricks with bload
- Added Render_Option()
> Normal, Mipmap levels or Zbuffer
- GenerateMipMap() total rewrite
> More efficient
Minor :
- Fixed a little bug in Z-buffer handling.
- More precise FPS calculus in the example source.
To Do next :
- LOD determination optimization
- bilinear filtering
- fixing some undetermined bug(s) in the clipping function.
The rewrite worked nice and you'll be pleased to know that it did not crash here :)
It looks lovely actually and great that you added som options during the demo to turn things off an on, you can see straight away the mipmapping makes a nice difference to the quality :)
QuoteThe rewrite worked nice and you'll be pleased to know that it did not crash here Smiley
I am ! ;)
QuoteIt looks lovely actually and great that you added som options during the demo to turn things off an on, you can see straight away the mipmapping makes a nice difference to the quality Smiley
thanks :) I hope the bilinear filtering will add more quality to the rendering, but I'm a bit afraid of the FPS fall it will cause ..
I did some work on bi linear filtering a while ago and posted a little about it here: http://dbfinteractive.com/forum/index.php?topic=2285.0 there's a link to a bit about efficient blending there too.
EDIT:
There's also a little bit about the bleeding edges and clamping which can be useful for the filtering here: http://dbfinteractive.com/forum/index.php?topic=2342.0 although it's in c++.
Yey ! Awesome trick :D thanks for sharing it, it'll definitely be useful :)
QuoteThere's also a little bit about the bleeding edges and clamping which can be useful for the filtering here: http://dbfinteractive.com/forum/index.php?topic=2342.0 although it's in c++.
Well I have to admit I haven't understood all the vocabulary used in this thread :P I know (I think :p) what are bleeding edges, but clamping ? A literal translation didn't help :)
The edge bleeding is where the pixels from the opposite edge of a texture are used in part of the filtering, clamping is used to stop the pixels from the opposite edge being used.
if you have a 16*16 texture and you read from coords 15.3,12.7 then you'd read from 4 texels (15,12) (16,12) (15,13) (16,13)
but the 16 wraps around to 0 and reads from the other side of the texture so you'd really want anything over 15 to still equal 15.
okay, got it, thanks :D
Quoteclamping is used to stop the pixels from the opposite edge being used.
In case your texture is tilable it's totally valid to adress texel from the opposite side when filtering.
In the other case (usually needed for projective textures or lightmaps) I usually made my texture a pixel bigger in each direction, so that the texel (u+1,v+1) is always present and I don't have to handle special cases; or I make sure that the right/bottom-boundary texel will never be addressed by the uv-coords.
Update :D
So this is the v0.04 and there are lots of new stuff in there ! Stuff like camera handling, new tore object, etc ... But I let you see the changes list :
Quotev0.04 Changes List
MAJOR :
- Big Optimization with the Shaders calculations.
> Shaders are now calculated only on visible triangles
- Big Optimization while computing mipMap level
> MipMap level is now calculated only once per scanline
- Camera Rotation handling.
> Camera can now rotate according to Y axis and X axis
- Added Init_Engine Sub.
> User can now choose desired resolution
- Added GenerateTore Sub.
> object.GenerateTore(x0,y0,z0,r1,r2,nbfaces)
- material Ambient is modified when Diffuse is modified.
> Can still modify the ambiant after the diffuse modification.
Minor :
- Camera can now translate in world space or in View space
> Move_Camera X,Y,Z, SPACE (IN_VIEWPOS or IN_WORLD)
- Fixed a bug with the Z-Buffer handling
- Fixed a bug in the untextured rendering
You can still move with the keyboard arrow keys but Left and right now handle the camera handle.
Filtering is still not done though, and I hope I will have some motivation to code it very soon (I can't stand the moiré patterns appearing on the checkered floor, even with mip-mapping)
You can DL it here : Download (http://freefile.kristopherw.us/uploads/hezad/engine3dv0_04.rar)
Quote
Todo List :
- real time texture bilinear filtering
- simple obj loader (vertices/faces, UV coords, normals)
Wonderings :
- Implementing phong shading ?
- Is there a way to do fast super sampling or any type of FSAA without Open GL/direct X ?
- Implementing environment mapping ?
I cant really notice any speed improvement, it's still about the sam as before but.. It has much nicer colours and the ability to be able to spin the camera to look around makes a huge difference to this.
The torus is nifty too :)
Please do keep finding the motivation to improve it more and more :)
Every now and again we have one of these threads, someone will have a go at textures, raytracing or something, this has been one of the best threads of this sort we've had. People will learn a lot from it.
Well done!
QuoteImplementing phong shading ?
It's easy. Instead of calculating lighting per vertex and interpolating the resulting rgb-components, you interpolate the xyz-components of your normal-vector and apply the vertex-lighting per pixel.
But you won't use it because it's just too slow.
Better:
QuoteImplementing environment mapping ?
There are lots of ways to do it (spherical, cubic, dual-parabolic) and even several representations of spherical coordinates.
Do you have specific questions?
QuoteIs there a way to do fast super sampling
Accelerators go the brute-force way: render at higher resolution and afterwards sample down to the original resolution; slow.
Another method is to find the siluette edges and render those as anti-aliased lines; this requires transparency, thus sorting polygons and trouble with the zbuffer.
None of these is really fun.
@shockwave
QuoteI cant really notice any speed improvement, it's still about the sam as before
lol I gained at least 3 FPS and since it was running at ~9/10 FPS, I really have a gain in speed ^^ But maybe my computer is too old, idk :)
QuoteIt has much nicer colours and the ability to be able to spin the camera to look around makes a huge difference to this.
The torus is nifty too Smiley
Please do keep finding the motivation to improve it more and more Smiley
Every now and again we have one of these threads, someone will have a go at textures, raytracing or something, this has been one of the best threads of this sort we've had. People will learn a lot from it.
Thanks :) 'glad you like it, and 'glad it'd be useful in any way. Plus, if people can enjoy the awesome help and advises I got here, it's really cool !
@hellfire
QuoteWell done!
Kiitos :D
QuoteImplementing phong shading ?
[...] you won't use it because it's just too slow.
That's exactly what I wanted to know :) Well, no phong shading then.
QuoteImplementing environment mapping ?
There are lots of ways to do it (spherical, cubic, dual-parabolic) and even several representations of spherical coordinates.
Do you have specific questions?
Well not really, I mainly wanted to know if it'd be useful or if it'd really enhance the graphics :) I thought about using either spherical or cubic mapping and using the provided texture to make a skybox/skysphere besides the environment mapping.
QuoteIs there a way to do fast super sampling
Accelerators go the brute-force way: render at higher resolution and afterwards sample down to the original resolution; slow.
Another method is to find the siluette edges and render those as anti-aliased lines; this requires transparency, thus sorting polygons and trouble with the zbuffer.
None of these is really fun.
Hum got it .. So, am I bounded to see crenelated lines ? or maybe the bilinear filtering (if well coded) will greatly improve the graphics and then make the viewer to forget the crenelated lines ?
Thanks for your replies mates :)
btw, could you say me how many FPS you get when you're looking from some distance to the objects and almost see them all (average) ?
You know what, there is really very little difference here between a scene with over 600 polygons to one with very few.
Always between 9 and 12fps
P4 3.0Ghz, ATI X1650 SE
:)
@Hezad: yeah, Phong is very expensive... but you can fake it a lot. Have a look at these:
http://groups.csail.mit.edu/graphics/pubs/thesis_drdaniel2.pdf
http://igad.nhtv.nl/~bikker/articles/various/tut_lighting.htm
@Shockwave :
oh okay :P Well there must me some calculations done even on clipped triangles.. Or maybe the clipping function must be drastically optimized :)
@Stormbringer :
hey cool :) very interesting links, didn't red all the pdf yet but I'm doing it right now, thanks for pointing me on this !
QuoteI thought about using [...] cubic mapping
good luck ;D
I feel an ounce of irony ^^
Some colors :D
edit : added another screenshot
Psychedelic :)
Bottom picture looks like the sun is coming up over the scene
Quote
Bottom picture looks like the sun is coming up over the scene
hum.. now I wanna try some sunset moods :p I'll try env. mapping for that :D
before that, I just tried super sampling ... Well, ok it runs at 2 FPS but wow..the graphics look bloody smooth oO
Update :) I'm working on the 0.05 right now, and here are some news :)
Mainly : Supersampling, HeightMap handling, Fog :D
still a temp version though
v0.05 Changes List
Quote
MAJOR :
- Added Supersampling !
> Switch it ON or OFF before Initiating the engine. (Set_Super_Sampling ON/OFF)
> Not for Real-time use
- Added Height-map loader.
- Smoothed Mip-map generation.
- Added Linear Fog Handling.
- Added Exponential (density) Fog Handling
- Working on Mouse camera handling
Minor :
- Added a skysphere in the example
- User can use fbgfx flags with the init_engine sub
> e.g. GFX_FULLSCREEN
- Fixed a bug in tiling
- Changed textures in the example.
In the zip attached, you'll find the code and a demo (Real_time.exe or Super_Sampled.exe). Warning, the Super_sampled version is incredibly slow. It's definitely not for real time purposes.
You can rotate view with mouse (well .. almost, there's something about the camera rotation I haven't resolved yet :p), move the camera with the UP/DOWN arrow keys.
You can change some options with the function keys.
Props to you for including all three fog modes supported by Opengl :) I guess that it would be easy to alter the fog density too?
Using the terrain to test the engine looks better, it looks very accomplished, the supersampled version ran at 3fps here, which surprisingly was quite useable. In fact it reminded me of flying around in the game "FA18 Interceptor" on the Amiga, sometimes when you were flying around and there were several things on the screen at once, it used to drop to less than 2fps! Still, that game really rocked.
The way that you have your mouse controls set up at the moment it's quite similar to the controls of an aircraft.
Am I right in thinking that all the triangles are both textured and gourad shaded?
Also I saw things like this in the source;
Memcpy(Destlist,TmpList,(tempNbV)*Sizeof(VertexT))
Does this mean that you are rendering things in a temporary buffer and then copying them to the framebuffer?
I really am impressed at how good the scenery looks though Hezad, you made a great job of it :)
Really nice!
The "normal" version runs at 18fps while the supersampled one does 3, which is basically unusable ;)
If you have a look at the supersampled image you can see how few pixels actually benefit from it and how "global" supersampling merely wastes power.
The sky-sphere works amazingly well and integrates perfectly into the terrain!
Also the lighting achieves a very natural look.
I noticed you used gouraud shading on the sky, too, which probably doesn't make sense because the texture captures the surrounding (including the light-source) - actually you would set up your light-sources to match the sky-texture and local lights won't reach the sky.
I'm also pretty sure that you can easily optimize your code to do twice the framerate :)
@Shockwave :
Quoteall three fog modes supported by Opengl Smiley I guess that it would be easy to alter the fog density too?
An other fog mathod is planned : Perlin Noise Fog (I saw some methods using it on the net and it looks bloody realistic :D) And yes, the fog density is easily alterable (even in real-time if wanted). The fog's density can be changed when setting the fog :
Set_Fog METHOD, DENSITY=0
Method : Linear (Density has no effect) / Exponential (you just have to select the desired density)QuoteUsing the terrain to test the engine looks better, it looks very accomplished, the supersampled version ran at 3fps here, which surprisingly was quite useable. In fact it reminded me of flying around in the game "FA18 Interceptor" on the Amiga, sometimes when you were flying around and there were several things on the screen at once, it used to drop to less than 2fps! Still, that game really rocked.
The way that you have your mouse controls set up at the moment it's quite similar to the controls of an aircraft.
Well, in fact the
aircraft-style-mouse-control wasn't wanted ^^ 'still working on it :)
QuoteAm I right in thinking that all the triangles are both textured and gourad shaded?
You're right :) But the engine can still handle flat shaded and non shaded polys.
Quote[...] Does this mean that you are rendering things in a temporary buffer and then copying them to the framebuffer?
Well the line of code you copied is used when clipping : it copies a temporary list of vertices to the one which will be returned. The only "temporary" graphics buffer I use is the one used for super sampling
Quote
I really am impressed at how good the scenery looks though Hezad, you made a great job of it Smiley
Thanks :cheers:
@hellfire
QuoteThe "normal" version runs at 18fps while the supersampled one does 3, which is basically unusable Wink
Sure :D In fact, I start to know what I want with this engine : Since I want to experiment a lot of stuff (even, non real time stuff), I'll work on two versions of the engine : A real-time one, and a CG one, the real-time could be used for games and the CG version could be used for some kind of a CG renderer.
QuoteIf you have a look at the supersampled image you can see how few pixels actually benefit from it and how "global" supersampling merely wastes power.
Well here, you're totally right. The super sampling doesn't really affects the rendering quality. But when objects are more "curved" or complex (spheres, tores, future loaded objects :p), the super-sampling brought a huge difference. I'll try to make a test image to compare both versions.
QuoteThe sky-sphere works amazingly well and integrates perfectly into the terrain!
Also the lighting achieves a very natural look.
Cool :D 'glad you liked the lighting, I wondered some time to find the good blend between fog color, ambiant color and light color :p
QuoteI noticed you used gouraud shading on the sky, too, which probably doesn't make sense because the texture captures the surrounding (including the light-source)
actually you would set up your light-sources to match the sky-texture and local lights won't reach the sky.
In fact, the sky in not shaded :) wherever the lights are, whatever the number of lights is, the sky will always look the same (normally), excepted when fog changes its apparent color of course :)
QuoteI'm also pretty sure that you can easily optimize your code to do twice the framerate Smiley
That would be bloody awesome ^^ But my coding skills appear really weak to myself when it has to do with optimization. I will do it, but it will take time I guess :)
First, I should use the stuff you wrote about : Using Vertex listings and triangle list instead of 3 vertices per triangle, it should drastically improve the speed, but I'm not very sure about how to convert my code to this yet.
Thanks a lot to both of you, your commentaries always motive me :)
You'll gain the most by optimizing those functions which are called most often.
Since the number of pixels to process is by some magnitude larger than the number of vertices, you should start optimizing at your polyfillers, namely the TSLine-Macro.
Just have a look at the code you run for each pixel: :o
tmp_Z = 1/Cur_Z
If tmp_z < ZBuffer(it) then
ZBuffer(it) = tmp_Z
ValU = (U_z*tmp_Z)
ValV = (V_z*tmp_Z)
if ValU>=1 then ValU = ValU-int(ValU)
if ValU<0 then ValU = 0
if ValV>=1 then ValV = ValV-int(ValV)
if ValV<0 then ValV = 0
If UseMipmapping = 0 then
CurTex = Texture.GFX[int(ValU*Texture.SizeX)+Texture.SizeX*int(ValV*Texture.SizeY)]
Cb = (CurTex and 255)
Cg = ((CurTex shr 8) and 255)
Cr = ((CurTex shr 16) and 255)
else
LODVal = iif(mipU>mipV,MipU,mipV)
If LODVal>Texture.NbLOD then LODVal = Texture.NbLod
If LodVal<0 then LODVal = 0
If LODVal = 0 then
CurTex = Texture.GFX[int(ValU*Texture.SizeX)+Texture.SizeX*int(ValV*Texture.SizeY)]
Cb = CurTex and 255
Cg = (CurTex shr 8) and 255
Cr = (CurTex shr 16) and 255
else
CurTex = Texture.MipMap(LODVal)[int(ValU*Texture.MipMapX(LODVal))+Texture.MipMapX(LODVal)*int(ValV*Texture.MipMapY(LODVal))]
Cb = CurTex and 255
Cg = (CurTex shr 8) and 255
Cr = (CurTex shr 16) and 255
end if
end if
Cb *= b*0.003921 '' 1/255
Cg *= g*0.003921
Cr *= r*0.003921
Select Case Render_Option_Val
Case RENDER_NO_OPTION
if Use_Fog then
select case fog_method
case LINEAR
dim as single blendfunc = (MAX_Z-tmp_Z)/(MAX_Z-1)
Cr = blendfunc * Cr + (1-blendfunc) * Fog_Color.r
Cg = blendfunc * Cg + (1-blendfunc) * Fog_Color.g
Cb = blendfunc * Cb + (1-blendfunc) * Fog_Color.b
case EXPONENTIAL
dim as single blendfunc = exp(-(tmp_Z/MAX_Z) * fog_Density)
Cr = blendfunc * Cr + (1-blendfunc) * Fog_Color.r
Cg = blendfunc * Cg + (1-blendfunc) * Fog_Color.g
Cb = blendfunc * Cb + (1-blendfunc) * Fog_Color.b
case HETEROGENOUS
'' placeholder
end select
end if
ScrPtr[it] = rgb(Cr,Cg,Cb)
Case RENDER_MIPMAP
dim as integer ldv = LODVal*127/Texture.NbLOD
ScrPtr[it] = rgb(255-2*Ldv,255-ldv,255-ldv)
Case RENDER_ZBUFFER
dim as integer zc = tmp_Z*255/MAX_Z
ScrPtr[it] = rgb(zc,zc,zc)
end Select
end if
it+=1
U_z += dU_z
V_z += dV_z
Cur_Z += d_Z
r += dR
g += dG
b += dB
First of all you shouldn't be checking render-settings on a per-pixel basis, but do an optimized loop for each of them.
Secondly you can store the interpolated 1/z in your zbuffer (reasonably scaled to fill an integer) so you don't have to do a divsion even if the zbuffer-test fails.
Remove all floating-point-operations when dealing with colours.
Then have a look what I already wrote about subdividing your scanlines and interpolating linearly across these segments.
When that's done, your framerate has atleast doubled.
@hellfire :
You're totally right, I started to code the optimizations you wrote about, especially "one procedure per render type" and I gained 1-2 FPS on the most demanding rendering (Texture + gouraud shader + Fog + transparency) and at least 10 FPS on the Z-buffer rendering ^^
As promised, here is a screen comparing real-time rendering and super sampled rendering, I think it's way more visible with the checker tex.
Quotecomparing real-time rendering and super sampled rendering
Remember that the anti-aliasing of edges inside the
texture would already be handled by bilinear filtering...
Yep, I started to work on bilinear filtering but the slow down was way to strong. I must work on optimization and camera handling before working on filtering again :)
If you want to get some more performance from multisampling, you might want to try non-regular grids with samples shared across pixels:
(http://www.abload.de/img/multisampling1cu.gif)
Although there are 4 samples for each output-pixel, you just have to calculate two new samples per pixel.
Handling the different sampling-positions inside the polyfiller is a bit tricky, though.
Thanks for the advice :)
Don't know yet if I'll use it (I'm still on my camera/optimization wanderings :p) but I'll definitely remember this :D
For static objects (Terrain,etc), I would suggest you do some space partitioning to speed things up quite a bit.
For outside areas with a free-look cam, you could use Octrees. If you are going to limit your FOV from the top, Quadtrees would suffice. Both would almost be coded exactly the same.
Here's a demo on how quadtrees could render stuff faster.
http://rel.betterwebber.com/mytutes/octreetute/octree_tute.zip
It's just sources and exes since I haven't coded anything since those. :*(
Thanks for that :) The point is I don't know yet what this engine's purpose will be ! The main purpose is of course learning, but eh, since It has to be coded, why not making it useful ? ;) In fact, I don't know if I want it to be a 3d engine designed for games, or for CG rendering ... Well, I guess in both cases, Space partitioning will help but I wanted to precise :D
thanks for the sources and the exes, I'll check that :)
Heya :)
Nothing new, I didn't work on it since the last posts :-\ There are so much stuff to do for it to be useful .. I got unmotivated. But right now I want to get back into it, I have a lot of ideas to code with software rendering but I HAVE TO code everything needed before (optimization, camera handling, texture filtering ..).
Okay, so now i'm focusing about camera handling.
Have I to use matrices inevitably ? In fact, for translations, it's pretty easy, no matrices needed. But for rotations ... I use three angle parameters to rotate all the world when needed. But if it works for the Y axis (eg, player rotation), when I wanna rotate along the X axis, it doesn't rotate in the camera space but in world space (So if I rotate along the Y axis and THEN rotate in the X axis, the X rotation isn't done about the direction I'm aiming but along a constant world X axis.
hum... I guess what I just wrote is simply illegible ... Sorry for that .. To resume in a sentence, When I rotate along the Y axis, the X axis stays constant instead of rotating "with me".
(You can see that in the last preview I posted if you try to rotate the camera with your mouse).
So my question is, Can I avoid that with "simple" angles parameters in the Camera Type ? Or MUST I use matrices ?
Using matrices is inevitable and you might be surprised to hear that you are using them already.
You are now rotating your vertices using euler angles, applying one after another.
Euler rotation is nothing else than a 2d-rotation matrix (because the axis of rotation is cartesian, one component of your vector keeps unaffected):
x'= cos(a) * x - sin(a) * y + 0 * z
y'= sin(a) * x + cos(a) * y + 0 * z
z'= 0 * x + 0 * y + 1 * z
(exactcly what you do in "Rotate_Object")
Since a 2d-rotation (2x2 matrix) requires 4 multiplies/additions, you now already require a total of 12 mul/add (not containing translation) and your transformation will soon contain more components like scaling, shearing etc.
If you multiply the three matrices and translation to a single 4x3 matrix you just require 9 muls & 12 adds:
x'= Mxx * x - Mxy * y + Mxz * z + Mxw
y'= Myx * x + Myy * y + Myz * z + Myw
z'= Mzx * x + Mzy * y + Mzz * z + Mzw
Rotating around cartesian axes is also rather unflexible, especially for camera-handling you'll preferably align your view along position- & target-vector (like gluLookAt does).
Try to understand that a matrix just defines the orientation (and origin) of a vector-space. The 3 vectors of the 3x3-part show were side/height/depth is. You can get from one space to another by multiplying with the corresponding matrix. get back again by multiplying with the inverse matrix.
thanks hellfire, I think I start to see the point :) k++
So, for testing purposes, before implementing a LookAt camera, can I apply the 2d rotation matrix 3 times (each time for each axis) ?
Quotecan I apply the 2d rotation matrix 3 times
That's basically what you already do.
If you want to keep the euler angles, I suggest to construct a 3x3 rotation-matrices for each axis and multiply them to a single matrix:
M = Mx * My * Mz
Notice that the order is important - traditionally one rotates around the "up"-axis first.
3x3 layout for (clockwise) rotation-matrices looks like this:
1 0 0
0 cos(x) -sin(x)
0 sin(x) cos(x)
cos(y) 0 -sin(y)
0 1 0
sin(y) 0 cos(y)
cos(z) -sin(z) 0
sin(z) cos(z) 0
0 0 1
Notice that an angle of 0 results in an identity matrix.
Firstly welldone on your development Hezad, and secondly ( or should this be first ) congrats on the first to get over 170 ( at time of typing ) replies in a Coding category; plus on your very first post too - all with cool and handy stuff from peeps.
Karma++
@hellfire :
I don't particulary want to keep Euler angles, but I just want to understand what's going on before working on a look at procedure :)
QuoteI suggest to construct a 3x3 rotation-matrices for each axis and multiply them to a single matrix:
M = Mx * My * Mz
So, if I understand well, I "just" have to build the three 3x3 rotation matrices (the layouts you posted) in the good order and then multiply them. So, how can I use the resulting matrix ? Can I just rotate the viewpos by using numbers contained by the matrix ?
@Clyde :
Thanks a lot :) Credit goes to all the awesome information people posted here, especially hellfire !
btw, what do you mean by "peeps" ?
QuoteSo, if I understand well, I "just" have to build the three 3x3 rotation matrices (the layouts you posted) in the good order and then multiply them. So, how can I use the resulting matrix ? Can I just rotate the viewpos by using numbers contained by the matrix ?
Yes :)
You rotate those points in the matrix and multiply everything with them.
And it will be quite fast too.
Thanks Shockwave :) I have a last question though : if I multiply the three 3x3 matrices, it will result in a 3x3 matrix no? If I'd have a 3x1 matrix, I could multiply each point coordinate by a value in the matrix
e.g. :
point.x *= matrix(1)
point.y *= matrix(2)
point.z *= matrix(3)
But since the resulting matrix is a 3x3 one, what have I to do with the 6 remaining numbers ? Or how to use a 3x3 matrix to handle coordinates using 1x3 numbers ? Rah once again I'm lost. Now I know how to handle simple matrices, but I still don't understand how to handle the transformations they describe :S
Anyway, I coded a little Header to handle matrices (addition, subtraction, multiplication, create empty matrix, create Identity matrix, and a cast operator to print matrices on screen) to use it with my engine.
I post it here, maybe it'll be useful for someone :
matrix.bi :
'' Matrix.bi
''
'' 2008 - Hezad
''
''
'' Contact : Hezad0@Gmail.com
#Define nl chr(10)+chr(13)
Type Matrix
as integer SizeX, SizeY
as single ptr Dat
Declare Constructor(sx as integer, sy as integer)
Declare Operator Cast() as string
Declare Function Value(i as integer, j as integer) as single
Declare Sub Set(i as integer, j as integer,value as single)
Declare function Transpose() as matrix
Declare Sub LoadIdentity()
Declare Sub Empty()
End type
Constructor Matrix(sx as integer, sy as integer)
this.SizeX = sx
this.SizeY = sy
Dat = Callocate(sx*sy+1,Sizeof(single))
End Constructor
Operator Matrix.Cast() as string
dim as string finStr
dim as integer iter
for j as integer = 0 to this.SizeY-1
finStr += "["
For i as integer = 0 to this.SizeX-1
finStr += " "+str(this.Dat[iter])
iter+=1
next
finStr += " ]"+nl
next
return finStr
End Operator
Operator + (LM as matrix, RM as matrix) as matrix
if LM.SizeX<>RM.SizeX or LM.SizeY<>RM.SizeY then exit Operator
Dim as matrix newMat = matrix(LM.SizeX,LM.SizeY)
dim as integer iter
for i as integer = 0 to LM.SizeX-1
for j as integer = 0 to RM.SizeY-1
NewMat.dat[iter] = LM.dat[iter] + RM.Dat[iter]
iter+=1
next
next
return NewMat
end Operator
Operator - (LM as matrix, RM as matrix) as matrix
if LM.SizeX<>RM.SizeX or LM.SizeY<>RM.SizeY then exit Operator
Dim as matrix newMat = matrix(LM.SizeX,LM.SizeY)
dim as integer iter
for i as integer = 0 to LM.SizeX-1
for j as integer = 0 to RM.SizeY-1
NewMat.dat[iter] = LM.dat[iter] - RM.Dat[iter]
iter+=1
next
next
return NewMat
end Operator
Operator *(LM as matrix, RM as matrix) as matrix
if LM.SizeX<>RM.SizeY or LM.SizeY<>RM.SizeX then exit Operator
Dim as matrix newMat = matrix(RM.SizeY,LM.SizeX)
dim as single curVal
dim as integer iter
for j as integer = 0 to newMat.SizeY-1
for i as integer = 0 to newMat.SizeX-1
curVal = 0
for k as integer = 0 to LM.SizeX-1
curVal += LM.dat[k+j*LM.SizeX] * RM.dat[i+k*RM.SizeX]
next
newMat.dat[iter] = curval
iter+=1
next
next
return NewMat
end Operator
Operator *(LM as matrix, RM as single) as matrix
Dim as matrix newMat = matrix(LM.SizeX,LM.SizeY)
dim as integer iter
for i as integer = 0 to newMat.SizeX-1
for j as integer = 0 to newMat.SizeY-1
newMat.dat[iter] = RM * LM.dat[iter]
iter+=1
next
next
return NewMat
end Operator
Sub Matrix.Empty()
for i as integer = 0 to this.SizeX*this.SizeY-1
this.Dat[i] = 0
next
end sub
Sub Matrix.LoadIdentity()
if this.SizeX <> this.SizeY then exit sub
for i as integer = 0 to this.SizeX-1
for j as integer = 0 to this.SizeY-1
this.set i,j,iif(i=j,1,0)
next
next
end sub
function Matrix.Transpose() as Matrix
dim as matrix tmp = matrix(this.SizeY,this.SizeX)
For j as integer = 0 to this.SizeY-1
for i as integer = 0 to this.SizeX-1
tmp.dat[i+j*this.SizeY] = this.dat[j+i*this.SizeX]
next
next
return tmp
end function
Function Matrix.Value(i as integer, j as integer) as single
return this.Dat[i+j*this.SizeX]
end function
Sub Matrix.Set(i as integer, j as integer,value_m as single)
this.Dat[i+j*this.SizeX] = value_m
end sub
matrixExample.bas
'' Matrix.bi Example file
''
'' 2008 - Hezad
''
''
'' Contact : Hezad0@Gmail.com
#include "matrix.bi
Dim as matrix A = Matrix(3,3), B = Matrix(3,3), ID = Matrix(3,3)
for i as integer = 0 to 2
For j as integer = 0 to 2
A.Set i,j,i*2+j
next
next
Print "A =>"
Print A
B = A*A
Print "B = A*A =>"
Print B
B = A.Transpose
Print "B = A.Transpose =>"
Print B
ID.LoadIdentity
Print "ID.LoadIdentity =>"
Print ID
B.Empty
Print "B.Empty =>"
Print B
B = A*ID
Print "B = A*ID (=ID*A) =>"
Print B
sleep
edit : Attached a .rar (tested the header with the engine.., a funny bug to see : try to rotate the camera with your mouse ^^)
You won't need matrices of different size.
I suggest to use 4x3 matrices which is a 3x3 matrix with an additional translation-vector.
Most APIs use 4x4 matrices, but I'm pretty sure you won't need those for a while.
Matrix:
xx xy xz xp
yx yy yz yp
zx zy zz zp
[rotation] [position]
Try to understand that the 3x3 part consists of three vectors.
These vectors are the axes of your coordinate system:
(xx,yx,zx) points to the right
(xy,yy,zy) points up
(xz,yz,zz) points into depth
Identity matrix (transforms x,y,z to the same x,y,z):
1 0 0 0
0 1 0 0
0 0 1 0
Notice that the bases are the cartesian axes (transformation to the vector-space you're already in).
A vector describes a position or direction relative to a coordinate system.
When transforming a vector (x,y,z) to another coordinate-system, the components of the vector are then relative to new coordinate axes:
x'= xx*x + xy*y + xz*z + xp
y'= yx*x + yy*y + yz*z + yp
z'= zx*x + zy*y + zz*z + zp
Now we want to transform using two matrices "m1" and "m2":
first transform (x,y,z) to (x1,y1,z1) using m1 (using equation above)
x1= m1.xx*x + m1.xy*y + m1.xz*z + m1.xp
y1= m1.yx*x + m1.yy*y + m1.yz*z + m1.yp
z1= m1.zx*x + m1.zy*y + m1.zz*z + m1.zp
then transform (x1,y1,z1) to (x2,y2,z2) using m2
x2= m2.xx*x1 + m2.xy*y + m2.xz*z + m2.xp
y2= m2.yx*x1 + m2.yy*y + m2.yz*z + m2.yp
z2= m2.zx*x1 + m2.zy*y + m2.zz*z + m2.zp
This is twice the work, so we're looking for new matrix "n" which does both transforms at once.
We simply plug the first equation into the second and reorder back to matrix-layout (just shown for one component):
x2= m2.xx * (m1.xx*x + m1.xy*y + m1.xz*z + m1.xp) (equation for x1)
+ m2.xy * (m1.yx*x + m1.yy*y + m1.yz*z + m1.yp) (equation for y1)
+ m2.xz * (m1.zx*x + m1.zy*y + m1.zz*z + m1.zp) (equation for z1)
+ m2.xp
=> remove braces
x2= m2.xx*m1.xx*x + m2.xy*m1.yx*x + m2.xz*m1.zx*x
+ m2.xx*m1.xy*y + m2.xy*m1.yy*y + m2.xz*m1.zy*y
+ m2.xx*m1.xz*z + m2.xy*m1.yz*z + m2.xz*m1.zz*z
+ m2.xx*m1.xp + m2.xy*m1.yp + m2.xz*m1.zp + m2.xp
=> refactor to x,y,z:
x2= (m2.xx*m1.xx+m2.xy*m1.yx+m2.xz*m1.zx) * x
+ (m2.xx*m1.xy+m2.xy*m1.yy+m2.xz*m1.zy) * y
+ (m2.xx*m1.xz+m2.xy*m1.yz+m2.xz*m1.zz) * z
+ (m2.xx*m1.xp + m2.xy*m1.yp + m2.xz*m1.zp + m2.xp)
So here we've got the first row of a matrix which transform "x" directly to "x2".
The complete new matrix "n" looks like this:
n.xx= m2.xx*m1.xx + m2.xy*m1.yx + m2.xz*m1.zx
n.xy= m2.xx*m1.xy + m2.xy*m1.yy + m2.xz*m1.zy
n.xz= m2.xx*m1.xz + m2.xy*m1.yz + m2.xz*m1.zz
n.xp= m2.xx*m1.xp + m2.xy*m1.yp + m2.xz*m1.zp + m2.xp
n.yx= m2.yx*m1.xx + m2.yy*m1.yx + m2.yz*m1.zx
n.yy= m2.yx*m1.xy + m2.yy*m1.yy + m2.yz*m1.zy
n.yz= m2.yx*m1.xz + m2.yy*m1.yz + m2.yz*m1.zz
n.yp= m2.yx*m1.xp + m2.yy*m1.yp + m2.yz*m1.zp + m2.yp
n.zx= m2.zx*m1.xx + m2.zy*m1.yx + m2.zz*m1.zx
n.zy= m2.zx*m1.xy + m2.zz*m1.zy + m2.zy*m1.yy
n.zz= m2.zx*m1.xz + m2.zy*m1.yz + m2.zz*m1.zz
n.zp= m2.zx*m1.xp + m2.zy*m1.yp + m2.zz*m1.zp + m2.zp
@Hezad: No worries dude, btw peeps is a way I use to say people.
@hellfire :
Thanks a lot mate, it'll definitely be helpful :) k++
@Clyde : thanks for the precision :D
Finally !!!!!!!!!
UPDATE !
The engine hasn't progressed for a while because of the matrix stuff, I didn't know how to implement all this, even with the very good help I received here. But finally, the camera rotation now works :D Camera movement (correlated with its rotation) isn't implemented yet (or .. well, it's implemented but not working :P)
So I'm proud to announce the version 0.055 ! Here is the change log :
Quotev0.055 Changes List
MAJOR :
- Added Material Opacity handling.
- Changed all the Render Subs. One Render procedure for each render type
> Render_Opt_ZBuffer
> Render_Opt_MipMap
> Render_Flat_NoTex
> Render_Flat_NoTex_Fog
> Render_Flat_Tex
> Render_Flat_Tex_Fog
> Render_Smooth_NoTex
> Render_Smooth_NoTex_Fog
> Render_Smooth_Tex
> Render_Smooth_Tex_Fog
- Camera now uses a matrix for the engine to rotate Viewport !
> New header : matrix.bi (handles matrices)
Minor :
- Added Water Texture.
Known bugs :
- Camera movement to code
- Transparent Objects must be the last projected objects.
Attached : *.rar archive :)
edit : camera movement is now totally fixed :D I didn't uploaded the fix, but you'll see that with the 0.06 :D
Looks very nice but still needs some serious optimizing ;)
(the zbuffer-view already got pretty fast, though!)
Your exponential fog is rather expensive:
blendfunc = exp(-(tmp_Z*_MAX_Z) * fog_Density)
Cr = blendfunc * Cr + (1-blendfunc) * Fog_Color.r
Cg = blendfunc * Cg + (1-blendfunc) * Fog_Color.g
Cb = blendfunc * Cb + (1-blendfunc) * Fog_Color.b
Generally I would recommend not to call any trigonometric functions on a per pixel basis ;)
You can do a second pass which looks up the fog-density from the significant bits of zbuffer using a small table.
This way your polyfiller's innerloop gets tighter and the blending is only done once per pixel (due to overdraw you'll usually plot every pixel more than once).
I think that it looks great Hezad, it really does!
As Hellfire says, it really needs optimising though, please see the attachment.
This attachment was written by Stonemonkey, he developed a similar engine to yours, this example doesn't have fog etc, but take a look, this is the sort of speed you can achieve with optimisation.
@hellfire : I get the point, thanks :) Couldn't I create an Exp() look up table ? I mean precalculate a huge Exp() table and seeking the good value inside ?
@Shockwave : Thanks for the demo ... those ~55 FPS make me dream ^^
@Both :
Thanks for the feedback :) Yep, optimizing is bloody necessary now if I want to continue to add features :S
Your new features will not look thier best without those optimisations Hezad, but take heart.
Stonemonkeys demo there has quite a lot of triangles, gourad shading, perspective correct mapping, clipping and mip mapping and the fps is limited too.. It can go even faster than that so there's no reason why you should not be able to at least treble your fps in time :)
There seems to be some lack of precision.
@Shockwave :
thanks :) I hope I'll succeed in such an objective ^^
@hellfire :
ouch ! Never noticed it o_O Could it come from not precise enough data types ? (eg : single instead of double)
QuoteCould it come from not precise enough data types ? (eg : single instead of double)
No. One reason is missing subpixel-/-texel-precision (I wrote something about that somewhat earlier).
The problem is that the coordinates you interpolate along the slopes of your triangle are valid for a floating-point position (cyan dots) on the edge, but the actual (integer) pixel-position is significantly different:
(http://www.abload.de/img/rasterizem710.gif)
(image assumes all coordinates are rounded up)
Concerning optimization I restructured your source a bit.
It's still far from optimal but maybe you can get some ideas out of it:
#Macro SmoothLine_Tex_Fog(x1,x2,y, _
U_z1,V_z1,_
U_z2,V_z2,_
Texture, _
r1,r2,g1,g2,b1,b2,_
_z1,_z2,_
CurOpacity)
dim as integer dR,dG,dB
dim as integer r,g,b
dim as single sr1,sr2,sg1,sg2,sb1,sb2
dim as single dU_z,dV_z,U_z,V_z
dim as single Uu_z1,Vv_z1,Uu_z2,Vv_z2
dim as integer itx1,itx2
dim as single xDiv,Cur_Z,_zz1,_zz2,d_Z
dim as integer it, LODVal, MipU, MipV
dim as integer CurTex,Cr,Cg,Cb
dim as single tmp_Z
' this isn't required when all polygons have equal orientation!
if x1<x2 then
_zz1 = _z1
_zz2 = _z2
itx1 = x1
itx2 = x2
Uu_z1 = u_z1
Uu_z2 = u_z2
Vv_z1 = v_z1
Vv_z2 = v_z2
sr1 = r1
sr2 = r2
sg1 = g1
sg2 = g2
sb1 = b1
sb2 = b2
else
_zz1 = _z2
_zz2 = _z1
itx1 = x2
itx2 = x1
Uu_z1 = u_z2
Uu_z2 = u_z1
Vv_z1 = v_z2
Vv_z2 = v_z1
sr1 = r2
sr2 = r1
sg1 = g2
sg2 = g1
sb1 = b2
sb2 = b1
end if
' deltas can be computed once per triangle!
xDiv = 1/(itx2 - itx1)
' delta rgb (integer)
dR = xDiv * (sr2 - sr1) * 256.0
dG = xDiv * (sg2 - sg1) * 256.0
dB = xDiv * (sb2 - sb1) * 256.0
' delta uvz (float)
d_Z = xDiv * (_zz2 - _zz1)
dU_z = xDiv * (Uu_z2 - Uu_z1)
dV_z = xDiv * (Vv_z2 - Vv_z1)
Cur_Z = _zz1
U_z = Uu_z1
V_z = Vv_z1
r = sr1 * 256.0
g = sg1 * 256.0
b = sb1 * 256.0
it = y*xRes + itx1
dim length as integer
length= itx2-itx1
dim u1 as integer
dim v1 as integer
dim u2 as integer
dim v2 as integer
dim du as integer
dim dv as integer
dim shiftu as integer
dim shiftv as integer
' get first set of perspective correct u,v
tmp_Z = 65536.0/Cur_Z
u1 = U_z*tmp_Z*Texture.SizeX
v1 = V_z*tmp_Z*Texture.SizeY
while length>0
' draw 16 pixels (or rest of scanline)
dim size as integer
size= 16
if size > length then size= length
length-=size
' step 16 pixels further
U_z += dU_z*16
V_z += dV_z*16
' get 2nd set of perspective u,v
tmp_Z = 65536.0/(Cur_Z+d_Z*16)
u2 = U_z*tmp_Z*Texture.SizeX
v2 = V_z*tmp_Z*Texture.SizeY
' linear deltas
du= (u2-u1) shr 4
dv= (v2-v1) shr 4
' mip-level from deltas
mipu = log2( abs(du shr 16) )
mipv = log2( abs(dv shr 16) )
LODVal = iif(mipU>mipV,MipU,mipV)
If LODVal>Texture.NbLOD then LODVal = Texture.NbLod
' store this in Texture
shiftu= log2(Texture.SizeX)
shiftv= log2(Texture.SizeY)
shiftu-= LODVal
shiftv-= LODVal
dim masku as integer
dim maskv as integer
masku= (1 shl shiftu)-1
maskv= (1 shl shiftv)-1
for i as integer = 1 to size
If Cur_Z > ZBuffer(it) then
' store 1/z in zbuffer (zbuffer is now single and cleared to 0!)
ZBuffer(it) = Cur_Z
dim u as integer
dim v as integer
u= (u1 shr (16+LODVal)) and masku
v= (v1 shr (16+LODVal)) and maskv
' CurTex = Texture.GFX[ (v shl 8) or u ]
CurTex = Texture.MipMap(LODVal)[ (v shl shiftu) or u ]
Cb = (CurTex and 255)
Cg = ((CurTex shr 8) and 255)
Cr = ((CurTex shr 16) and 255)
Cb= Cb*b shr 16
Cg= Cg*g shr 16
Cr= Cr*r shr 16
ScrPtr[it] = rgb(Cr,Cg,Cb)
end if
it+=1
r += dR
g += dG
b += dB
u1+= du
v1+= dv
Cur_Z+=d_Z
Next
' reuse 2nd set of u,v
u1= u2
v1= v2
Wend
#EndMacro
(Skipped the fog- & transparency-parts for readability)
thanks, Thanks, THANKS ;D I'm reading it, and it's really interesting :)
edit : I just tried it in my code ... DUDE, YOU SHOULD BE CANONIZED BY THE CODERS CHURCH :goodpost: I get between 15 and 25 FPS (35 if just ~500 triangles are in view) instead of the 9 FPS I got before :P yehookey, the v0.06 will have a big improvement in speed, thanks to you mate.
I have a question though,
In a comment, you say :
Quote' deltas can be computed once per triangle!
But I don't understand how it could be possible :S Since I use 1/(x2-x1) to calculate the deltas, don't I need to recalc it on each scanline ((x2-x1) changes on every line) ?
QuoteQuotedeltas can be computed once per triangle!
But I don't understand how it could be possible :S
Well, the reason
why this is possible is a bit difficult to explain without rolling out a lot of math.
It's probably easy to understand by showing a picture:
(http://www.abload.de/img/perspective-uvxnzv.jpg)
On the left you see a textured cube with some perspective.
The texture-coordinates are interpolated as u/z, v/z, 1/z and reconstructed to u'= (u/z)/(1/z) v'= (v/z)/(1/z) (just the same as you do now).
In the middle you can see the perspective-correct texture-coordinates u',v' shown as red and green (a bit differently scaled so you can see a gradient).
On the right you can see the interpolated u/z and v/z (also in red and green).
What you can see here is that the gradients are parallel(!)
That's the reason why you can interpolate them linearly (instead of the original u,v).
This also means that the deltau,deltav between two pixels doesn't change from one scanline to another but is *always* the same.
To get the highest precision, you can simply calculate the deltas at the longest scanline.
QuoteI get between 15 and 25 FPS (35 if just ~500 triangles are in view) instead of the 9 FPS I got before
A big portion of the speed-improvement was achieved by removing float-to-integer-conversions.
Conversion is expensive because cpu & fpu don't have a direct connection but need to write/read values from memory.
In your source there are many more situations that can benefit significantly from such optimization.
My personal favourite is this:
For i as integer = 0 to xRes-1 step 2
For j as integer = 0 to yRes-1 step 2
RenderPtr[i*.5+xRes_2*j*.5] = ...
The key to proper optimization is to find those functions where your cpu spends most of the time.
Luckily there's a counter in your cpu which is increased with each processor-cycle.
You can simply query it before and after a function-call and the difference is the number of cycles spend inside the function.
To query the counter you can use this:
function getCpuTick() as double
dim as double ticks
asm
lea edi,[ticks]
rdtsc
mov [edi],eax
mov [edi+4],edx
fild qword ptr [edi]
fstp qword ptr [edi]
end asm
return ticks
end function
Thanks for all those tips, it'll definitely help me to optimize the engine :)
I implemented in every render procedure the restructured code you shared two or three posts ago and it work pretty well :) As I said, the speed improvement is totally noticeable, thanks again for that. But there's a little problem. It seems like the texture tiling doesn't work anymore ! Well in fact it works perfectly on the terrain, but the sphere I use for the sky is really weirdly tiled. I attach a screenshot to show what I mean. And I post here the render procedure used for the skysphere :
#Macro FlatLine_Tex(x1,x2,y, _
U_z1,V_z1,_
U_z2,V_z2,_
Texture, _
r_,g_,b_,_
_z1,_z2,_
CurOpacity)
dim as integer r,g,b
dim as single dU_z,dV_z,U_z,V_z
dim as single Uu_z1,Vv_z1,Uu_z2,Vv_z2
dim as integer itx1,itx2
dim as single xDiv,Cur_Z,_zz1,_zz2,d_Z
dim as integer it, LODVal, MipU, MipV
dim as integer CurTex,Cr,Cg,Cb
dim as single tmp_Z
' this isn't required when all polygons have equal orientation!
if x1<x2 then
_zz1 = _z1
_zz2 = _z2
itx1 = x1
itx2 = x2
Uu_z1 = u_z1
Uu_z2 = u_z2
Vv_z1 = v_z1
Vv_z2 = v_z2
else
_zz1 = _z2
_zz2 = _z1
itx1 = x2
itx2 = x1
Uu_z1 = u_z2
Uu_z2 = u_z1
Vv_z1 = v_z2
Vv_z2 = v_z1
end if
' deltas can be computed once per triangle!
xDiv = 1/(itx2 - itx1)
' delta uvz (float)
d_Z = xDiv * (_zz2 - _zz1)
dU_z = xDiv * (Uu_z2 - Uu_z1)
dV_z = xDiv * (Vv_z2 - Vv_z1)
Cur_Z = _zz1
U_z = Uu_z1
V_z = Vv_z1
r = r_ * 256.0
g = g_ * 256.0
b = b_ * 256.0
it = y*xRes + itx1
dim length as integer
length= itx2-itx1
dim u1 as integer
dim v1 as integer
dim u2 as integer
dim v2 as integer
dim du as integer
dim dv as integer
dim shiftu as integer
dim shiftv as integer
' get first set of perspective correct u,v
tmp_Z = 65536.0/Cur_Z
u1 = U_z*tmp_Z*Texture.SizeX
v1 = V_z*tmp_Z*Texture.SizeY
while length>0
' draw 16 pixels (or rest of scanline)
dim size as integer
size= 16
if size > length then size= length
length-=size
' step 16 pixels further
U_z += dU_z*16
V_z += dV_z*16
' get 2nd set of perspective u,v
tmp_Z = 65536.0/(Cur_Z+d_Z*16)
u2 = U_z*tmp_Z*Texture.SizeX
v2 = V_z*tmp_Z*Texture.SizeY
' linear deltas
du= (u2-u1) shr 4
dv= (v2-v1) shr 4
' mip-level from deltas
mipu = eng_log2( abs(du shr 16) )
mipv = eng_log2( abs(dv shr 16) )
LODVal = iif(mipU>mipV,MipU,mipV)
If LODVal>Texture.NbLOD then LODVal = Texture.NbLod
' store this in Texture
shiftu= eng_log2(Texture.SizeX)
shiftv= eng_log2(Texture.SizeY)
shiftu-= LODVal
shiftv-= LODVal
dim masku as integer
dim maskv as integer
masku= (1 shl shiftu)-1
maskv= (1 shl shiftv)-1
for i as integer = 1 to size
If Cur_Z > ZBuffer(it) then
' store 1/z in zbuffer (zbuffer is now single and cleared to 0!)
ZBuffer(it) = Cur_Z
dim u as integer
dim v as integer
u= (u1 shr (16+LODVal)) and masku
v= (v1 shr (16+LODVal)) and maskv
' CurTex = Texture.GFX[ (v shl 8) or u ]
CurTex = Texture.MipMap(LODVal)[ (v shl shiftu) or u ]
Cb = (CurTex and 255)
Cg = ((CurTex shr 8) and 255)
Cr = ((CurTex shr 16) and 255)
Cb = Cb*b shr 16
Cg = Cg*g shr 16
Cr = Cr*r shr 16
If CurOpacity<>1 then
dim as integer br,bg,bb,ptVal
ptVal = ScrPtr[it]
br = (ptVal shr 16) and 255
bg = (ptVal shr 8) and 255
bb = ptVal and 255
Cr = CurOpacity * (Cr - Br) + Br
Cg = CurOpacity * (Cg - Bg) + Bg
Cb = CurOpacity * (Cb - Bb) + Bb
end if
if Use_Fog then
Dim as single BlendFunc
select case fog_method
case LINEAR
blendfunc = 1-_MAX_Z/ZBuffer(it)
Cr = BlendFunc * (Cr - Fog_Color.r) + Fog_Color.r
Cg = BlendFunc * (Cg - Fog_Color.g) + Fog_Color.g
Cb = BlendFunc * (Cb - Fog_Color.b) + Fog_Color.b
case EXPONENTIAL
blendfunc = exp(-(_MAX_Z/ZBuffer(it)) * fog_Density)
Cr = BlendFunc * (Cr - Fog_Color.r) + Fog_Color.r
Cg = BlendFunc * (Cg - Fog_Color.g) + Fog_Color.g
Cb = BlendFunc * (Cb - Fog_Color.b) + Fog_Color.b
case HETEROGENOUS
'' placeholder
end select
end if
ScrPtr[it] = rgb(Cr,Cg,Cb)
end if
it+=1
u1+= du
v1+= dv
Cur_Z+=d_Z
Next
' reuse 2nd set of u,v
u1= u2
v1= v2
Wend
#EndMacro
( its the macro used for Flat shading, but I tried with gouraud shading and there's the same problem :S )
As you noticed, I didn't change the deltas calculations to be done once per triangle yet, I'd like to understand what's going on with the tiling before :)
For information, I tried to change the tiling coefficient (I tried higher and lower) and it seems like the selected mipmap level is wrong with big triangles (like the ones on the skysphere), don't know if it can help.
thanks again !
There's some ugly inconsistency in your mipmap handling:
TextureT.MipMap(x) starts at x=1 (which contains the first mipmap).
x can be 0 though (the original texture is to be used), which causes an out-of-bounds array-access.
Accidentally the correct pointer "GFX" is stored at that adress ;D
I strongly recommend to handle that a bit cleaner.
This doesn't seem to cause your problem, though.
Where do you scale your texture-coordinates according to MaterialT.TileX/.TileY ?
And what exactly does eng_log2() do and how does it differ from Log2() ?
Hey :)
Quotex can be 0 though (the original texture is to be used), which causes an out-of-bounds array-access.
Accidentally the correct pointer "GFX" is stored at that adress Grin
I strongly recommend to handle that a bit cleaner.
Yep, i'll change that :)
QuoteWhere do you scale your texture-coordinates according to MaterialT.TileX/.TileY ?
well in fact each UV coords of a triangle is multiplicated by TileX/Y variable before rendering the textures. That's why I used U = U-int(U) before (to get the U val between 0 and 1). I tried to use it with your code but I was a bit lost between the shifts, UV calcs, etc.. :P
QuoteAnd what exactly does eng_log2() do and how does it differ from Log2() ?
oh sorry I forgot the tell about it :P in fact eng_log2() is EXACTLY the same as the previous Log2() ^^ just the name changed (that was because I tried the crt/maths.bi version of exp_() to see if it was faster but it was not. I changed the name of the sub because there was a sub in maths.bi also called Log2() )
Sorry Hazard, I forgot to mention that texture-addressing assumes the texture-size to by a power of 2 (your sky-texture is 300x300 pixels though).
I believe this is a reasonable limitation and matches well with the concept of mipmapping.
Another thing I noticed is that, when calculating the scanline-deltas, you use the integer-coordinates of x1,x2:
dim as integer itx1,itx2
...
itx1 = x1
itx2 = x2
...
xDiv = 1/(itx2 - itx1)
d_Z = xDiv * (_zz2 - _zz1)
...
That makes you loose a lot of precision.
I changed the size of the texture to be 256*256 and it works PERFECTLY :D thanks :) I don't even know how I could forget it (the power of 2 thing) >_<
Quote[...] That makes you loose a lot of precision.
hum.. Once again I don't understand lol since the x delta is based on the screen pixels delta, shouldn't it be integers coords anyway ?
Quotesince the x delta is based on the screen pixels delta, shouldn't it be integers coords anyway ?
Have a look at the image (triangle) above again...
You interpolated u,v,z along the left and right side of your triangle.
These coordinates are valid
on the edge of the triangle (precisely at the x-coordinate which was interpolated, too).
The pixel starts somewhat later (or earlier, depends on rounding - later you should also take into acount the space "skipped" from the edge to the first pixel of the scanline).
So when dividing by the
number of pixels you're somewhat away from the actual space between the two sets of coordinates.
In other words:
If you have 7.9 and want to know how much is 1.0 you wouldn't divide it by 7 ;)
Of course the same applies to the interpolation along the edges (y-direction).
QuoteHave a look at the image (triangle) above again...
erm .. yeah sorry I spoke before thinking >< I modified the render subs and macros and it looks way better :) Now I notice a small gap when I look for clipped triangles from a really near point of view. But it must be a precision stuff again, I'll check that :)
I continue to optimize stuff with the advises you shared :)
oh by the way, my pseudo's not "hazard" :2funny:
anyway, thanks again :)
Quoteoh by the way, my pseudo's not "hazard"
Oops, sorry dude.
I also just realized that I have three folders on my pc, each containing a different version of your engine.
Their names are "hazad", "hezard" and "hazerd" ;D
Feel honoured, they're usually called "crap" or "shit" ;)
But I promise to pay some more attention from now on :)
Don't worry, I don't feel outraged ^^ And I am honored to have my own folders on your hard-drive :D
Now that the zbuffer contains floating-point-values, the ztest became a bit suboptimal:
fld dword ptr [ebp-380] ' load Cur_Z (floating-point)
fcomp dword ptr [eax] ' Compare with Zbuffer(it)
push eax ' Save Register ax
fnstsw ax ' load fpu-state to ax
test ah, 0b01000001 ' test for bit containing "lower" state
pop eax ' restore register ax
jnz .Lt_05EA ' jump if bit was set
' render pixel
.Lt_05EA:
' continue
(piece of assembly-output from the freebasic compiler)
As this is a general overhead even for those pixels which don't pass the ztest, I suggest you scale your interpolated z-value to reasonable size and store it into an integer zbuffer:
dim as integer Cur_Z,d_Z
...
d_Z = xDiv * (_zz2 - _zz1) * 2147483647.0
Cur_Z = _zz1 * 2147483647.0
for i as integer = 1 to size
If Cur_Z > ZBuffer(it) then
ZBuffer(it) = Cur_Z
...
end if
...
Cur_Z+=d_Z
Next
(Using 2147483647.0 [2^31-1] assumes your front-clipplane is >=1)
Alternatively, because the binary bitset of strictly increasing positive floating-point values is also strictly increasing, you can simply "assume them as integers":
Make integer-pointers to your (single-)zbuffer and (single-)z-value and compare those.
Both ways end up equally:
mov eax, dword ptr [ebp-380] ' load Cur_Z (inetger)
cmp dword ptr [eax] ' Compare with Zbuffer(it)
jle .Lt_05EA ' jump if smaller or equal
' render
.Lt_05EA:
' skip
If your sub (including macros) is called often and has a lot of local variables, declare them static.
Otherwise the compiler will initialize all local variables to zero when it is called, which looks like this:
mov dword ptr [ebp-4], 0
mov dword ptr [ebp-8], 0
mov dword ptr [ebp-12], 0
mov dword ptr [ebp-16], 0
mov dword ptr [ebp-20], 0
mov dword ptr [ebp-24], 0
mov dword ptr [ebp-28], 0
mov dword ptr [ebp-32], 0
mov dword ptr [ebp-36], 0
mov dword ptr [ebp-40], 0
mov dword ptr [ebp-44], 0
mov dword ptr [ebp-48], 0
mov dword ptr [ebp-52], 0
mov dword ptr [ebp-56], 0
mov dword ptr [ebp-60], 0
mov dword ptr [ebp-64], 0
mov dword ptr [ebp-68], 0
mov dword ptr [ebp-72], 0
mov dword ptr [ebp-76], 0
mov dword ptr [ebp-80], 0
mov dword ptr [ebp-84], 0
mov dword ptr [ebp-88], 0
mov dword ptr [ebp-92], 0
mov dword ptr [ebp-96], 0
mov dword ptr [ebp-100], 0
mov dword ptr [ebp-104], 0
mov dword ptr [ebp-108], 0
mov dword ptr [ebp-112], 0
mov dword ptr [ebp-116], 0
mov dword ptr [ebp-120], 0
mov dword ptr [ebp-124], 0
mov dword ptr [ebp-128], 0
mov dword ptr [ebp-132], 0
mov dword ptr [ebp-136], 0
mov dword ptr [ebp-140], 0
mov dword ptr [ebp-144], 0
mov dword ptr [ebp-148], 0
mov dword ptr [ebp-152], 0
mov dword ptr [ebp-156], 0
mov dword ptr [ebp-160], 0
mov dword ptr [ebp-164], 0
mov dword ptr [ebp-168], 0
mov dword ptr [ebp-172], 0
mov dword ptr [ebp-176], 0
mov dword ptr [ebp-180], 0
mov dword ptr [ebp-184], 0
mov dword ptr [ebp-188], 0
mov dword ptr [ebp-192], 0
mov dword ptr [ebp-196], 0
In TextureT.GenerateMipMap you should take into account that texture do not need to be quadratic.
Especially for sky-domes it's common to have 1024x256 or something like that.
Cool :D thanks a lot for this tip, I'll code it immediately :)
edit : erm .. Since d_Z is used for the U,V coordinates and LOD calculation, and since theses are already subjects to integers conversions (the reorganization you shared some posts ago), if I modify d_Z, all the nexts calcs involving it become wrong :S Must I reconvert d_Z before ?
A little preview of a new feature of the v0.06 before I release it : the klein bottle generation :D
(You can see a little light problem on the screenshot, it's because of the normals, since the Klein has only one side, some normals are not okay, I don't really know how to fix that for now but it's not a problem, the Klein bottle generation is more a gadget than anything else :D )
Nice pipe ;D
d_Z isn't required for LOD calculation if you lay out your code as I suggested before.
If it's a problem to scale the interpolated z, you can also avoid the float-compare as described above:
dim as integer ptr izbuf= zbuffer
dim as integer ptr izval = @Cur_Z
...
for i as integer = 1 to size
if *izval > izbuf(it) then
zbuffer(it)= Cur_Z
...
end if
Cur_Z+=d_Z
...
end
(untested)
Another thing you should look after is the fact that your triangle-structure is 696 Bytes big!
It doesn't make sense to store a copy(!) of the material for each triangle.
I suggest keeping an array of (different) materials, each with a list of triangles.
Also try to reduce the vertex-size to a minimum. Remember: A "vertex" is the data necessary for rasterization and not a trashbin of coordinates ;)
Personally I would create different types of materials, each doing a specified job.
Notice that the compare in GenerateTore and GenerateSphere doesn't work due to rounding errors:
dPhi = PIx2/nbFaces
for theta as single = 0 to PIx2 step dphi
...
if theta<>PIx2 then ' this is always the case!
- seems like the sphere has an additional row of triangles (tested with this (http://www.abload.de/image.php?img=skysphere3tst.jpg) texture)
Heya :) Thanks again for all this ! I already implemented the Material list and I am wondering about the z_buffer change :
I tried and I'm not sure to notice a change in speed, and it brings me to a question : Since the pointer points to an integer, when I do
*zbuff = zbuffer(it)*2147483647.0
*zval = Cur_Z*2147483647.0
If *zbuff > *zval then
[...]
,
There's still a single>integer conversion, no ? Maybe I shouldn't multiply by 2147483647.0 before, but if it's the case, I must admit there's something I didn't understand :S
Plus, I must allocate and deallocate the pointer, which makes me loose some cycles again ???
It's much simpler.
You are not doing any conversion and keep everything in floating point:
redim as single zBuffer(SCREEN_MEM)
dim as single Cur_Z, d_Z ' the interpolated z-value and delta
Now you get pointers to both:
dim as integer ptr izbuf= @zbuffer(0)
dim as integer ptr izval= @Cur_Z
When addressing a value with the integer pointer, this is handled by the cpu.
All you have to do is replace the compare:
for i as integer = 1 to size
' if Cur_Z > ZBuffer(it) then ' old: the fpu handles the fairly slow single-compare
if *izval > izbuf[it] then ' new: the compiler thinks these values are integers and lets the cpu do the compare
...
endif
it+=1
Cur_Z+=d_Z
...
wend
This works for positive singles only.
Made me go from 21 to 23 fps (initial view, 1002 triangles).
Alternatively you multiply all "1/z" and the corresponding deltas with a fairly big number and make them (and your zbuffer) integer.
ah ! Okay I get It now :) It works perfectly :D (excepted I have some "Suspicious pointer assignement" warnings)
Well I'm on all those optimizations then :)
edit : the v0.06 will also have a false phong shader :D (without a big FPS loss)
Unbelievalble ... project is very cool.
Just out of curiosity .. current version is v0.06.
What is the roadmap to V1.0 ???
heya benny :) thanks mate !
Well, my objective for the v1.0 is to have a working, optimized, complete engine, easily usable and modifiable by anyone (with two modes : RT for realtime rendering (games ?) and CG for Computer graphics rendering (supersampling, maybe true phong, why not some reflections, refractions, env. map, Ambiant Occlusion, ...)) and all this, without any bug or flaw .. Of course it can take a veeery long time for those versions to be released ^^ Useless to say there's a huge probability we'll never see those versions :xmas:
However, The main roadmap for the next versions consists in Optimization, OBJ Loader and texture filtering (yeah ... still have to code it lol)
The image of the fake phong looks fabulous, nice one!
It sounds like you still have a lot of work planned for it too... I bet you never planned on doing all this when you started :)
thanks shocky :D
QuoteI bet you never planned on doing all this when you started Smiley
uhuh totally ^^ When I started, I guess I just wanted to code some texture mapped stuff, maybe little intro with it and nothing more .. ;D
Very nice and I love those Checkboard 8) 8)
Will there be any shadow reflections to the checkboard? i.e. Ball shadow that relections to the checkboard :) :)
thanks :)
Shadows/Reflections/Refraction .. I'd like to implement them :) But I don't think it'll be real-time .. We'll see :)
A little screen of an improvement of the fake phong shading (highlights follow your view)
Because the "highlight" is a reflection, it doesn't interact with the texture-color (add instead of multiply).
For all the round stuff, environment mapping probably works better and gives some amount of "reflections" for free (compare here (http://hellfire.untergrund.net/stuff/torus.zip)).
Shadows and reflections were very well managable in realtime over a decade ago, just have a look at this (http://www.pouet.net/prod.php?which=312) or that (http://www.pouet.net/prod.php?which=1285) - refraction is a little bit trickier, though.
QuoteBecause the "highlight" is a reflection, it doesn't interact with the texture-color (add instead of multiply).
Yeah, but I didn't find how to change it for now (since highlight is calculated per vertex and directly fed in the (Blinn I guess) lighting equation )
QuoteFor all the round stuff, environment mapping probably works better and gives some amount of "reflections" for free (compare here).
That .. is .. bloody .. awesome o___O Yeah, I'd like to add some environment mapping, but the bump mapping does a huge work too in "awesomizing" this torus :D
QuoteShadows and reflections were very well managable in realtime over a decade ago, just have a look at this or that - refraction is a little bit trickier, though.
Both didn't run on my PC :S
The v0.06 is almost ready :D Now I'm just wondering if I must release it and add next optimizations/changes in the v0.07 or wait for those changes to be done ..
Release it! :)
uhuh I'm glad to see your enthusiasm ^^ Okay, I'll release it this evening, when I'll be back home :)
QuoteI didn't find how to change it for now (since highlight is calculated per vertex and directly fed in the lighting equation)
Usually diffuse and specular components are interpolated individually across the triangle.
Since the specular highlight (with large exponent) covers only a few triangles, you can either draw those in a separate pass (with additive transparency) or call a different polyfiller if one of the vertices has specular>EPS.
QuoteThat .. is .. bloody .. awesome o___O
thanks :)
Notice the two profiling counters (measured in million-cpu-cycles)
The first shows the time required for vertex-transformation & clipping, the second for filling the polygons.
This way it's easy to find good places to start optimization.
QuoteBoth didn't run on my PC :S
You can try dosbox (http://www.dosbox.com).
Heya :)
QuoteUsually diffuse and specular components are interpolated individually across the triangle.
Since the specular highlight (with large exponent) covers only a few triangles, you can either draw those in a separate pass (with additive transparency) or call a different polyfiller if one of the vertices has specular>EPS.
oh yeah okay, I'll change that in the next release, thanks :)
QuoteYou can try dosbox.
ah yep, didn't think about it ^^
Well, as promised, here is the v0.06 :D Lots of stuff changed : new example files, faster, a new fake phong shader, ...
-
Download Rar(Src + bin + tex + mipmaps) (http://narky00.free.fr/NewSite/MyFiles/Engine_v0.06.rar) -
Sorry, too big to post it here :S (Next time, I'll suppress the mipmaps to gain some space, the engine generates them itself)
Quotev0.06 Changes List
MAJOR :
- Camera is totally handled by matrices (movement + rotation).
- Huge Render Optimization (Won almost 10 FPS)
- NEW SHADER :D => FAKE_PHONG_SHADER : lighting is more realistic
Minor :
- replaced Materials copy by a List of Materials
- Different optimizations (GenerateSphere(), Handle_Super_Sampling, ZBuffer, ..)
- Added Generate_Klein_Bottle Sub
- New Material property : Mat_No_Culling : Will render this material without backface culling (useful to render open shapes/surfaces)
- New Examples
Known bugs :
- Objects with Opacity <> 1 must be projected at last.
- There's a problem with the Klein Bottle normals
- PreCalc_Object_Normals() sub anormaly slow
Attached *.rar :)
Really nice!
The camera-handling works really well now and even the supersampled versions have "interactive framerates" :)
Definately a huge improvement in framerate Hezad, this is quite a leap ahead from the last version you made :)
My favourite part of your engine is the way that you have implimented the fog. It's excellent and looks exactly the same as the standard opengl implimentations of it.
I know that I said abou the fog looking great before, but it really does look good with all the nice rendermodes now.
Fake phong is fast as well.
thanks a lot mates :)
I noticed that all triangles have a flag "IsInverted" which is used to flip orientation and causes special-case-handling at many places.
If you want to flip a triangle which is made up by the vertices v1,v2,v3, you can simply change the order of the vertices, for example v1,v3,v2.
In Culling_Visible you don't need to normalize viewpos and viewposnormal.
You can also simplify the computation for ViewPosNormal:
ViewPosNormal.x = Tri.Vertex(1).Viewpos.y*Tri.Vertex(2).Viewpos.z - Tri.Vertex(1).Viewpos.z*Tri.Vertex(2).Viewpos.y
ViewPosNormal.y = Tri.Vertex(1).Viewpos.z*Tri.Vertex(2).Viewpos.x - Tri.Vertex(1).Viewpos.x*Tri.Vertex(2).Viewpos.z
ViewPosNormal.z = Tri.Vertex(1).Viewpos.x*Tri.Vertex(2).Viewpos.y - Tri.Vertex(1).Viewpos.y*Tri.Vertex(2).Viewpos.x
A better way is to test culling in object-space, but I guess that's a bit too far out at the moment.
Objects with higher tesselation won't benefit much from perspective interpolation.
For rather small triangles (and those with small delta-z) you can use linear interpolation.
Try to separate your vertex-data from the triangles. At the moment you're still transforming 6 times as many vertices as would be required.
Also try to do as few transformations on your vertices as absolutely necessary.
Actually there's no need to touch vertices in Rotate_Object.
Just build a rotation-matrix, multiply it with the camera-matrix and do the complete transform (with a single matrix) in RotateViewPos (already mentioned here (http://dbfinteractive.com/forum/index.php?topic=3445.msg49371#msg49371)).
When calculating lighting (where you require .pos3d) you can transform the lights into camera-space (or object-space).
You can generally speed up vector-normalization (extensively used in the lighting-calculation) with the method described here (http://dbfinteractive.com/forum/index.php?topic=3669.0).
Since the specular exponent is constant, you can find an lower bound for (N*H) so that (N*H)^e<EPS.
You're now storing an id of the material in your triangle-structure. It's more efficient to do it the other way round:
In your material-structure store a list of pointers to all meshes which got this material applied.
This way you save the (rather complex) test which material is currently active for every triangle.
You might consider upgrading to fbc 0.2 and use command-line parameter "-fpu sse" to gain ~5fps.
The more I look at freebasic's asm-output, the more I think this will become a good opportunity for you to learn assembler ;)
well, thanks (again) for those advises :) I'll think about each of them.
Concerning learning Assembler .. I'd like to, but I won't have time lol It will also be more and more difficult for me to upgrade this engine (I found a job lol, and I have lots of projects aside).
But of course, I stay in the course :p I'll continue to work on it but releases will be even more spread in time :S
@hellfire : a question : If I turn on the fpu option when compiling the examples, will they run on most/every machines ?
@shockie : by the way, thanks for the fog :) I red a really useful (and bloody interresting) paper on the subject, you can find it here :
http://www.cescg.org/CESCG-2004/web/Zdrojewska-Dorota/
QuoteIf I turn on the fpu option when compiling the examples, will they run on most/every machines ?
It won't run on anything older than Pentium3 (1999) or AthlonXP (2001).
A benefit of using SSE (http://en.wikipedia.org/wiki/Sse) for floating-point operations is that MMX (http://en.wikipedia.org/wiki/MMX_(instruction_set)) (which shares its registers with the fpu) can be used for pixel-processing.
I don't have anything useful to offer, but this is a fascinating thread, and you're doing a fantastic job with the advice of these experts, Hezad! It's hard to see many practical reasons for software rendering these days, but I still find it fascinating, and I have tons of respect for people who implement it. I also love the visual idiosyncracies that software rendering can offer over standard DirectX/OpenGL rendering. All games should look like Zeewolf! http://amiga.probers.info/images/stories/predseda/zeewolf205.gif (http://amiga.probers.info/images/stories/predseda/zeewolf205.gif)
I do think (well, I hope) that Intel's Larrabee (and the competition's resulting efforts) will offer a genuine avenue for such in-depth software rendering knowledge in the future:
http://arstechnica.com/articles/paedia/gpu-sweeney-interview.ars (http://arstechnica.com/articles/paedia/gpu-sweeney-interview.ars)
http://www.anandtech.com/cpuchipsets/intel/showdoc.aspx?i=3367&p=1 (http://www.anandtech.com/cpuchipsets/intel/showdoc.aspx?i=3367&p=1)
You will be one of the very few people in the world who are able to take advantage of such arcane knowledge!
@hellfire : oh yeah okay, thanks :) I'll make a fpu and a "non-fpu" compilations of examples for the next release :D
@DruggedBunny : Heya :) thanks a lot for your kind words. These papers are very interesting :D
A little screen from the v0.07 in progress :D The fake phong shader has been improved !
Very shiny! the shapes look like they are more solid now.
Quotethe shapes look like they are more solid now.
Yep :D The way I "add" (specular) and (diffuse + ambiant + texture data) is not perfect though, I must work on it, but it's a first step :D
That looks great, Hezad! Now all that's missing are shadows ;D
Quote from: hellfire on November 21, 2008
That looks great, Hezad! Now all that's missing are shadows ;D
I suspect that Hezad will try them soon... He's run out of other things to add to the engine now I think.
QuoteThat looks great, Hezad! Now all that's missing are shadows Grin
QuoteThat looks great, Hezad! Now all that's missing are shadows Grin
I suspect that Hezad will try them soon... He's run out of other things to add to the engine now I think.
uhuhuh thanks ;D In fact I still have optimizations to code :S but yeah, shadows are already planned ^^ Just don't know when yet.
However, there're still a lot of cool 3D "concepts" I'd like to add :p
(not in any order)
- Env. Mapping
- real phong
- Shadows (of course :p)
- dynamic (in an utopic world :P static otherwise) ambient occlusion
- reflexion/refraction (so raytracing, and scene handling)
- other stuff I'm not thinking about right now ...
I already red several articles/tuts/... about those different steps and I get them roughly but I just don't really know what to code next. Of course some stuff are way more difficult to handle than others ...
I'll start with a question about shadows if you don't mind ^^
Can I really envisage to add
dynamic shadows in
real-time in a software 3d engine without loosing a huge amount of FPS ? Obviously without using 'cpu-costly' shaders (using Gouraud shader max no phong or fake phong) :)
Do you want objects to be able to cast shadows upon other objects and themselves or are you looking to simply project a shadow onto a plane?
Probably the method you want to impliment is shadow volumes.
For each light source you have, you need to project a ray from the light source, passing through every vertex in the object you are testing to some point outside your scene (infinity). You can then test the vertices in your scene to see if they lie inside the shadow volume, if they do then they are in shadow and you can colour accordingly.
This would work quite nicely for you without too much cost.
Yeah, I'd like the objects to cast shadows on other objects and themselves :)
I red some stuff about shadow volumes too.
QuoteThis would work quite nicely for you without too much cost.
I'll give it a try then :D
Hi hezad, nice work you've done on this. Stencil shadows (volumes) are definitely do-able in software although you'd probably be best to try to keep the number of large shadow casting objects to a minimum and in some cases you can simplify the shadow casting geometry but it can lead to some problems particularly with self shadowing objects.
A couple of things I would recommend looking up are sillouette edges and z-fail, using z-pass doesn't work well if the camera is inside a shadow volume.
Since the technique needs at least a depth pass of the scene initially I did my texturing and shading interpolation in that pass also but instead of doing the shading calcs I stored the colour as 24 bit and the shading as 8 bit (I came up with a bit of a dodgy method of storing the diffuse and specular in 8 bits).
Then filled the shadow volumes, front facing triangles increment the value in the stencil buffer if the z test fails and backfacing triangles decrement the value if z test fails.
Then in a single pass over the screenbuffer did my shading calcs for each pixel using the values in the stencil buffer to determine whether the pixel was in light or shadow.
I could never get this to work with anything transparent.
One way to add simple planar reflections for example to the floor is to make the floor transparent and place an inverted copy of your scene underneath.
Shadow-volumes are a serious fillrate-killer and since you need a z-pass anyway, you're sometimes better off with zbuffer-shadows. Both techniques have their pros & cons, though.
thanks for your advices, I'm still checking a lot of ressources on the net about it to understand the vocabulary and the different algorithms :)
In the same time i'm pretty interested by Occlusion culling :P So i'm thinking about a way to use both shadows and occlusion culling in the same time, that'd be bloody awesome :D
Even though shadow volumes suffer from hard edges I still prefer that over the aliasing of other methods and as I said it's possible to do it in a single pass of the scene so the impact on using shadows only depends on how many objects are actually casting shadows and what size they are, too many or too large and yep, fillrate becomes a problem. Still, it's all down to personal preference and some methods might be preferable depending on the application.
Here's a small example done in freebasic I've dug out, sorry I can't find much else atm.
heya thanks for the exe !! it's pretty attractive ^^
Oooh, I hadn't seen that example before Stonemonkey.
It runs at a very good framerate.
Quoteshadow volumes suffer from hard edges
That's not so much of problem when several light-sources are blending together (have a look here (http://hellfire.untergrund.net/stuff/shader2test.rar), it's accelerated though).
Distance attenuation makes the hard edges less obvious and near to a light-source shadow-edges are pretty hard anyway.
But a proper lighting-rig that doesn't look totally synthetic requires a considerable amount of light-sources; not all lights require shadow-casting though, but one is quickly multiplying fillrate to unreasonable amounts (especially when filling in software), not to mention enabling multisampling to make the hard edges a bit softer.
I haven't yet managed to integrate shadow-volumes with envmap-based lighting authentically.
Shadow-maps on the contrary have one major benefit: You can look up a pixel's surrounding in the map and decide the amount of shadowing without much cost.
Still both methods are useful to have.
Ah very nice hellfire, I'd done some stuff with multiple lightsources and volume shadows in hardware too but it might be a bit too much work to get out of software rendering.
I'd also looked into shadow maps but never attempted it as it still looked to me like a lot of additional work, particularly for dynamic shadows.
Quote
Shadow-maps on the contrary have one major benefit: You can look up a pixel's surrounding in the map and decide the amount of shadowing without much cost.
even with software? I've been trying bi linear filtering which costs a little over nearest pixel but to add more interpolation as well as multiple lookups from the z buffer and then calcs to work out the shadow value could add a fair bit.
Quoteit might be a bit too much work to get out of software rendering
For the beginning you can keep the software-engine as it is and just call "someone else's" polyfiller.
On the long run it's inevitable to rearrange most of the data-structure, though.
It can be quite deflating to see how much faster that damn hardware is in comparism to a seemingly well-optimized software-polyfiller...
Quoteshadow maps [...] looked to me like a lot of additional work
Since you need to draw a z-pass from the light's point of view, it requires some sensible concept to use a framebuffer's zbuffer as a texture without making the renderloop a complete mess.
Unluckily inheritance isn't implemented in FreeBasic yet, so I can't really suggest an aesthetic solution here.
QuoteQuotelook up a pixel's surrounding...
to add more interpolation as well as multiple lookups from the z buffer and then calcs to work out the shadow value could add a fair bit.
For hard-edged shadows you'd test if the current pixel's distance from the light is greater than the distance stored in the shadowmap, so it's a simple compare (to avoid the compare it's even just the sign-bit of the difference). Usually the z-values can be stored with enough precision in 16bits, so a 2x2 area only requires reading of two dwords. There's no interpolation required here, you just count how many samples are lit. Creating a few levels of mipmaps from the shadowmap can increase the sampling area without much cost.
To avoid out-of-range tests when accessing the shadowmap in the innerloop, I clipped all polygons at the borders of the shadowmap.
Quote
There's no interpolation required here
This is maybe where I'm getting confused, how do you know which pixels to test from the shadow map?
Sorry for the confusion; of course you need to interpolate coordinates for the (projective or cubic) shadowmap, you just won't need color-interpolation in the amount bilinear-filtering requires.