You are on page 1of 7

BuildingaSimple3DEnginewithSilverlight

09February2009
byVladimirBodurov

Evenifyouintendonlytouseexisting3Denginesinyourapplications,italwayshelpstounderstand
howtheywork.Vladhopesthatyou'taketheredpill',andlearnhowtorenderthreedimensional
objectswithC#inSilverlightwithouttheuseofanypreexisting3Dengine.
Thisarticlewilldemonstratehowtobuildasimple3Denginefromscratch,usingC#andSilverlight,whichwillallow
youto:
Drawagroupoflinesinthreedimensions
Rotatethoselinesaroundacentralpoint
Addorremovelines
Youcanseethefinishedapplicationhere
Inordertohelpdemonstratehowthesetechniqueswork,IhavedevelopedaSilverlightdemonstrationapplication,
calledtheVectorVisualizer,ontopofthe3Denginediscussedinthisarticle.Youcanseeitinactionhereand the
completesourcecodeisavailableatCodeplex.Mygoalistoshowyouhowmatrixmathsisusedtoproducesome
simple3Dprojectionsandrotationsandtoconvinceyouthatthisisactuallynotasdifficultataskasonemaythink.
However, you may be asking yourself why you would need such a tool in the first place. Isn't this a little bit like
reinventingthewheel?Afterall,aren'ttheremanyfullydeveloped3Denginesalreadyoutthere?Onaphilosophical
note,Idon'treallysubscribetotheideathat"reinventingthewheel"isalwaysabadthing.Ofcourse,therearetimes
whendeadlinepressuresmakereuseofexistingcodeamuchmoreattractiveoptionbut,ontheotherhand,canyou
evenfullyunderstandthewheelifyounevertrytoreinventit?Andifnooneevertriedtoreinventit,wouldn'twebe
usingthesameoldwoodenwheelspeopleusedthousandsofyearsago?
Onamorepracticalnote,theremaybetimeswhenafullblown3Denginemightbeoverkillforyourrequirements.If
youwanttoimplementalimited3Dfeaturethatmaynothavealltheaspectsofafullyfunctional3Denginebutis
moreefficientintermsofperformance,orifyouneedtoperformasingletransformationofa2Dobjecttosimulate3D
effect,thenthisarticlewilllaythefoundationknowledgeyouneedtodothat.Evenifyouchosenottoimplementthem
inyourowncode,andinsteaduseexternallibraries,thebasicunderstandingofhowitallworkswillgiveyoumore
confidenceandclaritywhenusingthoselibraries.

VectorsandMatrixes
Thebasicgoalofany3Dengineistobeabletoshowtheprojectionofathreedimensionalobjectintheplaneofthe
observer. In order to achieve this goal in building our engine, we need first to understand a few linear algebra
concepts.
NOTE:
Forthoseinterestedtodelvingdeeperintolinearalgebraconcepts,IwouldrecommendthelecturesofProf.
GilbertStrangfromtheMassachusettsInstituteofTechnology.TheyarefreelyavailableonYouTubehere
Inthisarticle,Iwantfocusonimplementingonlybasic3Dfunctionality,somorecomplexissuessuchaszorderand
drawingsurfaceswillnotbecovered.Assuch,wewillonlyneedtoanswerthefollowingcorequestions:
1. Whatisamatrix?
2. Whatisavector?
3. HowdoImultiplyvectorbymatrix?
4. HowdoImultiplymatrixbymatrix?
Amatrixisarectangulartableofnumbersorfunctions.Inourcase,avectorwillrepresentapointinthreedimensional
space.Ourbasicgoalistobeabletoplotathreedimensionalpointandthen,usingamatrix,trackitsprojectionona
givenplaneaswerotatethepointin3Dspace.

Iamsurethatthevastmajorityofpeoplereadingthisarticlewillbefamiliarwiththemovie,TheMatrix,withNeoflying
aroundandshootingevilagents.I'malsosurethatmanyofyouhaveheardanalogiesmadetothisfilmbefore,when
thetopicofmatrixeshasbeenraised.However,Iaskyoutobearwithmeonemoretime,asitreallycanshedsome
lightonthissubject.
Themovieplothingesontheideathathumansliveinavirtualreality,withtheirentireworldbeingcontrolledbya
programcalledMatrix.Peoplegoabouttheirdailylife,completelyunawareofthecontrollinginfluencethatthematrix
hasovertheirlives.Likewise,ontheplaneofanormalobserver,changesintheprojectionofapointrotatingin3D
spacecanappearrandomandunpredictable.However,infact,thesepatternsarepreciselyandaccuratelypredicted
bymatrixoperations.Foronewhocan'see'theMatrix,likeoursuperheroNeo,theworldisgovernedbyanabstract
principleinvisiblefortheothersbutobvioustohim.
Andnowfortheimportantquestion:doyouwanttheblueortheredpill?Ifyouchoosethebluepill,thenstopreading
thisarticle,godownloadany3Dengineandstoptryingtounderstandhowitallactuallyworks.Ifyouchoosethered
pillthenstayinwonderlandandreadon.

OperationswithMatrixes
Nowthatwe'veestablishedthatthematrixdeterminesthechangesinprojectionofourthreedimensionalpoint,asit
rotates,wecangetdeeperintothemechanismsofhowitdoesso.
Saywewanttorotateapointby30degrees,aroundtheaxisZ.First,weneedtocreatethematrixthatcanperform
suchanoperation.Inotherwords,weneedtofindthematrixthatwilltransformathreedimensionalpointsuchthatit
isrotated30degreesaroundthezaxis.
We then multiply the 3D point (vector) by
thismatrixtoproduceanotherpointthathas
beenrotatedappropriately.

Figure:1

Wecanalsomultiplyonematrixbyanother
matrix. Let's say we know the matrix that
rotatesapointby30degreesaroundZand
we know the matrix that rotates a point by
10degreesaroundY.Ifwesimplymultiply
thosetwomatrixestogether,wearriveata
singlematrixthatdoesboth:rotatesapoint
by30degreesaroundZandby10degrees
aroundY.
Theseexamplesillustratethatthetwooperations
we need to understand in order to build our 3D
engineare:
1.

howtomultiplyavector(inourcasea
3Dpoint)byamatrix,

2.

howtomultiplyonematrixbyanother
matrix.

Figure:2

VectorMatrixoperations
Wewilluseathreerowbythreecolumnmatrix,reflectingthefactthatourpointislocatedinthreedimensions,andour

vectorwillbeathreedimensionalpoint(X,Y,Z).So,let'sfirstseehowtomultiplyapointbyamatrix.Surprisingly,itis
quiteeasyandstraightforward.Hereisanexampleofsuchamultiplication:
Here we have an arbitrary matrix acting on a
Figure:3
point with coordinates X = 5, Y = 1, Z = 2. We
multiply each element of each row of the matrix
byeachelementofthevectorinordertoobtain
newvaluesof(X,Y,Z)forthetransformedpoint.
So, for example, we multiply column 1 row 1 of
thematrixbyX,column2row1byY,andcolumn
3row1byZ.WethensumthesevaluestogetthevalueofXforthetransformedpoint.Werepeattheprocessforrows
2and3toarriveatthetransformedvaluesofYandZ.

MatrixMatrixoperations
Themultiplicationoftwomatriceslooksverysimilar.Hereisoneexample:
Asyoucanseewemultiplyeach
elementoftherowofthefirstmatrixby
eachelementofthecolumnofthe
secondmatrix.Werepeatthatforallthe
rowsandcolumns.

Figure:4

TheIdentityMatrix
Thefinalconceptthatneedstobeintroduced,beforewemoveontocreatingmatrixesforrotatingapointaroundX,Y
orZ,isthatoftheIdentityMatrix.ThematrixesthatIwillusewillbebuiltontopoftheidentitymatrix.Thefollowing
figuredepictsa3x3identitymatrix:
Allofthenumbersinthematrixarezero,exceptthenumbersinthetoplefttobottomrightdiagonal,
whicharesetto1.Ifyoutrytomultiplyanymatrixbytheidentitymatrixyouwillgetthesamematrix,just
asanynumbermultipliedbyonegivesthesamenumber.

WritingMatrixOperationsinC#
ItisnowtimetotranslatethesematrixconceptsintoarealC#code.InthesourcecodethatIreferencedatthestartof
thearticle,youcanfindthefollowingobjects:
Point3Dobjectthisstores(X,Y,Z)valuesofa3Dpoint.Wewilluseittostoreboththeinitialandcurrent
valuesforX,YandZ.LaterIwillexplainwhyweneedtostoreboth.
Matrix3DobjecttheMatrix3Dobjectthathasprivateproperty,_matrix,whichistwodimensionalarrayof
typedouble.Inoneofthedimensionswewillstoretherows,andintheotherthecolumns,ofthematrix.
WecanseeheretheeleganceofC#,inthatitallowsustouseoperatoroverloadsforthemultiplicationofmatrixand
vector.Inourcase,wewillsimplymultiplytheMatrix3DbythePoint3Dobjects.Followingistheimplementationofthis
operation:
publicstaticPoint3Doperator*(Matrix3Dmatrix1,Point3Dpoint3D)

{
varx=point3D.InitialX*matrix1._matrix[0,0]+
point3D.InitialY*matrix1._matrix[0,1]+
point3D.InitialZ*matrix1._matrix[0,2]
vary=point3D.InitialX*matrix1._matrix[1,0]+
point3D.InitialY*matrix1._matrix[1,1]+
point3D.InitialZ*matrix1._matrix[1,2]
varz=point3D.InitialX*matrix1._matrix[2,0]+
point3D.InitialY*matrix1._matrix[2,1]+
point3D.InitialZ*matrix1._matrix[2,2]
point3D.X=x
point3D.Y=y
point3D.Z=z
returnpoint3D
}

ThereasonthatIstoreboththeinitialandcurrentvaluesof(X,Y,Z)isthatIalwaystransformacurrentpointfromthe
initial coordinates rather than the current coordinates. This is because during each transformation there will be a
certainlossofprecision.ThismeansthatifIrotateapointbyacertainangleandthenrotateitbackbythesameangle
but with opposite sign, I will not return to the initial point because of that loss of precision. In order to avoid these
complications,Ialwaysperformtransformationsfromtheinitialpointposition.
Nowlet'sseehowwewouldhandlethemultiplicationoftwomatricesinC#:
publicstaticMatrix3Doperator*(Matrix3Dmatrix1,Matrix3Dmatrix2)
{
varmatrix=newMatrix3D()
for(vari=0i<3i++)
{
for(varj=0j<3j++)
{
matrix._matrix[i,j]=
(matrix2._matrix[i,0]*matrix1._matrix[0,j])+
(matrix2._matrix[i,1]*matrix1._matrix[1,j])+
(matrix2._matrix[i,2]*matrix1._matrix[2,j])
}
}
returnmatrix
}

ThefollowingC#codeisusedtosetthecurrentmatrixasidentitymatrix:
publicMatrix3DMakeIdentity()
{
this._matrix[0,0]=this._matrix[1,1]=this._matrix[2,2]=1
this._matrix[0,1]=this._matrix[0,2]=
this._matrix[1,0]=this._matrix[1,2]=
this._matrix[2,0]=this._matrix[2,1]=0
returnthis
}

IcallthismethodintheconstructoroftheMatrix3DobjectbecauseallthematrixesIusearebuiltontopoftheidentity
matrix.
Now,weneedtowritethecodeforthematrixthatwillrotateour3DpointaroundtheaxesX,YorZ.Infact,thereare
threematrices,oneforeachplane,andeachoneisbuiltontopoftheidentitymatrix:

Figure:6

TranslatingthisintoC#,wegetthefollowingcode
publicstaticMatrix3DNewRotateAroundX(doubleradians)
{
varmatrix=newMatrix3D()
matrix._matrix[1,1]=Math.Cos(radians)
matrix._matrix[1,2]=Math.Sin(radians)
matrix._matrix[2,1]=(Math.Sin(radians))
matrix._matrix[2,2]=Math.Cos(radians)
returnmatrix
}
publicstaticMatrix3DNewRotateAroundY(doubleradians)
{
varmatrix=newMatrix3D()
matrix._matrix[0,0]=Math.Cos(radians)
matrix._matrix[0,2]=(Math.Sin(radians))
matrix._matrix[2,0]=Math.Sin(radians)
matrix._matrix[2,2]=Math.Cos(radians)
returnmatrix
}
publicstaticMatrix3DNewRotateAroundZ(doubleradians)
{
varmatrix=newMatrix3D()
matrix._matrix[0,0]=Math.Cos(radians)
matrix._matrix[0,1]=Math.Sin(radians)
matrix._matrix[1,0]=(Math.Sin(radians))
matrix._matrix[1,1]=Math.Cos(radians)
returnmatrix
}

Inordertocreateasinglematrixthatrotatesapointinallthethreeaxes,simultaneously,wesimplymultiplythese
threematricestogether,asfollows:
publicstaticMatrix3DNewRotate(doubleradiansX,doubleradiansY,doubleradiansZ)
{
varmatrix=NewRotateAroundX(radiansX)
matrix=matrix*NewRotateAroundY(radiansY)
matrix=matrix*NewRotateAroundZ(radiansZ)
returnmatrix
}

RadiansorDegrees?
YouwillhavenoticedthatIampassinganglesinradians.Radiansdescribetheanglesasarelationshipbetween
radiusandthelengthofanarclyingonanimaginarycirclearoundtheanglewithinthisangle.However,becausewe
liveonaplanetthatrevolvesapproximately360timesaroundit'saxisforeachrotationaroundourlovelystar,thesun,
our ancestors decided to split the circle into 360 parts and call each part a degree. So, out of respect for the
knowledgeandwisdomofourancestors,we'llusedegreesfortheangleswepasstothematrix.Inordertodothat,I
havecreatedautilityfunctionthattransformsdegreesintoradiansasfollows:
publicstaticdoubleDegreesToRadians(doubledegrees)
{
return(Math.PI/180)*degrees
}

Andinthiscasearotationmatrixcanbecreatedfromangleindegreesasfollow:
publicstaticMatrix3DNewRotateByDegrees
(doubledegreesX,doubledegreesY,doubledegreesZ)
{
returnNewRotate(
Angle.DegreesToRadians(degreesX),
Angle.DegreesToRadians(degreesY),
Angle.DegreesToRadians(degreesZ)
)
}

PerspectiveEffect
Once we have multiplied the 3D point coordinates by the desired matrix, we can simply assign the new X and Y
valuestoapointdrawnonthescreen,asfollows:
PointOnTheScreen.X=TransformedPoint3D.X
PointOnTheScreen.Y=TransformedPoint3D.Y

However,becausethecentralpointaroundwhichthepointsrotateisnotnecessarilythe(0,0)pointonthescreenwe
havetoadjustthepositionofthispointbyaddingtoitthedistancefromthe(0,0)pointonthescreen.Wedothatas
follows:
PointOnTheScreen.X=TransformedPoint3D.X+RotationCenterPoint.X
PointOnTheScreen.Y=TransformedPoint3D.Y+RotationCenterPoint.Y

Thisisallweneedtodoinordertogetthebasic3Dtransformation.However,ifwewanttohavemorerealistic3D
Figure:wealsohavetoconsidertheperspectiveeffects.Thecloseranobject,orpartoftheobjectisthebiggeritwill
appear.Likewise,moredistantobjectswilllooksmaller.
Thefollowingformulawilltransformtheobjectinsuchawaysotomakeacloserone(onewithhigherZvalue)tolook
biggerandmoredistantone(withsmallerZ)tolooksmaller:
PointOnTheScreen.X=
TransformedPoint3D.X*Coefficient/(TransformedPoint3D.Z+Coefficient)+
RotationCenterPoint.X
PointOnTheScreen.Y=
TransformedPoint3D.Y*Coefficient/(TransformedPoint3D.Z+Coefficient)+
RotationCenterPoint.Y

Inthisway,Icanincreaseordecreasethecoefficienttoaltertheeffectofthatchange.Ifthecoefficientissmalleritwill
lookasiftherotatingobjectislocatedatacertaindistancefromtheeyesoftheobserver.Ifthecoefficientishigherit
willlookasifitisveryclosetotheeyesoftheobserver,orthatitisverybiginsize.Thebiggerobjectswillexperience
thatchangemorethanthesmallerones.
Inthesourcecode,thisfunctionalityisimplementedinthemethod,MapToPoints,oftheclassLine2D.Thismethod
performs the mapping from the three dimensional point of our object to a two dimensional point on the computer
screen.

Conclusion
Once you wrap your head around a few basic operations with matrixes, implementing threedimensional
transformationsisquiteeasy,3stepprocess:
1. Createthedesiredtransformingmatrixbymultiplyingdifferentmatrices
2. Multiplythatmatrixbyapointin3D
3. Assignthatpointtoa2Dscreenpointbyapplyingperspectiveformula
IhopeI'veprovidedsomehelpfulinsightsinto3Dtransformations,whichwillbeusefultoyouinwritingyourown3D
engine, or in understanding how a third party engine works. Hopefully, you'll now look at the full source code
hereandtryouttheapplicationhere.

You might also like