It will ONLY run on cards that support the stencil buffer. ... The trick behind creating a real looking reflection that reflects in the floor and nowhere ...
HOME
TWITTER
FACEBOOK
RSS
ATOM
FORUM
Clipping&ReflectionsUsingTheStencilBuffer
Welcometoanotherexcitingtutorial.ThecodeforthistutorialwaswrittenbyBanuOctavian.Thetutorialwasofcoursewrittenbymyself(NeHe).InthistutorialyouwilllearnhowtocreateEXTREMELYrealisticreflections.Nothingfakehere!Theobjectsbeingreflectedwillnotshowupunderneaththefloororontheothersideofawall.Truereflections!
Averyimportantthingtonoteaboutthistutorial:BecausetheVoodoo1,2andsomeothercardsdonotsupportthestencilbuffer,thisdemowillNOTrunonthosecards.ItwillONLYrunoncardsthatsupportthestencilbuffer.Ifyou'renotsureifyourcardsupportsthestencilbuffer,downloadthecode,andtryrunningthedemo.Also,thisdemorequiresafairlydecentprocessorandgraphicscard.EvenonmyGeForceInoticethereisalittleslowdownattimes.Thisdemorunsbestin32bitcolormode!
Asvideocardsgetbetter,andprocessorsgetfaster,Icanseethestencilbufferbecomingmorepopular.Ifyouhavethehardwareandyou'rereadytoreflect,readon!
Thefirstpartofthecodeisfairlystandard.Weincludeallnecessaryheaderfiles,andsetupourDeviceContext,RenderingContext,etc.
#include //HeaderFileForWindows
#include //HeaderFileForTheOpenGL32Library
#include //HeaderFileForTheGLu32Library
#include //HeaderFileForTheGlauxLibrary
#include //HeaderFileForStandardInput/Output
HDC hDC=NULL; //PrivateGDIDeviceContext
HGLRC hRC=NULL; //PermanentRenderingContext
HWND hWnd=NULL; //HoldsOurWindowHandle
HINSTANCE hInstance=NULL; //HoldsTheInstanceOfTheApplication
Nextwehavethestandardvariablestokeeptrackofkeypresses(keys[]),whetherornottheprogramisactive(active),andifweshouldusefullscreenmodeorwindowedmode(fullscreen).
bool keys[256]; //ArrayUsedForTheKeyboardRoutine
bool active=TRUE; //WindowActiveFlagSetToTRUEByDefault
bool fullscreen=TRUE; //FullscreenFlagSetToFullscreenModeByDefault
Nextwesetupourlightingvariables.LightAmb[]willsetourambientlight.Wewilluse70%red,70%greenand70%blue,creatingalightthatis70%brightwhite.LightDif[]willsetthediffuselighting(theamountoflightevenlyreflectedoffthesurfaceofourobject).Inthiscasewewanttoreflectfullintensitylight.LastlywehaveLightPos[]whichwillbeusedtopositionourlight.Inthiscasewewantthelight4unitstotheright,4unitsup,and6unitstowardstheviewer.Ifwecouldactuallyseethelight,itwouldbefloatinginfrontofthetoprightcornerofourscreen.
//LightParameters
staticGLfloat LightAmb[]={0.7f,0.7f,0.7f,1.0f}; //AmbientLight
staticGLfloat LightDif[]={1.0f,1.0f,1.0f,1.0f}; //DiffuseLight
staticGLfloat LightPos[]={4.0f,4.0f,6.0f,1.0f}; //LightPosition
Wesetupavariablecalledqforourquadraticobject,xrotandyrottokeeptrackofrotation.xrotspeedandyrotspeedcontrolthespeedourobjectrotatesat.zoomisusedtozoominandoutofthescene(westartat-7whichshowsustheentirescene)andheightistheheightoftheballabovethefloor.
Wethenmakeroomforour3textureswithtexture[3],anddefineWndProc().
GLUquadricObj *q; //QuadraticForDrawingASphere
GLfloat xrot =0.0f; //XRotation
GLfloat yrot =0.0f; //YRotation
GLfloat xrotspeed =0.0f; //XRotationSpeed
GLfloat yrotspeed =0.0f; //YRotationSpeed
GLfloat zoom =-7.0f; //DepthIntoTheScreen
GLfloat height =2.0f; //HeightOfBallFromFloor
GLuint texture[3]; //3Textures
LRESULT CALLBACKWndProc(HWND,UINT,WPARAM,LPARAM); //DeclarationForWndProc
TheReSizeGLScene()andLoadBMP()codehasnotchangedsoIwillskipoverbothsectionsofcode.
GLvoidReSizeGLScene(GLsizeiwidth,GLsizeiheight) //ResizeAndInitializeTheGLWindow
AUX_RGBImageRec*LoadBMP(char*Filename) //LoadsABitmapImage
Theloadtexturecodeisprettystandard.You'veuseditmanytimesbeforeintheprevioustutorials.Wemakeroomfor3textures,thenweloadthethreeimages,andcreatelinearfilteredtexturesfromtheimagedata.ThebitmapfilesweusearelocatedintheDATAdirectory.
intLoadGLTextures() //LoadBitmapsAndConvertToTextures
{
intStatus=FALSE; //StatusIndicator
AUX_RGBImageRec*TextureImage[3]; //CreateStorageSpaceForTheTextures
memset(TextureImage,0,sizeof(void*)*3); //SetThePointerToNULL
if((TextureImage[0]=LoadBMP("Data/EnvWall.bmp"))&& //LoadTheFloorTexture
(TextureImage[1]=LoadBMP("Data/Ball.bmp"))&& //LoadtheLightTexture
(TextureImage[2]=LoadBMP("Data/EnvRoll.bmp"))) //LoadtheWallTexture
{
Status=TRUE; //SetTheStatusToTRUE
glGenTextures(3,&texture[0]); //CreateTheTexture
for(intloop=0;loop<3;loop++) //LoopThrough5Textures
{
glBindTexture(GL_TEXTURE_2D,texture[loop]);
glTexImage2D(GL_TEXTURE_2D,0,3,TextureImage[loop]->sizeX,TextureImage[loop]->sizeY,0,GL_RGB,GL_UNSIGNED_BYTE,TextureImage[loop]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}
for(loop=0;loop<3;loop++) //LoopThrough5Textures
{
if(TextureImage[loop]) //IfTextureExists
{
if(TextureImage[loop]->data) //IfTextureImageExists
{
free(TextureImage[loop]->data); //FreeTheTextureImageMemory
}
free(TextureImage[loop]); //FreeTheImageStructure
}
}
}
returnStatus; //ReturnTheStatus
}
AnewcommandcalledglClearStencilisintroducedintheinitcode.Passing0asaparametertellsOpenGLtodisableclearingofthestencilbuffer.Youshouldbefamiliarwiththerestofthecodebynow.Weloadourtexturesandenablesmoothshading.Theclearcolorissettoanoffblueandthecleardepthissetto1.0f.Thestencilclearvalueissetto0.Weenabledepthtesting,andsetthedepthtestvaluetolessthanorequalto.Ourperspectivecorrectionissettonicest(verygoodquality)and2dtexturemappingisenabled.
intInitGL(GLvoid) //AllSetupForOpenGLGoesHere
{
if(!LoadGLTextures()) //IfLoadingTheTexturesFailed
{
returnFALSE; //ReturnFalse
}
glShadeModel(GL_SMOOTH); //EnableSmoothShading
glClearColor(0.2f,0.5f,1.0f,1.0f); //Background
glClearDepth(1.0f); //DepthBufferSetup
glClearStencil(0); //ClearTheStencilBufferTo0
glEnable(GL_DEPTH_TEST); //EnablesDepthTesting
glDepthFunc(GL_LEQUAL); //TheTypeOfDepthTestingToDo
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); //ReallyNicePerspectiveCalculations
glEnable(GL_TEXTURE_2D); //Enable2DTextureMapping
Nowit'stimetosetuplight0.ThefirstlinebelowtellsOpenGLtousethevaluesstoredinLightAmbfortheAmbientlight.Ifyourememberatthebeginningofthecode,thergbvaluesofLightAmbwereall0.7f,givingusawhitelightat70%fullintensity.WethensettheDiffuselightusingthevaluesstoredinLightDifandpositionthelightusingthex,y,zvaluesstoredinLightPos.
AfterwehavesetthelightupwecanenableitwithglEnable(GL_LIGHT0).Eventhoughthelightisenabled,youwillnotseeituntilweenablelightingwiththelastlineofcode.
Note:IfwewantedtoturnoffalllightsinascenewewoulduseglDisable(GL_LIGHTING).IfwewantedtodisablejustoneofourlightswewoulduseglDisable(GL_LIGHT{0-7}).Thisgivesusalotofcontroloverthelightingandwhatlightsareonandoff.JustrememberifGL_LIGHTINGisdisabled,youwillnotseelights!
glLightfv(GL_LIGHT0,GL_AMBIENT,LightAmb); //SetTheAmbientLightingForLight0
glLightfv(GL_LIGHT0,GL_DIFFUSE,LightDif); //SetTheDiffuseLightingForLight0
glLightfv(GL_LIGHT0,GL_POSITION,LightPos); //SetThePositionForLight0
glEnable(GL_LIGHT0); //EnableLight0
glEnable(GL_LIGHTING); //EnableLighting
Inthefirstlinebelow,wecreateanewquadraticobject.ThesecondlinetellsOpenGLtogeneratesmoothnormalsforourquadraticobject,andthethirdlinetellsOpenGLtogeneratetexturecoordinatesforourquadratic.Withoutthesecondandthirdlinesofcode,ourobjectwoulduseflatshadingandwewouldn'tbeabletotextureit.
ThefourthandfifthlinestellOpenGLtousetheSphereMappingalgorithmtogeneratethetexturecoordinates.Thisallowsustospheremapthequadraticobject.
q=gluNewQuadric(); //CreateANewQuadratic
gluQuadricNormals(q,GL_SMOOTH); //GenerateSmoothNormalsForTheQuad
gluQuadricTexture(q,GL_TRUE); //EnableTextureCoordsForTheQuad
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP); //SetUpSphereMapping
glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP); //SetUpSphereMapping
returnTRUE; //InitializationWentOK
}
Thecodebelowwilldrawourobject(whichisacoollookingenvironmentmappedbeachball).
WesetthecolortofullintensitywhiteandbindtoourBALLtexture(theballtextureisaseriesofred,whiteandbluestripes).
Afterselectingourtexture,wedrawaQuadraticSpherewitharadiusof0.35f,32slicesand16stacks(upanddown).
voidDrawObject() //DrawOurBall
{
glColor3f(1.0f,1.0f,1.0f); //SetColorToWhite
glBindTexture(GL_TEXTURE_2D,texture[1]); //SelectTexture2(1)
gluSphere(q,0.35f,32,16); //DrawFirstSphere
Afterdrawingthefirstsphere,weselectanewtexture(EnvRoll),setthealphavalueto40%andenableblendingbasedonthesourcealphavalue.glEnable(GL_TEXTURE_GEN_S)andglEnable(GL_TEXTURE_GEN_T)enablesspheremapping.
Afterdoingallthat,weredrawthesphere,disablespheremappinganddisableblending.
Thefinalresultisareflectionthatalmostlookslikebrightpointsoflightmappedtothebeachball.Becauseweenablespheremapping,thetextureisalwaysfacingtheviewer,evenastheballspins.Weblendsothatthenewtexturedoesn'tcancelouttheoldtexture(aformofmultitexturing).
glBindTexture(GL_TEXTURE_2D,texture[2]); //SelectTexture3(2)
glColor4f(1.0f,1.0f,1.0f,0.4f); //SetColorToWhiteWith40%Alpha
glEnable(GL_BLEND); //EnableBlending
glBlendFunc(GL_SRC_ALPHA,GL_ONE); //SetBlendingModeToMixBasedOnSRCAlpha
glEnable(GL_TEXTURE_GEN_S); //EnableSphereMapping
glEnable(GL_TEXTURE_GEN_T); //EnableSphereMapping
gluSphere(q,0.35f,32,16); //DrawAnotherSphereUsingNewTexture
//TexturesWillMixCreatingAMultiTextureEffect(Reflection)
glDisable(GL_TEXTURE_GEN_S); //DisableSphereMapping
glDisable(GL_TEXTURE_GEN_T); //DisableSphereMapping
glDisable(GL_BLEND); //DisableBlending
}
Thecodebelowdrawsthefloorthatourballhoversover.Weselectthefloortexture(EnvWall),anddrawasingletexturemappedquadonthez-axis.Prettysimple!
voidDrawFloor() //DrawsTheFloor
{
glBindTexture(GL_TEXTURE_2D,texture[0]); //SelectTexture1(0)
glBegin(GL_QUADS); //BeginDrawingAQuad
glNormal3f(0.0,1.0,0.0); //NormalPointingUp
glTexCoord2f(0.0f,1.0f); //BottomLeftOfTexture
glVertex3f(-2.0,0.0,2.0); //BottomLeftCornerOfFloor
glTexCoord2f(0.0f,0.0f); //TopLeftOfTexture
glVertex3f(-2.0,0.0,-2.0); //TopLeftCornerOfFloor
glTexCoord2f(1.0f,0.0f); //TopRightOfTexture
glVertex3f(2.0,0.0,-2.0); //TopRightCornerOfFloor
glTexCoord2f(1.0f,1.0f); //BottomRightOfTexture
glVertex3f(2.0,0.0,2.0); //BottomRightCornerOfFloor
glEnd(); //DoneDrawingTheQuad
}
Nowforthefunstuff.Here'swherewecombinealltheobjectsandimagestocreateourreflectivescene.
Westartoffbyclearingthescreen(GL_COLOR_BUFFER_BIT)toourdefaultclearcolor(offblue).Thedepth(GL_DEPTH_BUFFER_BIT)andstencil(GL_STENCIL_BUFFER_BIT)buffersarealsocleared.Makesureyouincludethestencilbuffercode,it'snewandeasytooverlook!It'simportanttonotewhenweclearthestencilbuffer,wearefillingitwith0's.
Afterclearingthescreenandbuffers,wedefineourclippingplaneequation.Theplaneequationisusedforclippingthereflectedimage.
Theequationeqr[]={0.0f,-1.0f,0.0f,0.0f}willbeusedwhenwedrawthereflectedimage.Asyoucansee,thevalueforthey-planeisanegativevalue.Meaningwewillonlyseepixelsiftheyaredrawnbelowthefloororatanegativevalueonthey-axis.Anythingdrawnabovethefloorwillnotshowupwhenusingthisequation.
Moreonclippinglater...readon.
intDrawGLScene(GLvoid) //DrawEverything
{
//ClearScreen,DepthBuffer&StencilBuffer
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
//ClipPlaneEquations
doubleeqr[]={0.0f,-1.0f,0.0f,0.0f}; //PlaneEquationToUseForTheReflectedObjects
Sowehaveclearedthescreen,anddefinedourclippingplanes.Nowforthefunstuff!
Westartoffbyresettingthemodelviewmatrix.Whichofcoursestartsalldrawinginthecenterofthescreen.Wethentranslatedown0.6funits(toaddasmallperspectivetilttothefloor)andintothescreenbasedonthevalueofzoom.Tobetterexplainwhywetranslatedown0.6funits,I'llexplainusingasimpleexample.Ifyouwerelookingatthesideofapieceofpaperatexactlyeyelevel,youwouldbarelybeabletoseeit.Itwouldmorethanlikelylooklikeathinline.Ifyoumovedthepaperdownalittle,itwouldnolongerlooklikealine.Youwouldseemoreofthepaper,becauseyoureyeswouldbelookingdownatthepageinsteadofdirectlyattheedgeofthepaper.
glLoadIdentity(); //ResetTheModelviewMatrix
glTranslatef(0.0f,-0.6f,zoom); //ZoomAndRaiseCameraAboveTheFloor(Up0.6Units)
Nextwesetthecolormask.Somethingnewtothistutorial!The4valuesforcolormaskrepresentred,green,blueandalpha.BydefaultallthevaluesaresettoGL_TRUE.
IftheredvalueofglColorMask({red},{green},{blue},{alpha})wassettoGL_TRUE,andalloftheothervalueswere0(GL_FALSE),theonlycolorthatwouldshowuponthescreenisred.Ifthevalueforredwas0(GL_FALSE),buttheothervalueswereallGL_TRUE,everycolorexceptredwouldbedrawntothescreen.
Wedon'twantanythingdrawntothescreenatthemoment,withallofthevaluessetto0(GL_FALSE),colorswillnotbedrawntothescreen.
glColorMask(0,0,0,0); //SetColorMask
Nowevenmorefunstuff...Settingupthestencilbufferandstenciltesting!
Westartoffbyenablingstenciltesting.Oncestenciltestinghasbeenenabled,weareabletomodifythestencilbuffer.
It'sveryhardtoexplainthecommandsbelowsopleasebearwithme,andifyouhaveabetterexplanation,pleaseletmeknow.Inthecodebelowwesetupatest.ThelineglStencilFunc(GL_ALWAYS,1,1)tellsOpenGLwhattypeoftestwewanttodooneachpixelwhenanobjectisdrawntothescreen.
GL_ALWAYSjusttellsOpenGLthetestwillalwayspass.Thesecondparameter(1)isareferencevaluethatwewilltestinthethirdlineofcode,andthethirdparameterisamask.ThemaskisavaluethatisANDedwiththereferencevalueandstoredinthestencilbufferwhenthetestisdone.Areferencevalueof1ANDedwithamaskvalueof1is1.SoifthetestgoeswellandwetellOpenGLto,itwillplaceaoneinthestencilbuffer(reference&mask=1).
Quicknote:Stenciltestingisaperpixeltestdoneeachtimeanobjectisdrawntothescreen.ThereferencevalueANDedwiththemaskvalueistestedagainstthecurrentstencilvalueANDedwiththemaskvalue.
Thethirdlineofcodetestsforthreedifferentconditionsbasedonthestencilfunctionwedecidedtouse.ThefirsttwoparametersareGL_KEEP,andthethirdisGL_REPLACE.
ThefirstparametertellsOpenGLwhattodoifthetestfails.BecausethefirstparameterisGL_KEEP,ifthetestfails(whichitcan'tbecausewehavethefuntionsettoGL_ALWAYS),wewouldleavethestencilvaluesetatwhateveritcurrentlyis.
ThesecondparametertellsOpenGLwhatdodoifthestenciltestpasses,butthedepthtestfails.Inthecodebelow,weeventuallydisabledepthtestingsothisparametercanbeignored.
Thethirdparameteristheimportantone.IttellsOpenGLwhattodoifthetestpasses!InourcodewetellOpenGLtoreplace(GL_REPLACE)thevalueinthestencilbuffer.ThevalueweputintothestencilbufferisourreferencevalueANDedwithourmaskvaluewhichis1.
Aftersettingupthetypeoftestingwewanttodo,wedisabledepthtestingandjumptothecodethatdrawsourfloor.
InsimpleenglishIwilltrytosumupeverythingthatthecodedoesupuntilnow...
WetellOpenGLnottodrawanycolorstothescreen.Thismeansthatwhenwedrawthefloor,itwontshowuponthescreen.BUT...eachspotonthescreenwheretheobject(ourfloor)shouldbeifwecouldseeitwillbetestedbasedonthetypeofstenciltestingwedecidetodo.Thestencilbufferstartsoutfullof0's(empty).Wewanttosetthestencilvalueto1whereverourobjectwouldhavebeendrawnifwecouldseeit.SowetellOpenGLwedon'tcareabouttesting.Ifapixelshouldhavebeendrawntothescreen,wewantthatspotmarkedwitha1.GL_ALWAYSdoesexactlythat.Ourreferenceandmaskvaluesof1makesurethatthevalueplacedintothestencilbufferisindeedgoingtobe1!Asweinvisiblydraw,ourstenciloperationcheckseachpixellocation,andreplacesthe0witha1.
glEnable(GL_STENCIL_TEST); //EnableStencilBufferFor"marking"TheFloor
glStencilFunc(GL_ALWAYS,1,1); //AlwaysPasses,1BitPlane,1AsMask
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); //WeSetTheStencilBufferTo1WhereWeDrawAnyPolygon
//KeepIfTestFails,KeepIfTestPassesButBufferTestFails
//ReplaceIfTestPasses
glDisable(GL_DEPTH_TEST); //DisableDepthTesting
DrawFloor(); //DrawTheFloor(DrawsToTheStencilBuffer)
//WeOnlyWantToMarkItInTheStencilBuffer
Sonowwehaveaninvisiblestencilmaskofthefloor.Aslongasstenciltestingisenabled,theonlyplacespixelswillshowupareplaceswherethestencilbufferhasavalueof1.Allofthepixelsonthescreenwheretheinvisiblefloorwasdrawnwillhaveastencilvalueof1.Meaningaslongasstenciltestingisenabled,theonlypixelsthatwewillseearethepixelsthatwedrawinthesamespotourinvisiblefloorwasdefinedinthestencilbuffer.Thetrickbehindcreatingareallookingreflectionthatreflectsinthefloorandnowhereelse!
Sonowthatweknowtheballreflectionwillonlybedrawnwherethefloorshouldbe,it'stimetodrawthereflection!Weenabledepthtesting,andsetthecolormaskbacktoallones(meaningallthecolorswillbedrawntothescreen).
InsteadofusingGL_ALWAYSforourstencilfunctionwearegoingtouseGL_EQUAL.We'llleavethereferenceandmaskvaluesat1.ForthestenciloperationwewillsetalltheparameterstoGL_KEEP.Inenglish,anyobjectwedrawthistimearoundwillactuallyappearonthescreen(becausethecolormaskissettotrueforeachcolor).AslongasstenciltestingisenabledpixelswillONLYbedrawnifthestencilbufferhasavalueof1(referencevalueANDedwiththemask,whichis1EQUALS(GL_EQUAL)thestencilbuffervalueANDedwiththemask,whichisalso1).Ifthestencilvalueisnot1wherethecurrentpixelisbeingdrawnitwillnotshowup!GL_KEEPjusttellsOpenGLnottomodifyanyvaluesinthestencilbufferifthetestpassesORfails!
glEnable(GL_DEPTH_TEST); //EnableDepthTesting
glColorMask(1,1,1,1); //SetColorMasktoTRUE,TRUE,TRUE,TRUE
glStencilFunc(GL_EQUAL,1,1); //WeDrawOnlyWhereTheStencilIs1
//(I.E.WhereTheFloorWasDrawn)
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); //Don'tChangeTheStencilBuffer
Nowweenablethemirroredclippingplane.Thisplaneisdefinedbyeqr,andonlyallowsobjecttobedrawnfromthecenterofthescreen(wheretheflooris)downtothebottomofthescreen(anynegativevalueonthey-axis).Thatwaythereflectedballthatwedrawcan'tcomeupthroughthecenterofthefloor.Thatwouldlookprettybadifitdid.Ifyoudon'tunderstandwhatImean,removethefirstlinebelowfromthesourcecode,andmovetherealball(nonreflected)throughthefloor.Ifclippingisnotenabled,youwillseethereflectedballpopoutofthefloorastherealballgoesintothefloor.
Afterweenableclippingplane0(usuallyyoucanhavefrom0-5clippingplanes),wedefinetheplanebytellingittousetheparametersstoredineqr.
Wepushthematrix(whichbasicallysavesthepositionofeverythingonthescreen)anduseglScalef(1.0f,-1.0f,1.0f)tofliptheobjectupsidedown(creatingareallookingreflection).SettingtheyvalueofglScalef({x},{y},{z})toanegativevalueforcesOpenGLtorenderoppositeonthey-axis.It'salmostlikeflippingtheentirescreenupsidedown.Whenpositionanobjectatapositivevalueonthey-axis,itwillappearatthebottomofthescreeninsteadofatthetop.Whenyourotateanobjecttowardsyourself,itwillrotateawayfromyou.Everythingwillbemirroredonthey-axisuntilyoupopthematrixorsettheyvaluebackto1.0finsteadof-1.0fusingglScalef({x},{y},{z}).
glEnable(GL_CLIP_PLANE0); //EnableClipPlaneForRemovingArtifacts
//(WhenTheObjectCrossesTheFloor)
glClipPlane(GL_CLIP_PLANE0,eqr); //EquationForReflectedObjects
glPushMatrix(); //PushTheMatrixOntoTheStack
glScalef(1.0f,-1.0f,1.0f); //MirrorYAxis
ThefirstlinebelowpositionsourlighttothelocationspecifiedbyLightPos.Thelightshouldshineonthebottomrightofthereflectedballcreatingaveryreallookinglightsource.Thepositionofthelightisalsomirrored.Ontherealball(ballabovethefloor)thelightispositionedatthetoprightofyourscreen,andshinesonthetoprightoftherealball.Whendrawingthereflectedball,thelightispositionedatthebottomrightofyourscreen.
Wethenmoveupordownonthey-axistothevaluespecifiedbyheight.Translationsaremirrored,soifthevalueofheightis5.0f,thepositionwetranslatetowillbemirrored(-5.0f).Positioningthereflectedimageunderthefloor,insteadofabovethefloor!
Afterpositionourreflectedball,werotatetheballonboththexaxisandyaxis,basedonthevaluesofxrotandyrot.Keepinmindthatanyrotationsonthexaxiswillalsobemirrored.Soiftherealball(ballabovethefloor)isrollingtowardsyouonthex-axis,itwillberollingawayfromyouinthereflection.
AfterpositioningthereflectedballanddoingourrotationswedrawtheballbycallingDrawObject(),andpopthematrix(restoringthingstohowtheywerebeforewedrewtheball).Poppingthematrixallcancelsmirroringonthey-axis.
Wethendisableourclippingplane(plane0)sothatwearenotstuckdrawingonlytothebottomhalfofthescreen,andlast,wedisablestenciltestingsothatwecandrawtootherspotsonthescreenotherthanwherethefloorshouldbe.
Notethatwedrawthereflectedballbeforewedrawthefloor.I'llexplainwhylateron.
glLightfv(GL_LIGHT0,GL_POSITION,LightPos); //SetUpLight0
glTranslatef(0.0f,height,0.0f); //PositionTheObject
glRotatef(xrot,1.0f,0.0f,0.0f); //RotateLocalCoordinateSystemOnXAxis
glRotatef(yrot,0.0f,1.0f,0.0f); //RotateLocalCoordinateSystemOnYAxis
DrawObject(); //DrawTheSphere(Reflection)
glPopMatrix(); //PopTheMatrixOffTheStack
glDisable(GL_CLIP_PLANE0); //DisableClipPlaneForDrawingTheFloor
glDisable(GL_STENCIL_TEST); //WeDon'tNeedTheStencilBufferAnyMore(Disable)
Westartoffthissectionofcodebypositioningourlight.They-axisisnolongerbeingmirroredsodrawingthelightthistimearoundwillpositionitatthetopofthescreeninsteadofthebottomrightofthescreen.
Weenableblending,disablelighting,andsetthealphavalueto80%usingthecommandglColor4f(1.0f,1.0f,1.0f,0.8f).TheblendingmodeissetupusingglBlendFunc(),andthesemitransparentfloorisdrawnovertopofthereflectedball.
Ifwedrewthefloorfirstandthenthereflectedball,theeffectwouldn'tlookverygood.Bydrawingtheballandthenthefloor,youcanseeasmallamountofcoloringfromthefloormixedintothecoloringoftheball.IfIwaslookingintoaBLUEmirror,Iwouldexpectthereflectiontolookalittleblue.Byrenderingtheballfirst,thereflectedimagelookslikeit'stintedthecolorofthefloor.
glLightfv(GL_LIGHT0,GL_POSITION,LightPos); //SetUpLight0Position
glEnable(GL_BLEND); //EnableBlending(OtherwiseTheReflectedObjectWontShow)
glDisable(GL_LIGHTING); //SinceWeUseBlending,WeDisableLighting
glColor4f(1.0f,1.0f,1.0f,0.8f); //SetColorToWhiteWith80%Alpha
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); //BlendingBasedOnSourceAlphaAnd1MinusDestAlpha
DrawFloor(); //DrawTheFloorToTheScreen
Nowwedrawthe'real'ball(theonethatfloatsabovethefloor).Wedisabledlightingwhenwedrewthefloor,butnowit'stimetodrawanotherballsowewillturnlightingbackon.
Wedon'tneedblendinganymoresowedisableblending.Ifwedidn'tdisableblending,thecolorsfromthefloorwouldmixwiththecolorsofour'real'ballwhenitwasfloatingovertopofthefloor.Wedon'twantthe'real'balltolooklikethereflectionsowedisableblending.
Wearenotgoingtocliptheactualball.Iftherealballgoesthroughthefloor,weshouldseeitcomeoutthebottom.Ifwewereusingclippingtheballwouldn'tshowupafteritwentthroughthefloor.Ifyoudidn'twanttoseetheballcomethroughthefloor,youwouldsetupaclippingequationthatsettheYvalueto+1.0f,thenwhentheballwentthroughthefloor,youwouldn'tseeit(youwouldonlyseetheballwhenitwasdrawnonatapositivevalueonthey-axis.Forthisdemo,there'snoreasonweshouldn'tseeitcomethroughthefloor.
Wethentranslateupordownonthey-axistothepositionspecifiedbyheight.Onlythistimethey-axisisnotmirrored,sotheballtravelstheoppositedirectionthatthereflectedimagetravels.Ifwemovethe'real'balldownthereflectedballwillmoveup.Ifwemovethe'real'ballup,thereflectedballwillmovedown.
Werotatethe'real'ball,andagain,becausethey-axisisnotmirrored,theballwillspintheoppositedirectionofthereflectedball.Ifthereflectedballisrollingtowardsyouthe'real'ballwillberollingawayfromyou.Thiscreatestheillusionofarealreflection.
Afterpositioningandrotatingtheball,wedrawthe'real'ballbycallingDrawObject().
glEnable(GL_LIGHTING); //EnableLighting
glDisable(GL_BLEND); //DisableBlending
glTranslatef(0.0f,height,0.0f); //PositionTheBallAtProperHeight
glRotatef(xrot,1.0f,0.0f,0.0f); //RotateOnTheXAxis
glRotatef(yrot,0.0f,1.0f,0.0f); //RotateOnTheYAxis
DrawObject(); //DrawTheBall
Thefollowingcoderotatestheballonthexandyaxis.Byincreasingxrotbyxrotspeedwerotatetheballonthex-axis.Byincreasingyrotbyyrotspeedwespintheballonthey-axis.Ifxrotspeedisaveryhighvalueinthepositiveornegativedirectiontheballwillspinquickerthanifxrotspeedwasalowvalue,closerto0.0f.Samegoesforyrotspeed.Thehigherthevalue,thefastertheballspinsonthey-axis.
BeforewereturnTRUE,wedoaglFlush().ThistellsOpenGLtorendereverythingleftintheGLpipelinebeforecontinuing,andcanhelppreventflickeringonslowervideocards.
xrot+=xrotspeed; //UpdateXRotationAngleByxrotspeed
yrot+=yrotspeed; //UpdateYRotationAngleByyrotspeed
glFlush(); //FlushTheGLPipeline
returnTRUE; //EverythingWentOK
}
Thefollowingcodewillwatchforkeypresses.Thefirst4lineschecktoseeifyouarepressingoneofthe4arrowkeys.Ifyouare,theballisspunright,left,downorup.
Thenext2lineschecktoseeifyouarepressingthe'A'or'Z'keys.Pressing'A'willzoomyouinclosertotheballandpressing'Z'willzoomyouawayfromtheball.
Pressing'PAGEUP'willincreasethevalueofheightmovingtheballup,andpressing'PAGEDOWN'willdecreasethevalueofheightmovingtheballdown(closertothefloor).
voidProcessKeyboard() //ProcessKeyboardResults
{
if(keys[VK_RIGHT]) yrotspeed+=0.08f; //RightArrowPressed(Increaseyrotspeed)
if(keys[VK_LEFT]) yrotspeed-=0.08f; //LeftArrowPressed(Decreaseyrotspeed)
if(keys[VK_DOWN]) xrotspeed+=0.08f; //DownArrowPressed(Increasexrotspeed)
if(keys[VK_UP]) xrotspeed-=0.08f; //UpArrowPressed(Decreasexrotspeed)
if(keys['A']) zoom+=0.05f; //'A'KeyPressed...ZoomIn
if(keys['Z']) zoom-=0.05f; //'Z'KeyPressed...ZoomOut
if(keys[VK_PRIOR]) height+=0.03f; //PageUpKeyPressedMoveBallUp
if(keys[VK_NEXT]) height-=0.03f; //PageDownKeyPressedMoveBallDown
}
TheKillGLWindow()codehasn'tchanged,soI'llskipoverit.
GLvoidKillGLWindow(GLvoid) //ProperlyKillTheWindow
Youcanskimthroughthefollowingcode.EventhoughonlyonelineofcodehaschangedinCreateGLWindow(),Ihaveincludedallofthecodesoit'seasiertofollowthroughthetutorial.
BOOLCreateGLWindow(char*title,intwidth,intheight,intbits,boolfullscreenflag)
{
GLuint PixelFormat; //HoldsTheResultsAfterSearchingForAMatch
WNDCLASS wc; //WindowsClassStructure
DWORD dwExStyle; //WindowExtendedStyle
DWORD dwStyle; //WindowStyle
fullscreen=fullscreenflag; //SetTheGlobalFullscreenFlag
hInstance =GetModuleHandle(NULL); //GrabAnInstanceForOurWindow
wc.style =CS_HREDRAW|CS_VREDRAW|CS_OWNDC; //RedrawOnSize,AndOwnDCForWindow
wc.lpfnWndProc =(WNDPROC)WndProc; //WndProcHandlesMessages
wc.cbClsExtra =0; //NoExtraWindowData
wc.cbWndExtra =0; //NoExtraWindowData
wc.hInstance =hInstance; //SetTheInstance
wc.hIcon =LoadIcon(NULL,IDI_WINLOGO); //LoadTheDefaultIcon
wc.hCursor =LoadCursor(NULL,IDC_ARROW); //LoadTheArrowPointer
wc.hbrBackground =NULL; //NoBackgroundRequiredForGL
wc.lpszMenuName =NULL; //WeDon'tWantAMenu
wc.lpszClassName ="OpenGL"; //SetTheClassName
if(!RegisterClass(&wc)) //AttemptToRegisterTheWindowClass
{
MessageBox(NULL,"FailedToRegisterTheWindowClass.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
if(fullscreen) //AttemptFullscreenMode?
{
DEVMODEdmScreenSettings; //DeviceMode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); //MakesSureMemory'sCleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings); //SizeOfTheDevmodeStructure
dmScreenSettings.dmPelsWidth =width; //SelectedScreenWidth
dmScreenSettings.dmPelsHeight =height; //SelectedScreenHeight
dmScreenSettings.dmBitsPerPel =bits; //SelectedBitsPerPixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
//TryToSetSelectedModeAndGetResults.NOTE:CDS_FULLSCREENGetsRidOfStartBar
if(ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
//IfTheModeFails,OfferTwoOptions.QuitOrUseWindowedMode
if(MessageBox(NULL,"TheRequestedFullscreenModeIsNotSupportedBy\nYourVideoCard.UseWindowedModeInstead?","NeHeGL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=FALSE; //WindowedModeSelected.Fullscreen=FALSE
}
else
{
//PopUpAMessageBoxLettingUserKnowTheProgramIsClosing
MessageBox(NULL,"ProgramWillNowClose.","ERROR",MB_OK|MB_ICONSTOP);
returnFALSE; //ReturnFALSE
}
}
}
if(fullscreen) //AreWeStillInFullscreenMode?
{
dwExStyle=WS_EX_APPWINDOW; //WindowExtendedStyle
dwStyle=WS_POPUP|WS_CLIPSIBLINGS|WS_CLIPCHILDREN; //WindowsStyle
ShowCursor(FALSE); //HideMousePointer
}
else
{
dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE; //WindowExtendedStyle
dwStyle=WS_OVERLAPPEDWINDOW|WS_CLIPSIBLINGS|WS_CLIPCHILDREN;//WindowsStyle
}
//CreateTheWindow
if(!(hWnd=CreateWindowEx( dwExStyle, //ExtendedStyleForTheWindow
"OpenGL", //ClassName
title, //WindowTitle
dwStyle, //WindowStyle
0,0, //WindowPosition
width,height, //SelectedWidthAndHeight
NULL, //NoParentWindow
NULL, //NoMenu
hInstance, //Instance
NULL))) //DontPassAnythingToWM_CREATE
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"WindowCreationError.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
static PIXELFORMATDESCRIPTORpfd= //pfdTellsWindowsHowWeWantThingsToBe
{
sizeof(PIXELFORMATDESCRIPTOR), //SizeOfThisPixelFormatDescriptor
1, //VersionNumber
PFD_DRAW_TO_WINDOW| //FormatMustSupportWindow
PFD_SUPPORT_OPENGL| //FormatMustSupportOpenGL
PFD_DOUBLEBUFFER, //MustSupportDoubleBuffering
PFD_TYPE_RGBA, //RequestAnRGBAFormat
bits, //SelectOurColorDepth
0,0,0,0,0,0, //ColorBitsIgnored
0, //NoAlphaBuffer
0, //ShiftBitIgnored
0, //NoAccumulationBuffer
0,0,0,0, //AccumulationBitsIgnored
16, //16BitZ-Buffer(DepthBuffer)
Theonlychangeinthissectionofcodeisthelinebelow.Itis*VERYIMPORTANT*youchangethevaluefrom0to1orsomeothernonzerovalue.Inalloftheprevioustutorialsthevalueofthelinebelowwas0.InordertouseStencilBufferingthisvalueHAStobegreaterthanorequalto1.Thisvalueisthenumberofbitsyouwanttouseforthestencilbuffer.
1, //UseStencilBuffer(*Important*)
0, //NoAuxiliaryBuffer
PFD_MAIN_PLANE, //MainDrawingLayer
0, //Reserved
0,0,0 //LayerMasksIgnored
};
if(!(hDC=GetDC(hWnd))) //DidWeGetADeviceContext?
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"Can'tCreateAGLDeviceContext.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
if(!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) //DidWindowsFindAMatchingPixelFormat?
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"Can'tFindASuitablePixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) //AreWeAbleToSetThePixelFormat?
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"Can'tSetThePixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
if(!(hRC=wglCreateContext(hDC))) //AreWeAbleToGetARenderingContext?
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"Can'tCreateAGLRenderingContext.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
if(!wglMakeCurrent(hDC,hRC)) //TryToActivateTheRenderingContext
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"Can'tActivateTheGLRenderingContext.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
ShowWindow(hWnd,SW_SHOW); //ShowTheWindow
SetForegroundWindow(hWnd); //SlightlyHigherPriority
SetFocus(hWnd); //SetsKeyboardFocusToTheWindow
ReSizeGLScene(width,height); //SetUpOurPerspectiveGLScreen
if(!InitGL()) //InitializeOurNewlyCreatedGLWindow
{
KillGLWindow(); //ResetTheDisplay
MessageBox(NULL,"InitializationFailed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
returnFALSE; //ReturnFALSE
}
returnTRUE; //Success
}
WndProc()hasnotchanged,sowewillskipoverit.
LRESULTCALLBACKWndProc( HWND hWnd, //HandleForThisWindow
UINT uMsg, //MessageForThisWindow
WPARAM wParam, //AdditionalMessageInformation
LPARAM lParam) //AdditionalMessageInformation
Nothingnewhere.TypicalstarttoWinMain().
intWINAPIWinMain( HINSTANCE hInstance, //Instance
HINSTANCE hPrevInstance, //PreviousInstance
LPSTR lpCmdLine, //CommandLineParameters
int nCmdShow) //WindowShowState
{
MSG msg; //WindowsMessageStructure
BOOL done=FALSE; //BoolVariableToExitLoop
//AskTheUserWhichScreenModeTheyPrefer
if(MessageBox(NULL,"WouldYouLikeToRunInFullscreenMode?","StartFullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; //WindowedMode
}
Theonlyrealbigchangeinthissectionofthecodeisthenewwindowtitletoleteveryoneknowthetutorialisaboutreflectionsusingthestencilbuffer.Alsonoticethatwepasstheresx,resyandresbppvariablestoourwindowcreationprocedureinsteadoftheusual640,480and16.
//CreateOurOpenGLWindow
if(!CreateGLWindow("BanuOctavian&NeHe'sStencil&ReflectionTutorial",resx,resy,resbpp,fullscreen))
{
return0; //QuitIfWindowWasNotCreated
}
while(!done) //LoopThatRunsWhiledone=FALSE
{
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) //IsThereAMessageWaiting?
{
if(msg.message==WM_QUIT) //HaveWeReceivedAQuitMessage?
{
done=TRUE; //IfSodone=TRUE
}
else //IfNot,DealWithWindowMessages
{
TranslateMessage(&msg); //TranslateTheMessage
DispatchMessage(&msg); //DispatchTheMessage
}
}
else //IfThereAreNoMessages
{
//DrawTheScene.WatchForESCKeyAndQuitMessagesFromDrawGLScene()
if(active) //ProgramActive?
{
if(keys[VK_ESCAPE]) //WasEscapePressed?
{
done=TRUE; //ESCSignalledAQuit
}
else //NotTimeToQuit,UpdateScreen
{
DrawGLScene(); //DrawTheScene
SwapBuffers(hDC); //SwapBuffers(DoubleBuffering)
InsteadofcheckingforkeypressesinWinMain(),wejumptoourkeyboardhandlingroutinecalledProcessKeyboard().NoticetheProcessKeyboard()routineisonlycallediftheprogramisactive!
ProcessKeyboard(); //ProcessedKeyboardPresses
}
}
}
}
//Shutdown
KillGLWindow(); //KillTheWindow
return(msg.wParam); //ExitTheProgram
}
Ireallyhopeyou'veenjoyedthistutorial.Iknowitcouldusealittlemorework.ItwasoneofthemoredifficulttutorialsthatIhavewritten.It'seasyformetounderstandwhateverythingisdoing,andwhatcommandsIneedtousetocreatecooleffects,butwhenyousitdownandactuallytrytoexplainthingskeepinginmindthatmostpeoplehaveneverevenheardofthestencilbuffer,it'stough!Ifyounoticeanythingthatcouldbemadeclearerorifyoufindanymistakesinthetutorialpleaseletmeknow.Asalways,Iwantthistutorialtobethebestitcanpossiblybe,yourfeedbackisgreatlyappreciated.
BanuOctavian(Choko)
JeffMolofee(NeHe)
*DOWNLOADVisualC++CodeForThisLesson.
*DOWNLOADBorlandC++Builder6CodeForThisLesson.(ConversionbyChristianKindahl)*DOWNLOADCodeWarrior5.3CodeForThisLesson.(ConversionbyScottLupton)*DOWNLOADDelphiCodeForThisLesson.(ConversionbyMichalTucek)*DOWNLOADDevC++CodeForThisLesson.(ConversionbyDan)*DOWNLOADEuphoriaCodeForThisLesson.(ConversionbyEvanMarshall)*DOWNLOADLCCWin32CodeForThisLesson.(ConversionbyRobertWishlaw)*DOWNLOADLinuxCodeForThisLesson.(ConversionbyGrayFox)*DOWNLOADLWJGLCodeForThisLesson.(ConversionbyMarkBernard)*DOWNLOADMacOSX/CocoaCodeForThisLesson.(ConversionbyBryanBlackburn)*DOWNLOADVisualStudio.NETCodeForThisLesson.(ConversionbyGrantJames)
©1997-2014Gamedev.Allrightsreserved.
NeHe™andNeHeProductions™aretrademarksofGameDev.net,LLC
OpenGL®isaregisteredtrademarkofSiliconGraphicsInc.