/* ================== RB_SetProgramEnvironmentSpace Sets variables related to the current space that can be used by all vertex programs ================== */ void RB_SetProgramEnvironmentSpace( void ) { if ( !glConfig.ARBVertexProgramAvailable ) { return; } const struct viewEntity_s *space = backEnd.currentSpace; float parm[4]; // set eye position in local space R_GlobalPointToLocal( space->modelMatrix, backEnd.viewDef->renderView.vieworg, *(idVec3 *)parm ); parm[3] = 1.0; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 5, parm ); // we need the model matrix without it being combined with the view matrix // so we can transform local vectors to global coordinates parm[0] = space->modelMatrix[0]; parm[1] = space->modelMatrix[4]; parm[2] = space->modelMatrix[8]; parm[3] = space->modelMatrix[12]; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 6, parm ); parm[0] = space->modelMatrix[1]; parm[1] = space->modelMatrix[5]; parm[2] = space->modelMatrix[9]; parm[3] = space->modelMatrix[13]; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 7, parm ); parm[0] = space->modelMatrix[2]; parm[1] = space->modelMatrix[6]; parm[2] = space->modelMatrix[10]; parm[3] = space->modelMatrix[14]; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 8, parm ); }
static void glSetFragmentShaderConstantATI (GLuint num, const GLfloat *val) { int constNum = num-GL_CON_0_ATI; if (sGeneratingProgram) { char str[128]; sprintf (str, " CONSTANT c%d = { %f, %f, %f, %f };\n", constNum, val[0], val[1], val[2], val[3]); strcat (sConstString, str); sConst[constNum] = 2; } else { // According to Duane, frequent setting of fragment shader constants, even if they contain // the same value, will cause a performance hit. // According to Chris Bentley at ATI, this performance hit appears if you are using // many different fragment shaders in each scene. // So, we cache those values and only set the constants if they are different. if (memcmp (val, sConstVal[constNum], sizeof(GLfloat)*8*4) != 0) { qglProgramEnvParameter4fvARB (GL_TEXT_FRAGMENT_SHADER_ATI, num-GL_CON_0_ATI, val); memcpy (sConstVal[constNum], val, sizeof(GLfloat)*8*4); } } }
/* ================== RB_ARB2_DrawInteraction ================== */ void RB_ARB2_DrawInteraction( const drawInteraction_t *din ) { // load all the vertex program parameters qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() ); // testing fragment based normal mapping if ( r_testARBProgram.GetBool() ) { qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, din->localViewOrigin.ToFloatPtr() ); } static const float zero[4] = { 0, 0, 0, 0 }; static const float one[4] = { 1, 1, 1, 1 }; static const float negOne[4] = { -1, -1, -1, -1 }; switch ( din->vertexColor ) { case SVC_IGNORE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zero ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; case SVC_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, one ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, zero ); break; case SVC_INVERSE_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOne ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; } // set the constant colors qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, din->specularColor.ToFloatPtr() ); // set the textures // texture 1 will be the per-surface bump map GL_SelectTextureNoClient( 1 ); din->bumpImage->Bind(); // texture 2 will be the light falloff texture GL_SelectTextureNoClient( 2 ); din->lightFalloffImage->Bind(); // texture 3 will be the light projection texture GL_SelectTextureNoClient( 3 ); din->lightImage->Bind(); // texture 4 is the per-surface diffuse map GL_SelectTextureNoClient( 4 ); din->diffuseImage->Bind(); // texture 5 is the per-surface specular map GL_SelectTextureNoClient( 5 ); din->specularImage->Bind(); // draw it RB_DrawElementsWithCounters( din->surf->geo ); }
/* ================== RB_SetProgramEnvironment Sets variables that can be used by all vertex programs ================== */ void RB_SetProgramEnvironment( void ) { float parm[4]; int pot; if ( !glConfig.ARBVertexProgramAvailable ) { return; } #if 0 // screen power of two correction factor, one pixel in so we don't get a bilerp // of an uncopied pixel int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; pot = globalImages->currentRenderImage->uploadWidth; if ( w == pot ) { parm[0] = 1.0; } else { parm[0] = (float)(w-1) / pot; } int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; pot = globalImages->currentRenderImage->uploadHeight; if ( h == pot ) { parm[1] = 1.0; } else { parm[1] = (float)(h-1) / pot; } parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); #else // screen power of two correction factor, assuming the copy to _currentRender // also copied an extra row and column for the bilerp int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; pot = globalImages->currentRenderImage->uploadWidth; parm[0] = (float)w / pot; int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; pot = globalImages->currentRenderImage->uploadHeight; parm[1] = (float)h / pot; parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); #endif qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm ); // window coord to 0.0 to 1.0 conversion parm[0] = 1.0 / w; parm[1] = 1.0 / h; parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm ); // // set eye position in global space // parm[0] = backEnd.viewDef->renderView.vieworg[0]; parm[1] = backEnd.viewDef->renderView.vieworg[1]; parm[2] = backEnd.viewDef->renderView.vieworg[2]; parm[3] = 1.0; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 1, parm ); }
/* ===================== RB_T_Shadow the shadow volumes face INSIDE ===================== */ static void RB_T_Shadow( const drawSurf_t *surf ) { const srfTriangles_t *tri; // set the light position if we are using a vertex program to project the rear surfaces if ( tr.backEndRendererHasVertexPrograms && r_useShadowVertexProgram.GetBool() && surf->space != backEnd.currentSpace ) { idVec4 localLight; R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.vLight->globalLightOrigin, localLight.ToVec3() ); localLight.w = 0.0f; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, localLight.ToFloatPtr() ); } tri = surf->geo; if ( !tri->shadowCache ) { return; } qglVertexPointer( 4, GL_FLOAT, sizeof( shadowCache_t ), vertexCache.Position(tri->shadowCache) ); // we always draw the sil planes, but we may not need to draw the front or rear caps int numIndexes; bool external = false; if ( !r_useExternalShadows.GetInteger() ) { numIndexes = tri->numIndexes; } else if ( r_useExternalShadows.GetInteger() == 2 ) { // force to no caps for testing numIndexes = tri->numShadowIndexesNoCaps; } else if ( !(surf->dsFlags & DSF_VIEW_INSIDE_SHADOW) ) { // if we aren't inside the shadow projection, no caps are ever needed needed numIndexes = tri->numShadowIndexesNoCaps; external = true; } else if ( !backEnd.vLight->viewInsideLight && !(surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE) ) { // if we are inside the shadow projection, but outside the light, and drawing // a non-infinite shadow, we can skip some caps if ( backEnd.vLight->viewSeesShadowPlaneBits & surf->geo->shadowCapPlaneBits ) { // we can see through a rear cap, so we need to draw it, but we can skip the // caps on the actual surface numIndexes = tri->numShadowIndexesNoFrontCaps; } else { // we don't need to draw any caps numIndexes = tri->numShadowIndexesNoCaps; } external = true; } else { // must draw everything numIndexes = tri->numIndexes; } // set depth bounds if( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { qglDepthBoundsEXT( surf->scissorRect.zmin, surf->scissorRect.zmax ); } // debug visualization if ( r_showShadows.GetInteger() ) { if ( r_showShadows.GetInteger() == 3 ) { if ( external ) { qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); } else { // these are the surfaces that require the reverse qglColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); } } else { // draw different color for turboshadows if ( surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE ) { if ( numIndexes == tri->numIndexes ) { qglColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); } else { qglColor3f( 1/backEnd.overBright, 0.4/backEnd.overBright, 0.1/backEnd.overBright ); } } else { if ( numIndexes == tri->numIndexes ) { qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); } else if ( numIndexes == tri->numShadowIndexesNoFrontCaps ) { qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.6/backEnd.overBright ); } else { qglColor3f( 0.6/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); } } } qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); qglDisable( GL_STENCIL_TEST ); GL_Cull( CT_TWO_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); GL_Cull( CT_FRONT_SIDED ); qglEnable( GL_STENCIL_TEST ); return; } // patent-free work around if ( !external ) { // "preload" the stencil buffer with the number of volumes // that get clipped by the near or far clip plane qglStencilOp( GL_KEEP, tr.stencilDecr, tr.stencilDecr ); GL_Cull( CT_FRONT_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); qglStencilOp( GL_KEEP, tr.stencilIncr, tr.stencilIncr ); GL_Cull( CT_BACK_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } // traditional depth-pass stencil shadows qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilIncr ); GL_Cull( CT_FRONT_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilDecr ); GL_Cull( CT_BACK_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); }
/* =================== RB_R200_ARB_DrawInteraction =================== */ static void RB_R200_ARB_DrawInteraction( const drawInteraction_t *din ) { // check for the case we can't handle in a single pass (we could calculate this at shader parse time to optimize) if ( din->diffuseImage != globalImages->blackImage && din->specularImage != globalImages->blackImage && memcmp( din->specularMatrix, din->diffuseMatrix, sizeof( din->diffuseMatrix ) ) ) { // common->Printf( "Note: Shader %s drawn as two pass on R200\n", din->surf->shader->getName() ); // draw the specular as a separate pass with a black diffuse map drawInteraction_t d; d = *din; d.diffuseImage = globalImages->blackImage; memcpy( d.diffuseMatrix, d.specularMatrix, sizeof( d.diffuseMatrix ) ); RB_R200_ARB_DrawInteraction( &d ); // now fall through and draw the diffuse pass with a black specular map d = *din; din = &d; d.specularImage = globalImages->blackImage; } // load all the vertex program parameters qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); const srfTriangles_t *tri = din->surf->geo; idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->xyz ); static const float zero[4] = { 0, 0, 0, 0 }; static const float one[4] = { 1, 1, 1, 1 }; static const float negOne[4] = { -1, -1, -1, -1 }; switch ( din->vertexColor ) { case SVC_IGNORE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zero ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; case SVC_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, one ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, zero ); break; case SVC_INVERSE_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOne ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; } // texture 0 = light projection // texture 1 = light falloff // texture 2 = surface diffuse // texture 3 = surface specular // texture 4 = surface bump // texture 5 = normalization cube map GL_SelectTexture( 5 ); if ( din->ambientLight ) { globalImages->ambientNormalMap->Bind(); } else { globalImages->normalCubeMapImage->Bind(); } GL_SelectTexture( 4 ); din->bumpImage->Bind(); GL_SelectTexture( 3 ); din->specularImage->Bind(); qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->normal ); GL_SelectTexture( 2 ); din->diffuseImage->Bind(); qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->tangents[1][0] ); GL_SelectTexture( 1 ); din->lightFalloffImage->Bind(); qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->tangents[0][0] ); GL_SelectTexture( 0 ); din->lightImage->Bind(); qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->st[0] ); qglSetFragmentShaderConstantATI( GL_CON_0_ATI, din->diffuseColor.ToFloatPtr() ); qglSetFragmentShaderConstantATI( GL_CON_1_ATI, din->specularColor.ToFloatPtr() ); if ( din->vertexColor != SVC_IGNORE ) { qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof(idDrawVert), (void *)&ac->color ); qglEnableClientState( GL_COLOR_ARRAY ); RB_DrawElementsWithCounters( tri ); qglDisableClientState( GL_COLOR_ARRAY ); qglColor4f( 1, 1, 1, 1 ); } else { RB_DrawElementsWithCounters( tri ); } }
/* ================== RB_NV20_DrawInteraction ================== */ static void RB_NV20_DrawInteraction(const drawInteraction_t *din) { const drawSurf_t *surf = din->surf; // load all the vertex program parameters qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr()); qglProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr()); // set the constant colors qglCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, din->diffuseColor.ToFloatPtr()); qglCombinerParameterfvNV(GL_CONSTANT_COLOR1_NV, din->specularColor.ToFloatPtr()); // vertex color passes should be pretty rare (cross-faded bump map surfaces), so always // run them down as three-passes if (din->vertexColor != SVC_IGNORE) { qglEnableClientState(GL_COLOR_ARRAY); RB_NV20_DI_BumpAndLightPass(din, false); RB_NV20_DI_DiffuseColorPass(din); RB_NV20_DI_SpecularColorPass(din); qglDisableClientState(GL_COLOR_ARRAY); return; } qglColor3f(1, 1, 1); // on an ideal card, we would now just bind the textures and call a // single pass vertex / fragment program, but // on NV20, we need to decide which single / dual / tripple pass set of programs to use // ambient light could be done as a single pass if we want to optimize for it // monochrome light is two passes int internalFormat = din->lightImage->internalFormat; if ((r_useNV20MonoLights.GetInteger() == 2) || (din->lightImage->isMonochrome && r_useNV20MonoLights.GetInteger())) { // do a two-pass rendering RB_NV20_DI_BumpAndLightPass(din, true); RB_NV20_DI_DiffuseAndSpecularColorPass(din); } else { // general case is three passes // ( bump dot lightDir ) * lightFalloff // diffuse * lightProject // specular * ( bump dot halfAngle extended ) * lightProject RB_NV20_DI_BumpAndLightPass(din, false); RB_NV20_DI_DiffuseColorPass(din); RB_NV20_DI_SpecularColorPass(din); } }