1 #include <iostream>
2 #include <osg/Geode>
3 #include <osg/TexGen>
4 #include <osg/Texture2D>
5 #include <osg/MatrixTransform>
6 #include <osg/BlendFunc>
7 #include <osgText/Text>
8 #include <osgDB/ReadFile>
9
10 #include "VirtualProgram.h"
11
12 using osgCandidate::VirtualProgram;
13
14 ////////////////////////////////////////////////////////////////////////////////
15 // Example shaders assume:
16 // one texture
17 // one directional light
18 // front face lighting
19 // color material mode not used (its not supported by GLSL anyway)
20 // diffuse/ambient/emissive/specular factors defined in material structure
21 // all coords and normal except gl_Position are in view space
22 ////////////////////////////////////////////////////////////////////////////////
23
24 char MainVertexShaderSource[] =
25 "vec4 texture( in vec3 position, in vec3 normal ); \n" //1
26 "void lighting( in vec3 position, in vec3 normal ); \n" //2
27 " \n" //3
28 "void main () \n" //4
29 "{ \n" //5
30 " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n" //6
31 " vec4 position4 = gl_ModelViewMatrix * gl_Vertex; \n" //7
32 " vec3 position = position4.xyz / position4.w; \n" //8
33 " vec3 normal = normalize( gl_NormalMatrix * gl_Normal ); \n" //9
34 " gl_TexCoord[0] = texture( position, normal ); \n" //10
35 " lighting( position, normal ); \n" //11
36 "} \n";//12
37
38 char TexCoordTextureVertexShaderSource[] =
39 "vec4 texture( in vec3 position, in vec3 normal ) \n" //1
40 "{ \n" //2
41 " return gl_TextureMatrix[0] * gl_MultiTexCoord0; \n" //3
42 "} \n";//4
43
44 char SphereMapTextureVertexShaderSource[] =
45 "vec4 texture( in vec3 position, in vec3 normal ) \n" //1
46 "{ \n" //2
47 " vec3 u = normalize( position ); \n" //3
48 " vec3 r = reflect(u, normal); \n" //4
49 " float m = 2.0 * sqrt(r.x * r.x + r.y * r.y + (r.z+1.0) * (r.z+1.0)); \n" //5
50 " return vec4(r.x / m + 0.5, r.y / m + 0.5, 1.0, 1.0 ); \n" //6
51 "} \n";//7
52
53 char PerVertexDirectionalLightingVertexShaderSource[] =
54 "void lighting( in vec3 position, in vec3 normal ) \n" //1
55 "{ \n" //2
56 " float NdotL = dot( normal, normalize(gl_LightSource[0].position.xyz) );\n" //3
57 " NdotL = max( 0.0, NdotL ); \n" //4
58 " float NdotHV = dot( normal, gl_LightSource[0].halfVector.xyz ); \n" //5
59 " NdotHV = max( 0.0, NdotHV ); \n" //6
60 " \n" //7
61 " gl_FrontColor = gl_FrontLightModelProduct.sceneColor + \n" //8
62 " gl_FrontLightProduct[0].ambient + \n" //9
63 " gl_FrontLightProduct[0].diffuse * NdotL; \n" //10
64 " \n" //11
65 " gl_FrontSecondaryColor = vec4(0.0); \n" //12
66 " \n" //13
67 " if ( NdotL * NdotHV > 0.0 ) \n" //14
68 " gl_FrontSecondaryColor = gl_FrontLightProduct[0].specular * \n" //15
69 " pow( NdotHV, gl_FrontMaterial.shininess );\n" //16
70 " \n" //17
71 " gl_BackColor = gl_FrontColor; \n" //18
72 " gl_BackSecondaryColor = gl_FrontSecondaryColor; \n" //19
73 "} \n";//20
74
75 char MainFragmentShaderSource[] =
76 "vec4 texture( void ); \n" //1
77 "void lighting( inout vec4 color ); \n" //2
78 " \n" //3
79 "void main () \n" //4
80 "{ \n" //5
81 " vec4 color = texture(); \n" //6
82 " lighting( color ); \n" //7
83 " gl_FragColor = color; \n" //8
84 "} \n";//9
85
86 char TextureFragmentShaderSource[] =
87 "uniform sampler2D baseTexture; \n" //1
88 "vec4 texture( void ) \n" //2
89 "{ \n" //3
90 " return texture2D( baseTexture, gl_TexCoord[0].xy ); \n" //4
91 "} \n";//5
92
93 char ProceduralBlueTextureFragmentShaderSource[] =
94 "vec4 texture( void ) \n" //1
95 "{ \n" //2
96 " return vec4( 0.3, 0.3, 1.0, 1.0 ); \n" //3
97 "} \n";//4
98
99 char PerVertexLightingFragmentShaderSource[] =
100 "void lighting( inout vec4 color ) \n" //1
101 "{ \n" //2
102 " color = color * gl_Color + gl_SecondaryColor; \n" //3
103 "} \n";//4
104
105 char PerFragmentLightingVertexShaderSource[] =
106 "varying vec3 Normal; \n" //1
107 "varying vec3 Position; \n" //2
108 " \n" //3
109 "void lighting( in vec3 position, in vec3 normal ) \n" //4
110 "{ \n" //5
111 " Normal = normal; \n" //6
112 " Position = position; \n" //7
113 "} \n";//8
114
115 char PerFragmentDirectionalLightingFragmentShaderSource[] =
116 "varying vec3 Normal; \n" //1
117 "varying vec3 Position; // not used for directional lighting \n" //2
118 " \n" //3
119 "void lighting( inout vec4 color ) \n" //4
120 "{ \n" //5
121 " vec3 n = normalize( Normal ); \n" //5
122 " float NdotL = dot( n, normalize(gl_LightSource[0].position.xyz) ); \n" //6
123 " NdotL = max( 0.0, NdotL ); \n" //7
124 " float NdotHV = dot( n, gl_LightSource[0].halfVector.xyz ); \n" //8
125 " NdotHV = max( 0.0, NdotHV ); \n" //9
126 " \n" //10
127 " color *= gl_FrontLightModelProduct.sceneColor + \n" //11
128 " gl_FrontLightProduct[0].ambient + \n" //12
129 " gl_FrontLightProduct[0].diffuse * NdotL; \n" //13
130 " \n" //14
131 " if ( NdotL * NdotHV > 0.0 ) \n" //15
132 " color += gl_FrontLightProduct[0].specular * \n" //16
133 " pow( NdotHV, gl_FrontMaterial.shininess ); \n" //17
134 "} \n";//18
135
136 ////////////////////////////////////////////////////////////////////////////////
137 // Convenience method to simplify code a little ...
SetVirtualProgramShader(VirtualProgram * virtualProgram,std::string shader_semantics,osg::Shader::Type shader_type,std::string shader_name,std::string shader_source)138 void SetVirtualProgramShader( VirtualProgram * virtualProgram,
139 std::string shader_semantics,
140 osg::Shader::Type shader_type,
141 std::string shader_name,
142 std::string shader_source )
143 {
144 osg::Shader * shader = new osg::Shader( shader_type );
145 shader->setName( shader_name );
146 shader->setShaderSource( shader_source );
147 virtualProgram->setShader( shader_semantics, shader );
148 }
149 ///////////////////////////////////////////////////////////////////////////////
AddLabel(osg::Group * group,const std::string & label,float offset)150 void AddLabel( osg::Group * group, const std::string & label, float offset )
151 {
152 osg::Vec3 center( 0, 0, offset * 0.5 );
153 osg::Geode * geode = new osg::Geode;
154
155 // Make sure no program breaks text outputs
156 geode->getOrCreateStateSet()->setAttribute
157 ( new osg::Program, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
158
159 // Turn off stage 1 texture set in parent transform (otherwise it darkens text)
160 geode->getOrCreateStateSet()->setTextureMode( 1, GL_TEXTURE_2D, osg::StateAttribute::OFF );
161
162 group->addChild( geode );
163
164 osgText::Text* text = new osgText::Text;
165 geode->addDrawable( text );
166 text->setFont("fonts/times.ttf");
167 text->setCharacterSize( offset * 0.1 );
168 text->setPosition(center);
169 text->setAlignment( osgText::TextBase::CENTER_CENTER );
170 text->setAxisAlignment(osgText::Text::SCREEN);
171
172 osg::Vec4 characterSizeModeColor(1.0f,0.0f,0.5f,1.0f);
173 #if 1
174 // reproduce outline bounding box compute problem with backdrop on.
175 text->setBackdropType(osgText::Text::OUTLINE);
176 text->setDrawMode(osgText::Text::TEXT | osgText::Text::BOUNDINGBOX);
177 #endif
178
179 text->setText( label );
180 }
181 ////////////////////////////////////////////////////////////////////////////////
CreateAdvancedHierarchy(osg::Node * model)182 osg::Node * CreateAdvancedHierarchy( osg::Node * model )
183 {
184 if( !model ) return NULL;
185 float offset = model->getBound().radius() * 1.3; // diameter
186
187 // Create transforms for translated instances of the model
188 osg::MatrixTransform * transformCenterMiddle = new osg::MatrixTransform( );
189 transformCenterMiddle->setMatrix( osg::Matrix::translate( 0,0, offset * 0.5 ) );
190 transformCenterMiddle->addChild( model );
191
192 osg::MatrixTransform * transformCenterTop = new osg::MatrixTransform( );
193 transformCenterMiddle->addChild( transformCenterTop );
194 transformCenterTop->setMatrix( osg::Matrix::translate( 0,0,offset ) );
195 transformCenterTop->addChild( model );
196
197 osg::MatrixTransform * transformCenterBottom = new osg::MatrixTransform( );
198 transformCenterMiddle->addChild( transformCenterBottom );
199 transformCenterBottom->setMatrix( osg::Matrix::translate( 0,0,-offset ) );
200 transformCenterBottom->addChild( model );
201
202 osg::MatrixTransform * transformLeftBottom = new osg::MatrixTransform( );
203 transformCenterBottom->addChild( transformLeftBottom );
204 transformLeftBottom->setMatrix( osg::Matrix::translate( -offset * 0.8,0, -offset * 0.8 ) );
205 transformLeftBottom->addChild( model );
206
207 osg::MatrixTransform * transformRightBottom = new osg::MatrixTransform( );
208 transformCenterBottom->addChild( transformRightBottom );
209 transformRightBottom->setMatrix( osg::Matrix::translate( offset * 0.8,0, -offset * 0.8 ) );
210 transformRightBottom->addChild( model );
211
212 // Set default VirtualProgram in root StateSet
213 // With main vertex and main fragment shaders calling
214 // lighting and texture functions defined in additional shaders
215 // Lighting is done per vertex using simple directional light
216 // Texture uses stage 0 TexCoords and TexMap
217
218 if( 1 )
219 {
220 // NOTE:
221 // duplicating the same semantics name in virtual program
222 // is only possible if its used for shaders of differing types
223 // here for VERTEX and FRAGMENT
224
225 VirtualProgram * vp = new VirtualProgram( );
226 transformCenterMiddle->getOrCreateStateSet()->setAttribute( vp );
227 AddLabel( transformCenterMiddle, "Per Vertex Lighting Virtual Program", offset );
228
229 SetVirtualProgramShader( vp, "main", osg::Shader::VERTEX,
230 "Vertex Main", MainVertexShaderSource );
231
232 SetVirtualProgramShader( vp, "main", osg::Shader::FRAGMENT,
233 "Fragment Main", MainFragmentShaderSource );
234
235 SetVirtualProgramShader( vp, "texture",osg::Shader::VERTEX,
236 "Vertex Texture Coord 0", TexCoordTextureVertexShaderSource );
237
238 SetVirtualProgramShader( vp, "texture",osg::Shader::FRAGMENT,
239 "Fragment Texture", TextureFragmentShaderSource );
240
241 SetVirtualProgramShader( vp, "lighting",osg::Shader::VERTEX,
242 "Vertex Lighting", PerVertexDirectionalLightingVertexShaderSource );
243
244 SetVirtualProgramShader( vp, "lighting",osg::Shader::FRAGMENT,
245 "Fragment Lighting", PerVertexLightingFragmentShaderSource );
246
247 transformCenterMiddle->getOrCreateStateSet()->
248 addUniform( new osg::Uniform( "baseTexture", 0 ) );
249
250 }
251
252 // Override default vertex ligting with pixel lighting shaders
253 // For three bottom models
254 if( 1 )
255 {
256 AddLabel( transformCenterBottom, "Per Pixel Lighting VP", offset );
257 VirtualProgram * vp = new VirtualProgram( );
258 transformCenterBottom->getOrCreateStateSet()->setAttribute( vp );
259
260 SetVirtualProgramShader( vp, "lighting",osg::Shader::VERTEX,
261 "Vertex Shader For Per Pixel Lighting",
262 PerFragmentLightingVertexShaderSource );
263
264 SetVirtualProgramShader( vp, "lighting",osg::Shader::FRAGMENT,
265 "Fragment Shader For Per Pixel Lighting",
266 PerFragmentDirectionalLightingFragmentShaderSource );
267 }
268
269 // Additionally set bottom left model texture to procedural blue to
270 // better observe smooth speculars done through per pixel lighting
271 if( 1 )
272 {
273 AddLabel( transformLeftBottom, "Blue Tex VP", offset );
274 VirtualProgram * vp = new VirtualProgram( );
275 transformLeftBottom->getOrCreateStateSet()->setAttribute( vp );
276
277 SetVirtualProgramShader( vp, "texture",osg::Shader::FRAGMENT,
278 "Fragment Shader Procedural Blue Tex",
279 ProceduralBlueTextureFragmentShaderSource );
280 }
281
282 // Additionally change texture mapping to SphereMAp in bottom right model
283 if( 1 )
284 {
285 AddLabel( transformRightBottom, "EnvMap Sphere VP", offset );
286
287 osg::StateSet * ss = transformRightBottom->getOrCreateStateSet();
288 VirtualProgram * vp = new VirtualProgram( );
289 ss->setAttribute( vp );
290 SetVirtualProgramShader( vp, "texture",osg::Shader::VERTEX,
291 "Vertex Texture Sphere Map", SphereMapTextureVertexShaderSource );
292
293 osg::Texture2D * texture = new osg::Texture2D( osgDB::readRefImageFile("Images/skymap.jpg") );
294
295 // Texture is set on stage 1 to not interfere with label text
296 // The same could be achieved with texture override
297 // but such approach also turns off label texture
298 ss->setTextureAttributeAndModes( 1, texture, osg::StateAttribute::ON );
299 ss->addUniform( new osg::Uniform( "baseTexture", 1 ) );
300
301 #if 0 // Could be useful with Fixed Vertex Pipeline
302 osg::TexGen * texGen = new osg::TexGen();
303 texGen->setMode( osg::TexGen::SPHERE_MAP );
304
305 // Texture states applied
306 ss->setTextureAttributeAndModes( 1, texGen, osg::StateAttribute::ON );
307 #endif
308
309 }
310
311
312 // Top center model usues osg::Program overriding VirtualProgram in model
313 if( 1 )
314 {
315 AddLabel( transformCenterTop, "Fixed Vertex + Simple Fragment osg::Program", offset );
316 osg::Program * program = new osg::Program;
317 program->setName( "Trivial Fragment + Fixed Vertex Program" );
318
319 transformCenterTop->getOrCreateStateSet( )->setAttributeAndModes
320 ( program, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
321
322 osg::Shader * shader = new osg::Shader( osg::Shader::FRAGMENT );
323 shader->setName( "Trivial Fragment Shader" );
324 shader->setShaderSource(
325 "uniform sampler2D baseTexture; \n"
326 "void main(void) \n"
327 "{ \n"
328 " gl_FragColor = gl_Color * texture2D( baseTexture,gl_TexCoord[0].xy);\n"
329 "} \n"
330 );
331
332 program->addShader( shader );
333 }
334
335 return transformCenterMiddle;
336 }
337
338 ////////////////////////////////////////////////////////////////////////////////
339 // Shders not used in the example but left for fun if anyone wants to play
340 char LightingVertexShaderSource[] =
341 "// Forward declarations \n" //1
342 " \n" //2
343 "void SpotLight( in int i, in vec3 eye, in vec3 position, in vec3 normal, \n" //3
344 " inout vec4 ambient, inout vec4 diffuse, inout vec4 specular ); \n" //4
345 " \n" //5
346 "void PointLight( in int i, in vec3 eye, in vec3 position, in vec3 normal, \n" //6
347 " inout vec4 ambient, inout vec4 diffuse, inout vec4 specular ); \n" //7
348 " \n" //8
349 "void DirectionalLight( in int i, in vec3 normal, \n" //9
350 " inout vec4 ambient, inout vec4 diffuse, inout vec4 specular ); \n" //10
351 " \n" //11
352 "const int NumEnabledLights = 1; \n" //12
353 " \n" //13
354 "void lighting( in vec3 position, in vec3 normal ) \n" //14
355 "{ \n" //15
356 " vec3 eye = vec3( 0.0, 0.0, 1.0 ); \n" //16
357 " //vec3 eye = -normalize(position); \n" //17
358 " \n" //18
359 " // Clear the light intensity accumulators \n" //19
360 " vec4 amb = vec4(0.0); \n" //20
361 " vec4 diff = vec4(0.0); \n" //21
362 " vec4 spec = vec4(0.0); \n" //22
363 " \n" //23
364 " // Loop through enabled lights, compute contribution from each \n" //24
365 " for (int i = 0; i < NumEnabledLights; i++) \n" //25
366 " { \n" //26
367 " if (gl_LightSource[i].position.w == 0.0) \n" //27
368 " DirectionalLight(i, normal, amb, diff, spec); \n" //28
369 " else if (gl_LightSource[i].spotCutoff == 180.0) \n" //29
370 " PointLight(i, eye, position, normal, amb, diff, spec); \n" //30
371 " else \n" //31
372 " SpotLight(i, eye, position, normal, amb, diff, spec); \n" //32
373 " } \n" //33
374 " \n" //34
375 " gl_FrontColor = gl_FrontLightModelProduct.sceneColor + \n" //35
376 " amb * gl_FrontMaterial.ambient + \n" //36
377 " diff * gl_FrontMaterial.diffuse; \n" //37
378 " \n" //38
379 " gl_FrontSecondaryColor = vec4(spec*gl_FrontMaterial.specular); \n" //39
380 " \n" //40
381 " gl_BackColor = gl_FrontColor; \n" //41
382 " gl_BackSecondaryColor = gl_FrontSecondaryColor; \n" //42
383 "} \n";//43
384
385 char SpotLightShaderSource[] =
386 "void SpotLight(in int i, \n" //1
387 " in vec3 eye, \n" //2
388 " in vec3 position, \n" //3
389 " in vec3 normal, \n" //4
390 " inout vec4 ambient, \n" //5
391 " inout vec4 diffuse, \n" //6
392 " inout vec4 specular) \n" //7
393 "{ \n" //8
394 " float nDotVP; // normal . light direction \n" //9
395 " float nDotHV; // normal . light half vector \n" //10
396 " float pf; // power factor \n" //11
397 " float spotDot; // cosine of angle between spotlight \n" //12
398 " float spotAttenuation; // spotlight attenuation factor \n" //13
399 " float attenuation; // computed attenuation factor \n" //14
400 " float d; // distance from surface to light source \n" //15
401 " vec3 VP; // direction from surface to light position \n" //16
402 " vec3 halfVector; // direction of maximum highlights \n" //17
403 " \n" //18
404 " // Compute vector from surface to light position \n" //19
405 " VP = vec3(gl_LightSource[i].position) - position; \n" //20
406 " \n" //21
407 " // Compute distance between surface and light position \n" //22
408 " d = length(VP); \n" //23
409 " \n" //24
410 " // Normalize the vector from surface to light position \n" //25
411 " VP = normalize(VP); \n" //26
412 " \n" //27
413 " // Compute attenuation \n" //28
414 " attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + \n" //29
415 " gl_LightSource[i].linearAttenuation * d + \n" //30
416 " gl_LightSource[i].quadraticAttenuation *d*d); \n" //31
417 " \n" //32
418 " // See if point on surface is inside cone of illumination \n" //33
419 " spotDot = dot(-VP, normalize(gl_LightSource[i].spotDirection)); \n" //34
420 " \n" //35
421 " if (spotDot < gl_LightSource[i].spotCosCutoff) \n" //36
422 " spotAttenuation = 0.0; // light adds no contribution \n" //37
423 " else \n" //38
424 " spotAttenuation = pow(spotDot, gl_LightSource[i].spotExponent); \n" //39
425 " \n" //40
426 " // Combine the spotlight and distance attenuation. \n" //41
427 " attenuation *= spotAttenuation; \n" //42
428 " \n" //43
429 " halfVector = normalize(VP + eye); \n" //44
430 " \n" //45
431 " nDotVP = max(0.0, dot(normal, VP)); \n" //46
432 " nDotHV = max(0.0, dot(normal, halfVector)); \n" //47
433 " \n" //48
434 " if (nDotVP == 0.0) \n" //49
435 " pf = 0.0; \n" //50
436 " else \n" //51
437 " pf = pow(nDotHV, gl_FrontMaterial.shininess); \n" //52
438 " \n" //53
439 " ambient += gl_LightSource[i].ambient * attenuation; \n" //54
440 " diffuse += gl_LightSource[i].diffuse * nDotVP * attenuation; \n" //55
441 " specular += gl_LightSource[i].specular * pf * attenuation; \n" //56
442 "} \n";//57
443
444 char PointLightShaderSource[] =
445 "void PointLight(in int i, \n" //1
446 " in vec3 eye, \n" //2
447 " in vec3 position, \n" //3
448 " in vec3 normal, \n" //4
449 " inout vec4 ambient, \n" //5
450 " inout vec4 diffuse, \n" //6
451 " inout vec4 specular) \n" //7
452 "{ \n" //8
453 " float nDotVP; // normal . light direction \n" //9
454 " float nDotHV; // normal . light half vector \n" //10
455 " float pf; // power factor \n" //11
456 " float attenuation; // computed attenuation factor \n" //12
457 " float d; // distance from surface to light source \n" //13
458 " vec3 VP; // direction from surface to light position \n" //14
459 " vec3 halfVector; // direction of maximum highlights \n" //15
460 " \n" //16
461 " // Compute vector from surface to light position \n" //17
462 " VP = vec3(gl_LightSource[i].position) - position; \n" //18
463 " \n" //19
464 " // Compute distance between surface and light position \n" //20
465 " d = length(VP); \n" //21
466 " \n" //22
467 " // Normalize the vector from surface to light position \n" //23
468 " VP = normalize(VP); \n" //24
469 " \n" //25
470 " // Compute attenuation \n" //26
471 " attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + \n" //27
472 " gl_LightSource[i].linearAttenuation * d + \n" //28
473 " gl_LightSource[i].quadraticAttenuation * d*d); \n" //29
474 " \n" //30
475 " halfVector = normalize(VP + eye); \n" //31
476 " \n" //32
477 " nDotVP = max(0.0, dot(normal, VP)); \n" //33
478 " nDotHV = max(0.0, dot(normal, halfVector)); \n" //34
479 " \n" //35
480 " if (nDotVP == 0.0) \n" //36
481 " pf = 0.0; \n" //37
482 " else \n" //38
483 " pf = pow(nDotHV, gl_FrontMaterial.shininess); \n" //39
484 " \n" //40
485 " ambient += gl_LightSource[i].ambient * attenuation; \n" //41
486 " diffuse += gl_LightSource[i].diffuse * nDotVP * attenuation; \n" //42
487 " specular += gl_LightSource[i].specular * pf * attenuation; \n" //43
488 "} \n";//44
489
490 char DirectionalLightShaderSource[] =
491 "void DirectionalLight(in int i, \n" //1
492 " in vec3 normal, \n" //2
493 " inout vec4 ambient, \n" //3
494 " inout vec4 diffuse, \n" //4
495 " inout vec4 specular) \n" //5
496 "{ \n" //6
497 " float nDotVP; // normal . light direction \n" //7
498 " float nDotHV; // normal . light half vector \n" //8
499 " float pf; // power factor \n" //9
500 " \n" //10
501 " nDotVP = max(0.0, dot(normal, \n" //11
502 " normalize(vec3(gl_LightSource[i].position)))); \n" //12
503 " nDotHV = max(0.0, dot(normal, \n" //13
504 " vec3(gl_LightSource[i].halfVector))); \n" //14
505 " \n" //15
506 " if (nDotVP == 0.0) \n" //16
507 " pf = 0.0; \n" //17
508 " else \n" //18
509 " pf = pow(nDotHV, gl_FrontMaterial.shininess); \n" //19
510 " \n" //20
511 " ambient += gl_LightSource[i].ambient; \n" //21
512 " diffuse += gl_LightSource[i].diffuse * nDotVP; \n" //22
513 " specular += gl_LightSource[i].specular * pf; \n" //23
514 "} \n";//24
515
516