Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 30 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
30
Dung lượng
336,17 KB
Nội dung
MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 368 float3 unitDirection = normalize(lightDirection); // L // (N.L) - dot product of surface normal and light direction float cosine = dot(unitNormal, unitDirection); // R = 2*(N.L)*N – L float3 reflection = normalize(2*cosine*unitNormal - unitDirection); // (R.V)^n specular reflection. float specularLevel = pow(dot(reflection, unitDirection), 2); specularColor = color*intensity*specularLevel; return specularColor; } The diffuse light is simply the dot product between the light direction vector and the object being lit. The dot product approaches 1 for full intensity with the direct- ness of the light to the surface. When this calculation is done in the pixel shader, the result is interpolated between pixels to produce a nice, smooth-looking light. As you move the camera closer to the wall, the light radiates brightly and fizzles outward from the point that is directly in front of the camera. Diffuse light is modeled by the following equation: Diffuse Color * Diffuse Intensity * N.L To make the light fade from the center even more dramatic, the light intensity is scaled with a fallOff variable. fallOff is the inverted exponent of the scaled dis- tance, d, between the light and pixel. exp(d) equals e d where e is approximately 2.718281828. Because N.L = cos α, as the angle between the surface normal and light vector de- creases, cos α approaches 1 and diffuse light increases. Shining a light directly at a surface normal generates a brighter reflection than a light shone at an angle away from the normal vector. PointLightDiffuse() calculates the color added by the diffuse light: float4 PointLightDiffuse(VStoPS IN){ // unit direction of light vector L float3 lightDirection = normalize(lightPosition - IN.transformedPosition); // brightest angle between L and N = 0 float diffuseLevel = dot(lightDirection, IN.normal); // get distance from light to pixel float distance = distance(lightPosition, IN.transformedPosition); 369 // compute a falloff for the lighting float scale = 0.2f; float fallOff = clamp(1.0f / exp(distance * scale), 0, 1); // adjust the light intensity based on the falloff lightIntensity *= fallOff; // point light diffuse*intensity and color return diffuseLevel * lightIntensity * color; } The point light vertex shader receives the vertex position, texture, and normal data. The position in the window is generated by multiplying the position by the WVP matrix so that each vertex can be seen properly by the camera. transformedPosition is calculated by normalizing the product of the position and World matrix, so this unit vector can be used in the specular and diffuse lighting calculations. The normal vector is also transformed with the World matrix and is then normalized for the specular and diffuse calculations. Ambient light is uniform across the entire surface, so this calculation is performed in the vertex shader to save a little processing time: void VertexShader(in VSinput IN, out VStoPS OUT){ OUT.position = mul(IN.position, wvpMatrix); OUT.transformedPosition = mul(IN.position, worldMatrix); OUT.normal = normalize(mul(IN.normal, (float3x3)worldMatrix)); OUT.UV = IN.UV; OUT.ambientColor = AmbientLight(); } The pixel shader combines the different lights together and blends them with the texture for each pixel. The sum of the ambient, specular, and diffuse light component vectors is equivalent to the combination of different lighting components in Phong’s reflection model. void PixelShader(in VStoPS IN, out PSoutput OUT){ float4 diffuseColor = PointLightDiffuse(IN); float4 specularColor = SpecularLight(IN); OUT.color = tex2D(textureSampler,IN.UV) *(IN.ambientColor+specularColor+diffuseColor); } CHAPTER 22 Lighting MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 370 The technique is identical to others used before this chapter for compiling the ver- tex and pixel shaders and for calling them: technique PointLightShader{ pass p0{ sampler[0] = (textureSampler); vertexshader = compile vs_2_0 VertexShader(); // set up vs pixelshader = compile ps_2_0 PixelShader(); // set up ps } } It is amazing that such a small amount of shader code can generate such a great lighting effect. Point Light Example: The XNA Code All of the shader code just described can be found in the PointLightPS.fx file in the Shaders folder on this book’s website. Be sure to add this file to your project in the Shaders folder. To assist in setting the matrices for the shader, and to provide position data for the lighting calculations, the effect parameters lightEffectWorld , lightEffectWVP , and lightEffectPosition are declared. A texture parame- ter, lightEffectTexture , allows you to set the image applied in the shader from the C# code. The parameter lightEffectIntensity lets you set the intensity of the diffuse point light at run time from the application, and lightEffectColor allows you to set the color of the light. Add these declarations to the game class mod- ule level so you can set these shader variables from your C# code: private Effect lightEffect; // point light shader private EffectParameter lightEffectWorld; // world matrix private EffectParameter lightEffectWVP; // wvp matrix private EffectParameter lightEffectPosition; // light position private EffectParameter lightEffectIntensity; // point light strength private EffectParameter lightEffectTexture; // texture private EffectParameter lightEffectColor; // color of point light To be able to use your shader, you must load and compile it when the program starts. Add code to set up the shader in Initialize(): lightEffect = Content.Load<Effect>("Shaders\\PointLightPS"); To set the data in the shader variables at run time, you must initialize the effect pa- rameters to reference the correct shader variables when the program begins. To make 371 this possible, assign the effect parameters to their corresponding shader variables from Initialize(): lightEffectWVP = lightEffect.Parameters["wvpMatrix"]; lightEffectWorld = lightEffect.Parameters["worldMatrix"]; lightEffectPosition = lightEffect.Parameters["lightPosition"]; lightEffectIntensity = lightEffect.Parameters["lightIntensity"]; lightEffectTexture = lightEffect.Parameters["textureImage"]; lightEffectColor = lightEffect.Parameters["color"]; The LightingShader() method is needed in the game class to apply the PointLightPS.fx shader while drawing with vertices while using an index buffer: private void LightingShader(PrimitiveType primitiveType){ // avoid drawing back face for large amounts of vertices graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace; lightEffect.Begin(); lightEffect.Techniques[0].Passes[0].Begin(); // 5: draw object - select vertex type, primitive type, index, & draw graphics.GraphicsDevice.VertexDeclaration = positionNormalTexture; graphics.GraphicsDevice.Indices = indexBuffer; graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes); // draw grid one row at a time for (int Z = 0; Z < NUM_ROWS - 1; Z++){ graphics.GraphicsDevice.DrawIndexedPrimitives( primitiveType, // primitive Z * NUM_COLS, // start point in vertex 0, // vertex buffer offset NUM_COLS * NUM_ROWS, // total verts in vertex buffer 0, // start point in index buffer 2 * (NUM_COLS - 1)); // end point in index buffer } // end shader lightEffect.Techniques[0].Passes[0].End(); lightEffect.End(); // disable back face culling graphics.GraphicsDevice.RenderState.CullMode = CullMode.None; } CHAPTER 22 Lighting MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 372 Most of the code used to draw the primitive surface has been explained in previous chapters. This includes transforming the object and drawing the vertices using an in- dex buffer reference. Also, the shader’s effect parameters are used here to move the point light with the camera, to set the diffuse light intensity, and to set the texture value. In step 4 of the code, the global variables in the shader are assigned values for the WVP matrix and the World matrix. This combination allows you to generate light in the view space and then to render the objects based on the World matrix. Re- place the existing version of DrawIndexedGrid() with the following code to draw the surfaces with the point light shader: private void DrawIndexedGrid(string surfaceName){ // 1: declare matrices Matrix world, translate, rotateX, scale, rotateY; // 2: initialize matrices translate = Matrix.CreateTranslation(0.0f, -3.6f, 0.0f); scale = Matrix.CreateScale(0.8f, 0.8f, 0.8f); rotateY = Matrix.CreateRotationY(0.0f); rotateX = Matrix.CreateRotationX(0.0f); if (surfaceName == "wall"){ // set parameters for wall rotateX = Matrix.CreateRotationX(MathHelper.Pi/2.0f); translate = Matrix.CreateTranslation(0.0f, 9.20f, -12.8f); lightEffectTexture.SetValue(wallTexture); } else if (surfaceName == "ground") // set parameters for ground lightEffectTexture.SetValue(floorTexture); // 3: build cumulative world matrix using I.S.R.O.T. sequence // identity, scale, rotate, orbit(translate & rotate), translate world = scale * rotateX * rotateY * translate; // 4: pass parameters to shader lightEffectWVP.SetValue(world*cam.viewMatrix*cam.projectionMatrix); lightEffectWorld.SetValue(world); lightEffectPosition.SetValue(new Vector4(cam.position, 1.0f)); lightEffectIntensity.SetValue(2.0f); lightEffectColor.SetValue(new Vector4(1.0f, 1.0f, 1.0f, 1.0f)); // 5: draw object - select vertex type, primitive type, index, and draw LightingShader(PrimitiveType.TriangleStrip); } 373 If you compile and run the project, you will see the point light traveling with the camera. Move closer to the wall, and the light reflected back will become brighter be- cause the point light is closer to the wall surface. Figure 22-4 shows the point light positioned above the center of the ground. The light is brightest directly beneath the light—hopefully this will help you see the point of point light! Point Light in the Vertex Shader Example You won’t always be able to afford pixel-based lighting, because it is expensive for the processor. Moving specular and diffuse lighting calculations into the vertex shader will drastically reduce the number of times these calculations need to be made each frame. The ambient, diffuse, and specular light can be combined in one color variable in the vertex shader, which can then be sent to the pixel shader so that the pixel shader doesn’t have to generate it. When this color data is sent to the pixel shader, it is automatically interpolated between vertices. Using more vertices pro- vides more definition and smoother shading; so for this method to be effective, an index buffer is recommended for primitive surfaces. This example begins with the solution from the previous example. You could fol- low the steps here to modify the shader to implement vertex shader–based point CHAPTER 22 Lighting FIGURE 22-4 Point light demo MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 374 light, or you could just load and reference the PointLightVS.fx file in place of the PointLightPS.fx file in your project to implement it. Once you have changed your shader reference, you will need to load the new shader from Initialize() when the program begins: lightEffect = Content.Load<Effect>("Shaders\\PointLightVS"); With this change, less information needs to be passed to the pixel shader, so a new struct for the vertex shader output is used. This struct is already added to the PointLightVS.fx file for you. However, if you are modifying the effect file from the previous example you will need to add this new struct. struct VStoPS2{ float4 position : POSITION0; float4 color : COLOR; float2 UV : TEXCOORD0; }; The revised version of the vertex shader uses the new struct to define the output. Note that the calculations for all lights are now performed in the vertex shader. The color variable that is sent to the pixel shader stores the sum of the ambient, diffuse, and specular lights. Replace the existing vertex shader with this revised version to process the lighting calculations before sending the output to the pixel shader: void VertexShader(in VSinput IN, out VStoPS2 OUT){ VStoPS vsData; // original output values used for color calculation vsData.transformedPosition = mul(IN.position, worldMatrix); vsData.normal = normalize(mul(IN.normal, (float3x3)worldMatrix)); vsData.position = mul(IN.position, wvpMatrix); vsData.UV = IN.UV; OUT.position = vsData.position; // position output OUT.UV = vsData.UV; // uv output vsData.ambientColor = AmbientLight(); // color output float4 diffuseColor = PointLightDiffuse(vsData); float4 specularColor = SpecularLight(vsData); OUT.color = (vsData.ambientColor + specularColor + diffuseColor); } A slight change is made in the pixel shader to receive the new vertex shader output, which already includes the combined ambient, diffuse, and specular light: void PixelShader(in VStoPS2 IN, out PSoutput OUT){ OUT.color = tex2D(textureSampler, IN.UV)*(IN.color); } 375 To view a stationary point light, in your XNA code, set the position of the light to a constant value. (Or you could continue to move the light with your camera if you prefer.) You can make the position of the point light stationary by replacing the in- struction that moves the light with the camera in DrawIndexedGrid(): lightEffectPosition.SetValue(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); When you run this version of the code, you will still see the point light. It will not be defined as much as the pixel shader point light, but you may notice a performance boost when running it. A simple lighting system, such as a lone directional light or the sun, can add depth to your game and reveal the details in your environment. Point light can add intrigu- ing details for night-time or indoor settings. As you can see, the effect is quite bril- liant. C HAPTER 22 REVIEW EXERCISES To get the most from this chapter, try out these chapter review exercises. 1. Complete the step-by-step examples presented in this chapter, if you have not already done so. 2. After completing the directional light demonstration using the BasicEffect object, try reducing the number of vertices that are stored in the vertex buffer by lowering the number of rows and columns to two each. Run the demo again (after this change has been made) and notice how the specular detail diminishes. Then, increase the total number of vertices for rows and columns to 50 each. Notice how the specular lighting’s effect improves with more vertices. 3. Using the directional light example, change the Y value of the normal in the vertex buffer from +1 to –1. Notice how everything turns black. Explain why this happens. 4. What is a useful intensity level for ambient light during daytime settings in the directional light demo? What is a useful intensity level for ambient light during evening settings in the directional light demo? CHAPTER 22 Lighting This page intentionally left blank CHAPTER CHAPTER 23 Input Devices Input Devices [...]... for each game pad are retrieved with the GetState() method and PlayerIndex attribute to identify the controller: gamePadState[0] gamePadState[1] gamePadState[2] gamePadState[3] = = = = GamePad.GetState(PlayerIndex.One); GamePad.GetState(PlayerIndex.Two); GamePad.GetState(PlayerIndex.Three); GamePad.GetState(PlayerIndex.Four); Handling Pressed and Released States Most of the controls on the game controller... handles events for the game controller The game controller itself provides several options to obtain 379 Input Devices C H A P T E R 380 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE user input through presses and shifts of the thumbstick, as well as presses to the DPad, buttons, left and right bumpers, and triggers Figure 23-1 shows the name of each control Game Pad States The GamePadState object for... within the game window: private String[] gamePadConnected = new String[4]; All controller states, including the IsConnected property, are retrieved by calling the GetState() method for each controller This code can be implemented from the Update() method: gamePadState[0] gamePadState[1] gamePadState[2] gamePadState[3] = = = = GamePad.GetState(PlayerIndex.One); GamePad.GetState(PlayerIndex.Two); GamePad.GetState(PlayerIndex.Three);... H A P T E R 396 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE UpdateInputEvents() method to send rumbles to the left and right sides of the controller whenever a trigger is squeezed: GamePad.SetVibration(0, gamePadState[0].Triggers.Left, gamePadState[0].Triggers.Right); When you run the program now, you will see the corresponding press, release, and XY values for the keyboard, mouse, and game controller... is stored in the corresponding string variable: gpA = gpBack = gpStart = "released"; if (gamePadState[0].Buttons.A == ButtonState.Pressed) gpA = "pressed"; 391 Input Devices C H A P T E R 392 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE if (gamePadState[0].Buttons.Back == ButtonState.Pressed) gpBack = "pressed"; if (gamePadState[0].Buttons.Start == ButtonState.Pressed) gpStart = "pressed"; The results from... usually declared as a four-element array Adding this instruction to the top of the game class allows access to the GamePadState object for each controller throughout the program private GamePadState[] gamePadState = new GamePadState[4]; Before handling game controller states, you first need to determine whether the game controller is actually connected String variables, declared in the module declaration... Buttons.LeftStick Buttons.Y Game pad controls 381 Input Devices C H A P T E R 382 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE Triggers You can enable intuitive features such as acceleration or rapid firing with the Xbox 360 controller triggers On every controller there is one left and one right trigger Each trigger returns a float that ranges from 0 (for released) to 1 (for fully pressed) float GamePadState.Triggers.Right... fundamental to every gamer’s experience Nowadays, this means that you need to support the keyboard, mouse, Xbox 360 game controller, Zune controls, and possibly even a wireless racing wheel The XNA Framework greatly simplifies this task Specifically, the Microsoft. Xna. Framework.Input namespace enables the capture of button press and release events, mouse click events, keyboard presses and game controller... GamePadState object for the controller allows you to check the state of each control on each game controller at every frame Because it is possible to have up to four game controllers connected to your Xbox 360, the GamePadState object is often declared as an array with a size of four: private GamePadState[] gamePadState = new GamePadState[4]; Although the array has room for up to four controllers, if only one... not have your game controller with you, or your intended audience may only have a keyboard and mouse as input devices For this reason, when running your games on the PC, your code should always consider the keyboard as an alternative for user input To handle the input events, a reference to the Microsoft. Xna. Framework.Input namespace is required at the top of the Game1 .cs file where the game class is . measured with a float that ranges from 0 to 1. GamePad.SetVibration(int controllerNumber, float LRumble, float RRumble); MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 382 383 Input Example This example. tex2D(textureSampler,IN.UV) *(IN.ambientColor+specularColor+diffuseColor); } CHAPTER 22 Lighting MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 370 The technique is identical to others used before this chapter for. culling graphics.GraphicsDevice.RenderState.CullMode = CullMode.None; } CHAPTER 22 Lighting MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 372 Most of the code used to draw the primitive surface has been explained