官术网_书友最值得收藏!

Implementing lens flare within the HiDef profile

Modern GPUs are very good at determining whether one set of polygons is obscured by another set. We can use this to our advantage when creating lens flares within a HiDef profile.

Getting ready

This recipe assumes that you've already got a scene, rendering correctly, albeit without a lens flare.

How to do it...

To create a lens flare within the HiDef profile:

  1. Create a new class to hold the lens flare behavior:
    class HiDefLensFlare
    {
    
  2. Add some instance-level variables to hold the details of the occlusion test, the lighting, and the glow image:
    SpriteBatch spriteBatch;
    GraphicsDevice graphicsDevice;
    public BasicEffect ShadowCaptureEffect;
    OcclusionQuery occlusionQuery;
    bool occlusionQueryActive;
    float occlusionAlpha;
    const float querySize = 50;
    VertexPositionColor[] queryVertices;
    public Vector3 LightDirection = Vector3.Normalize(new Vector3(0.5f, -0.1f, 0.5f));
    Vector2 lightPosition;
    bool lightBehindCamera;
    Texture2D glow;
    Vector2 glowOrigin;
    float glowScale = 400f;
    
  3. Next, add a constructor to set up the occlusion test prerequisites and load the glow texture:
    public HiDefLensFlare(GraphicsDevice graphicsDevice, ContentManager content)
    {
    this.graphicsDevice = graphicsDevice;
    spriteBatch = new SpriteBatch(graphicsDevice);
    ShadowCaptureEffect = new BasicEffect(graphicsDevice)
    {
    View = Matrix.Identity,
    VertexColorEnabled = true
    };
    occlusionQuery = new OcclusionQuery(graphicsDevice);
    queryVertices = new VertexPositionColor[4];
    queryVertices[0].Position = new Vector3(-querySize / 2, -querySize / 2, -1);
    queryVertices[1].Position = new Vector3(querySize / 2, -querySize / 2, -1);
    queryVertices[2].Position = new Vector3(-querySize / 2, querySize / 2, -1);
    queryVertices[3].Position = new Vector3(querySize / 2, querySize / 2, -1);
    glow = content.Load<Texture2D>(@"lensflare/glow");
    glowOrigin = new Vector2(glow.Width, glow.Height) / 2;
    }
    
  4. Create a new BlendState instance-level variable so the ocular test can proceed without changing the visible image:
    static readonly BlendState ColorWriteDisable = new BlendState
    {
    ColorWriteChannels = ColorWriteChannels.None
    };
    
  5. Add a new method to perform the ocular test:
    public void Measure(Matrix view, Matrix projection)
    {
    
  6. Calculate the position of the lens flare on screen, and exit early if it's behind the player's viewpoint:
    var infiniteView = view;
    infiniteView.Translation = Vector3.Zero;
    var viewport = graphicsDevice.Viewport;
    var projectedPosition = viewport.Project(
    -LightDirection, projection,
    infiniteView, Matrix.Identity);
    if ((projectedPosition.Z < 0) ||
    (projectedPosition.Z > 1))
    {
    lightBehindCamera = true;
    return;
    }
    lightPosition = new Vector2(projectedPosition.X, projectedPosition.Y);
    lightBehindCamera = false;
    
  7. Add the calculation for how much of the lens flare test area is occluded by the scene once the previous occlusion test has completed:
    if (occlusionQueryActive)
    {
    if (!occlusionQuery.IsComplete)
    {
    return;
    }
    const float queryArea = querySize * querySize;
    occlusionAlpha = Math.Min(
    occlusionQuery.PixelCount / queryArea, 1);
    }
    
  8. Set up for the next occlusion query:
    graphicsDevice.BlendState = ColorWriteDisable;
    graphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
    ShadowCaptureEffect.World = Matrix.CreateTranslation(
    lightPosition.X,
    lightPosition.Y, 0);
    ShadowCaptureEffect.Projection = Matrix.CreateOrthographicOffCenter(0,
    viewport.Width,
    viewport.Height,
    0, 0, 1);
    ShadowCaptureEffect.CurrentTechnique.Passes[0].Apply();
    
  9. Render the lens flare test vertices inside the occlusion test to determine how many pixels were rendered:
    occlusionQuery.Begin();
    graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, queryVertices, 0, 2);
    occlusionQuery.End();
    occlusionQueryActive = true;
    
  10. Complete the class by adding a Draw() method to render the glow:
    public void Draw()
    {
    if (lightBehindCamera || occlusionAlpha <= 0)
    return;
    Color color = Color.White * occlusionAlpha;
    Vector2 origin = new Vector2(glow.Width, glow.Height) / 2;
    float scale = glowScale * 2 / glow.Width;
    spriteBatch.Begin();
    spriteBatch.Draw(glow, lightPosition, null, color, 0,
    origin, scale, SpriteEffects.None, 0);
    spriteBatch.End();
    }
    

How it works...

XNA and the underlying DirectX infrastructure contain a rather handy little diagnostic tool in the form of the occlusion test. With this test, you can count how many pixels were filled when trying to render a particular portion of a scene.

We utilize this in the lens flare example by attempting to render a small rectangle across the opposite side of the scene from the player's viewpoint, and measuring how much of it is obscured by the scene's meshes. With this number, we adjust the opacity of the lens flare's glow texture up or down to simulate the sun disappearing either partially or completely behind an object.

主站蜘蛛池模板: 苍南县| 仙桃市| 饶平县| 云浮市| 宁乡县| 屏南县| 陇西县| 辰溪县| 安多县| 吉安县| 道真| 长寿区| 凉城县| 尉氏县| 宿迁市| 合山市| 东乌| 涡阳县| 灵台县| 高尔夫| 响水县| 滁州市| 南宁市| 抚松县| 柘城县| 都匀市| 苗栗县| 海淀区| 南皮县| 南雄市| 镇安县| 台中县| 成都市| 石城县| 四平市| 西藏| 庆城县| 临沂市| 龙口市| 保山市| 申扎县|