You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: articles/tutorials/advanced/2d_shaders/08_light_effect/index.md
+26-26Lines changed: 26 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -315,17 +315,13 @@ Generating normal maps is an artform. Generally, you find a _normal map picker_,
315
315
316
316
For this effect to work, we need an extra texture for every frame of every sprite we are drawing in the game. Given that the textures are currently coming from an atlas, the easiest thing to do will be to create a _second_ texture that shares the same layout as the first, but uses normal data instead.
317
317
318
-
For reference, here is the existing atlas texture.
318
+
For reference, the existing texture atlas is on the left, and a version of the atlas with normal maps is on the right.
319
319
320
-
||
|**Figure 8-17: The existing texture atlas**|**Figure 8-18: The normal texture atlas**|
323
323
324
-
And here is the atlast, but with normal data where the game sprites are instead. Download the [atlas-normal.png](./images/atlas-normal.png) texture and add it to the _DungeonSlime_'s content folder. Include it in the mgcb content file.
325
-
326
-
||
Download the [atlas-normal.png](./images/atlas-normal.png) texture and add it to the _DungeonSlime_'s content folder. Include it in the mgcb content file.
329
325
330
326
Now that we have the art assets, it is time to work the normal maps into the code.
331
327
@@ -374,9 +370,9 @@ Now that we have the art assets, it is time to work the normal maps into the cod
374
370
375
371
8. And do not forget to call the `DebugDraw()` method from the `GameScene`'s `Draw()` method. Then you will see a totally `red``NormalBuffer`, because the shader is hard coding the value to `float4(1,0,0,1)`.
376
372
377
-
||
373
+
||
To start rendering the normal values themselves, we need to load the normal texture into the `GameScene` and pass it along to the `gameEffect.fx` effect.
382
378
@@ -402,9 +398,9 @@ To start rendering the normal values themselves, we need to load the normal text
402
398
403
399
6. Now the `NormalBuffer` is being populated with the normal data for each sprite.
404
400
405
-
||
401
+
||
@@ -426,7 +422,7 @@ When each individual light is drawn into the `LightBuffer`, it needs to use the
426
422
427
423
In order to override the vertex shader function, we will need to repeat the `MatrixTransform` work from the previous chapter. However, it would better to _re-use_ the work from the previous chapter so that the lights also tilt and respond to the `MatrixTransform` that the rest of the game world uses.
428
424
429
-
Add a reference in the `3dEffect.fxh` file in the `pointLightEffect.fx` shader:
425
+
Add a reference to the `3dEffect.fxh` file in the `pointLightEffect.fx` shader:
430
426
431
427
[!code-hlsl[](./snippets/snippet-8-56.hlsl)]
432
428
@@ -458,7 +454,9 @@ When each individual light is drawn into the `LightBuffer`, it needs to use the
458
454
459
455
[!code-csharp[](./snippets/snippet-8-62.cs)]
460
456
461
-

|**Figure 8-19: The point light can access screen space**|
462
460
463
461
11. Now, the `pointLightEffect` can use the screen space coordinates to sample the `NormalBuffer` values. To build intuition, start by just returning the values from the `NormalBuffer`.
464
462
@@ -468,27 +466,29 @@ When each individual light is drawn into the `LightBuffer`, it needs to use the
468
466
469
467
12. Strangely, this will return a `white` box, instead of the normal data as expected.
470
468
471
-
||
469
+
||
|**Figure 8-21: A white box instead of the normal data?**|
471
+
|**Figure 8-20: A white box instead of the normal data?**|
474
472
475
473
This happens because of a misunderstanding between the shader compiler and `SpriteBatch`. _Most_ of the time when `SpriteBatch` is being used, there is a single `Texture` and `Sampler` being used to draw a sprite to the screen. The `SpriteBatch`'s draw function passes the given `Texture2D` to the shader by setting it in the `GraphicsDevice.Textures` array [directly](https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Graphics/SpriteBatcher.cs#L212). The texture is not being passed _by name_, it is being passed by _index_. In the lighting case, the `SpriteBatch` is being drawn with the `Core.Pixel` texture (a white 1x1 image we generated in the earlier chapters).
476
474
477
475
However, the shader compiler will aggressively optimize away data that isn't being used in the shader. The current `pointLightEffect.fx` does not _use_ the default texture or sampler that `SpriteBatch` expects by default. The default texture is _removed_ from the shader during compilation, because it isn't used anywhere and has no effect. The only texture that is left is the `NormalBuffer`, which now becomes the first indexable texture.
478
476
479
477
Despite passing the `NormalBuffer` texture to the named `NormalTexture``Texture2D` parameter in the shader before calling `SpriteBatch.Draw()`, the `SpriteBatch` code itself then overwrites whatever is in texture slot `0` with the texture passed to the `Draw()` call, the white pixel.
480
478
481
-
There are two workarounds. If performance is not _critical_, you could add back in a throw-away read from the main `SpriteTextureSampler` , and use the resulting color _somehow_ in the computation for the final result of the shader. However, this is useless work, and will likely confuse anyone who looks at the shader in the future. The other workaround is to pass the `NormalBuffer` to the `Draw()` function directly, and not bother sending it as a shader parameter at all.
479
+
There are two workarounds.
480
+
1. Modify the shader code to read data from the main `SpriteTextureSampler` and use the resulting color _somehow_ in the computation fro the final result of the shader. For example, You could multiple the color by a very small constant, like `.00001`, and then add the product to the final color. It would have no perceivable effect, but the shader compiler wouldn't be optimize the sampler away. Hoewver, this is useless and silly work. Worse, it will likely confuse anyone who looks at the shader in the future.
481
+
2. The better approach is to pass the `NormalBuffer` to the `Draw()` function directly, and not bother sending it as a shader parameter at all.
482
482
483
483
Change the `PointLight.Draw()` method to pass the `normalBuffer` to the `SpriteBatch.Draw()` method _instead_ of passing it in as a parameter to the `PointLightMaterial`. Here is the new `PointLight.Draw()` method:
484
484
485
485
[!code-csharp[](./snippets/snippet-8-64.cs)]
486
486
487
487
And now the normal map is being rendered where the light exists.
488
488
489
-
||
489
+
||
To drive the effect for a moment, this gif shows the normal effect being blended in. Notice how the wings on the bat shade differently based on their position towards the light as the normal effect is brought in.
508
508
509
-
||
509
+
||
0 commit comments