-
Notifications
You must be signed in to change notification settings - Fork 0
/
Scene.cpp
946 lines (721 loc) · 37.7 KB
/
Scene.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
//--------------------------------------------------------------------------------------
// Scene geometry and layout preparation
// Scene rendering & update
//--------------------------------------------------------------------------------------
#include "Scene.h"
#include "Mesh.h"
#include "Model.h"
#include "Camera.h"
#include "State.h"
#include "Shader.h"
#include "Input.h"
#include "Common.h"
#include "CVector2.h"
#include "CVector3.h"
#include "CMatrix4x4.h"
#include "MathHelpers.h" // Helper functions for maths
#include "GraphicsHelpers.h" // Helper functions to unclutter the code here
#include "ColourRGBA.h"
#include <array>
#include <sstream>
#include <memory>
//--------------------------------------------------------------------------------------
// Scene Data
//--------------------------------------------------------------------------------------
//********************
// Available post-processes
enum class PostProcess
{
None,
Copy,
Tint,
Gradient, // Gradient filter
Blur, // Blur Effect
Underwater, // Underwater Effect
Retro, // Retro Effect
GaussianBlur, // Gaussian Blur Horizontal
GaussianBlurVertical, // Gaussian Blur Vertical
Bloom, // Bloom Effect
GreyNoise,
Burn,
Distort,
Spiral,
HeatHaze,
};
enum class PostProcessMode
{
Fullscreen,
Area,
Polygon,
};
auto gCurrentPostProcess = PostProcess::None;
auto gCurrentPostProcessMode = PostProcessMode::Fullscreen;
//********************
// Constants controlling speed of movement/rotation (measured in units per second because we're using frame time)
const float ROTATION_SPEED = 1.5f; // Radians per second for rotation
const float MOVEMENT_SPEED = 50.0f; // Units per second for movement (what a unit of length is depends on 3D model - i.e. an artist decision usually)
// Lock FPS to monitor refresh rate, which will typically set it to 60fps. Press 'p' to toggle to full fps
bool lockFPS = true;
// Meshes, models and cameras, same meaning as TL-Engine. Meshes prepared in InitGeometry function, Models & camera in InitScene
Mesh* gStarsMesh;
Mesh* gGroundMesh;
Mesh* gCubeMesh;
Mesh* gCrateMesh;
Mesh* gLightMesh;
Model* gStars;
Model* gGround;
Model* gCube;
Model* gCrate;
Camera* gCamera;
// Store lights in an array in this exercise
const int NUM_LIGHTS = 2;
struct Light
{
Model* model;
CVector3 colour;
float strength;
};
Light gLights[NUM_LIGHTS];
// Additional light information
CVector3 gAmbientColour = { 0.3f, 0.3f, 0.4f }; // Background level of light (slightly bluish to match the far background, which is dark blue)
float gSpecularPower = 256; // Specular power controls shininess - same for all models in this app
ColourRGBA gBackgroundColor = { 0.3f, 0.3f, 0.4f, 1.0f };
// Variables controlling light1's orbiting of the cube
const float gLightOrbitRadius = 20.0f;
const float gLightOrbitSpeed = 0.7f;
//--------------------------------------------------------------------------------------
// Constant Buffers
//--------------------------------------------------------------------------------------
// Variables sent over to the GPU each frame
// The structures are now in Common.h
// IMPORTANT: Any new data you add in C++ code (CPU-side) is not automatically available to the GPU
// Anything the shaders need (per-frame or per-model) needs to be sent via a constant buffer
PerFrameConstants gPerFrameConstants; // The constants (settings) that need to be sent to the GPU each frame (see common.h for structure)
ID3D11Buffer* gPerFrameConstantBuffer; // The GPU buffer that will recieve the constants above
PerModelConstants gPerModelConstants; // As above, but constants (settings) that change per-model (e.g. world matrix)
ID3D11Buffer* gPerModelConstantBuffer; // --"--
//**************************
PostProcessingConstants gPostProcessingConstants; // As above, but constants (settings) for each post-process
ID3D11Buffer* gPostProcessingConstantBuffer; // --"--
//**************************
//--------------------------------------------------------------------------------------
// Textures
//--------------------------------------------------------------------------------------
// DirectX objects controlling textures used in this lab
ID3D11Resource* gStarsDiffuseSpecularMap = nullptr;
ID3D11ShaderResourceView* gStarsDiffuseSpecularMapSRV = nullptr;
ID3D11Resource* gGroundDiffuseSpecularMap = nullptr;
ID3D11ShaderResourceView* gGroundDiffuseSpecularMapSRV = nullptr;
ID3D11Resource* gCrateDiffuseSpecularMap = nullptr;
ID3D11ShaderResourceView* gCrateDiffuseSpecularMapSRV = nullptr;
ID3D11Resource* gCubeDiffuseSpecularMap = nullptr;
ID3D11ShaderResourceView* gCubeDiffuseSpecularMapSRV = nullptr;
ID3D11Resource* gLightDiffuseMap = nullptr;
ID3D11ShaderResourceView* gLightDiffuseMapSRV = nullptr;
//****************************
// Post processing textures
// This texture will have the scene renderered on it. Then the texture is then used for post-processing
ID3D11Texture2D* gSceneTexture = nullptr; // This object represents the memory used by the texture on the GPU
ID3D11RenderTargetView* gSceneRenderTarget = nullptr; // This object is used when we want to render to the texture above
ID3D11ShaderResourceView* gSceneTextureSRV = nullptr; // This object is used to give shaders access to the texture above (SRV = shader resource view)
// Additional textures used for specific post-processes
ID3D11Resource* gNoiseMap = nullptr;
ID3D11ShaderResourceView* gNoiseMapSRV = nullptr;
ID3D11Resource* gBurnMap = nullptr;
ID3D11ShaderResourceView* gBurnMapSRV = nullptr;
ID3D11Resource* gDistortMap = nullptr;
ID3D11ShaderResourceView* gDistortMapSRV = nullptr;
//****************************
// Tried to enable multiple effects at once
/*
std::vector<PostProcess> postProcessEffectList; // List of post-processes to apply
void EnablePostProcess(PostProcess postProcessEffect) // Post Process list to enable multiple effects at once
{
if (postProcessEffect != PostProcess::None)
{
postProcessEffectList.push_back(postProcessEffect);
}
}
void ResetPostProcess() // Reset the effect list / Clear the effects
{
postProcessEffectList.clear();
}
*/
//--------------------------------------------------------------------------------------
// Initialise scene geometry, constant buffers and states
//--------------------------------------------------------------------------------------
// Prepare the geometry required for the scene
// Returns true on success
bool InitGeometry()
{
////--------------- Load meshes ---------------////
// Load mesh geometry data, just like TL-Engine this doesn't create anything in the scene. Create a Model for that.
try
{
gStarsMesh = new Mesh("Stars.x");
gGroundMesh = new Mesh("Hills.x");
gCubeMesh = new Mesh("Cube.x");
gCrateMesh = new Mesh("CargoContainer.x");
gLightMesh = new Mesh("Light.x");
}
catch (std::runtime_error e) // Constructors cannot return error messages so use exceptions to catch mesh errors (fairly standard approach this)
{
gLastError = e.what(); // This picks up the error message put in the exception (see Mesh.cpp)
return false;
}
////--------------- Load / prepare textures & GPU states ---------------////
// Load textures and create DirectX objects for them
// The LoadTexture function requires you to pass a ID3D11Resource* (e.g. &gCubeDiffuseMap), which manages the GPU memory for the
// texture and also a ID3D11ShaderResourceView* (e.g. &gCubeDiffuseMapSRV), which allows us to use the texture in shaders
// The function will fill in these pointers with usable data. The variables used here are globals found near the top of the file.
if (!LoadTexture("Stars.jpg", &gStarsDiffuseSpecularMap, &gStarsDiffuseSpecularMapSRV) ||
!LoadTexture("GrassDiffuseSpecular.dds", &gGroundDiffuseSpecularMap, &gGroundDiffuseSpecularMapSRV) ||
!LoadTexture("StoneDiffuseSpecular.dds", &gCubeDiffuseSpecularMap, &gCubeDiffuseSpecularMapSRV) ||
!LoadTexture("CargoA.dds", &gCrateDiffuseSpecularMap, &gCrateDiffuseSpecularMapSRV) ||
!LoadTexture("Flare.jpg", &gLightDiffuseMap, &gLightDiffuseMapSRV) ||
!LoadTexture("Noise.png", &gNoiseMap, &gNoiseMapSRV) ||
!LoadTexture("Burn.png", &gBurnMap, &gBurnMapSRV) ||
!LoadTexture("Distort.png", &gDistortMap, &gDistortMapSRV))
{
gLastError = "Error loading textures";
return false;
}
// Create all filtering modes, blending modes etc. used by the app (see State.cpp/.h)
if (!CreateStates())
{
gLastError = "Error creating states";
return false;
}
////--------------- Prepare shaders and constant buffers to communicate with them ---------------////
// Load the shaders required for the geometry we will use (see Shader.cpp / .h)
if (!LoadShaders())
{
gLastError = "Error loading shaders";
return false;
}
// Create GPU-side constant buffers to receive the gPerFrameConstants and gPerModelConstants structures above
// These allow us to pass data from CPU to shaders such as lighting information or matrices
// See the comments above where these variable are declared and also the UpdateScene function
gPerFrameConstantBuffer = CreateConstantBuffer(sizeof(gPerFrameConstants));
gPerModelConstantBuffer = CreateConstantBuffer(sizeof(gPerModelConstants));
gPostProcessingConstantBuffer = CreateConstantBuffer(sizeof(gPostProcessingConstants));
if (gPerFrameConstantBuffer == nullptr || gPerModelConstantBuffer == nullptr || gPostProcessingConstantBuffer == nullptr)
{
gLastError = "Error creating constant buffers";
return false;
}
//********************************************
//**** Create Scene Texture
// We will render the scene to this texture instead of the back-buffer (screen), then we post-process the texture onto the screen
// This is exactly the same code we used in the graphics module when we were rendering the scene onto a cube using a texture
// Using a helper function to load textures from files above. Here we create the scene texture manually
// as we are creating a special kind of texture (one that we can render to). Many settings to prepare:
D3D11_TEXTURE2D_DESC sceneTextureDesc = {};
sceneTextureDesc.Width = gViewportWidth; // Full-screen post-processing - use full screen size for texture
sceneTextureDesc.Height = gViewportHeight;
sceneTextureDesc.MipLevels = 1; // No mip-maps when rendering to textures (or we would have to render every level)
sceneTextureDesc.ArraySize = 1;
sceneTextureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // RGBA texture (8-bits each)
sceneTextureDesc.SampleDesc.Count = 1;
sceneTextureDesc.SampleDesc.Quality = 0;
sceneTextureDesc.Usage = D3D11_USAGE_DEFAULT;
sceneTextureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; // IMPORTANT: Indicate we will use texture as render target, and pass it to shaders
sceneTextureDesc.CPUAccessFlags = 0;
sceneTextureDesc.MiscFlags = 0;
if (FAILED(gD3DDevice->CreateTexture2D(&sceneTextureDesc, NULL, &gSceneTexture)))
{
gLastError = "Error creating scene texture";
return false;
}
// We created the scene texture above, now we get a "view" of it as a render target, i.e. get a special pointer to the texture that
// we use when rendering to it (see RenderScene function below)
if (FAILED(gD3DDevice->CreateRenderTargetView(gSceneTexture, NULL, &gSceneRenderTarget)))
{
gLastError = "Error creating scene render target view";
return false;
}
// We also need to send this texture (resource) to the shaders. To do that we must create a shader-resource "view"
D3D11_SHADER_RESOURCE_VIEW_DESC srDesc = {};
srDesc.Format = sceneTextureDesc.Format;
srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srDesc.Texture2D.MostDetailedMip = 0;
srDesc.Texture2D.MipLevels = 1;
if (FAILED(gD3DDevice->CreateShaderResourceView(gSceneTexture, &srDesc, &gSceneTextureSRV)))
{
gLastError = "Error creating scene shader resource view";
return false;
}
return true;
}
// Prepare the scene
// Returns true on success
bool InitScene()
{
////--------------- Set up scene ---------------////
gStars = new Model(gStarsMesh);
gGround = new Model(gGroundMesh);
gCube = new Model(gCubeMesh);
gCrate = new Model(gCrateMesh);
// Initial positions
gCube->SetPosition({ 42, 5, -10 });
gCube->SetRotation({ 0.0f, ToRadians(-110.0f), 0.0f });
gCube->SetScale(1.5f);
gCrate->SetPosition({ -10, 0, 90 });
gCrate->SetRotation({ 0.0f, ToRadians(40.0f), 0.0f });
gCrate->SetScale(6.0f);
gStars->SetScale(8000.0f);
// Light set-up - using an array this time
for (int i = 0; i < NUM_LIGHTS; ++i)
{
gLights[i].model = new Model(gLightMesh);
}
gLights[0].colour = { 0.8f, 0.8f, 1.0f };
gLights[0].strength = 10;
gLights[0].model->SetPosition({ 30, 10, 0 });
gLights[0].model->SetScale(pow(gLights[0].strength, 1.0f)); // Convert light strength into a nice value for the scale of the light - equation is ad-hoc.
gLights[1].colour = { 1.0f, 0.8f, 0.2f };
gLights[1].strength = 40;
gLights[1].model->SetPosition({ -70, 30, 100 });
gLights[1].model->SetScale(pow(gLights[1].strength, 1.0f));
////--------------- Set up camera ---------------////
gCamera = new Camera();
gCamera->SetPosition({ 25, 18, -45 });
gCamera->SetRotation({ ToRadians(10.0f), ToRadians(7.0f), 0.0f });
return true;
}
// Release the geometry and scene resources created above
void ReleaseResources()
{
ReleaseStates();
if (gSceneTextureSRV) gSceneTextureSRV->Release();
if (gSceneRenderTarget) gSceneRenderTarget->Release();
if (gSceneTexture) gSceneTexture->Release();
if (gDistortMapSRV) gDistortMapSRV->Release();
if (gDistortMap) gDistortMap->Release();
if (gBurnMapSRV) gBurnMapSRV->Release();
if (gBurnMap) gBurnMap->Release();
if (gNoiseMapSRV) gNoiseMapSRV->Release();
if (gNoiseMap) gNoiseMap->Release();
if (gLightDiffuseMapSRV) gLightDiffuseMapSRV->Release();
if (gLightDiffuseMap) gLightDiffuseMap->Release();
if (gCrateDiffuseSpecularMapSRV) gCrateDiffuseSpecularMapSRV->Release();
if (gCrateDiffuseSpecularMap) gCrateDiffuseSpecularMap->Release();
if (gCubeDiffuseSpecularMapSRV) gCubeDiffuseSpecularMapSRV->Release();
if (gCubeDiffuseSpecularMap) gCubeDiffuseSpecularMap->Release();
if (gGroundDiffuseSpecularMapSRV) gGroundDiffuseSpecularMapSRV->Release();
if (gGroundDiffuseSpecularMap) gGroundDiffuseSpecularMap->Release();
if (gStarsDiffuseSpecularMapSRV) gStarsDiffuseSpecularMapSRV->Release();
if (gStarsDiffuseSpecularMap) gStarsDiffuseSpecularMap->Release();
if (gPostProcessingConstantBuffer) gPostProcessingConstantBuffer->Release();
if (gPerModelConstantBuffer) gPerModelConstantBuffer->Release();
if (gPerFrameConstantBuffer) gPerFrameConstantBuffer->Release();
ReleaseShaders();
// See note in InitGeometry about why we're not using unique_ptr and having to manually delete
for (int i = 0; i < NUM_LIGHTS; ++i)
{
delete gLights[i].model; gLights[i].model = nullptr;
}
delete gCamera; gCamera = nullptr;
delete gCrate; gCrate = nullptr;
delete gCube; gCube = nullptr;
delete gGround; gGround = nullptr;
delete gStars; gStars = nullptr;
delete gLightMesh; gLightMesh = nullptr;
delete gCrateMesh; gCrateMesh = nullptr;
delete gCubeMesh; gCubeMesh = nullptr;
delete gGroundMesh; gGroundMesh = nullptr;
delete gStarsMesh; gStarsMesh = nullptr;
}
//--------------------------------------------------------------------------------------
// Scene Rendering
//--------------------------------------------------------------------------------------
// Render everything in the scene from the given camera
void RenderSceneFromCamera(Camera* camera)
{
// Set camera matrices in the constant buffer and send over to GPU
gPerFrameConstants.cameraMatrix = camera->WorldMatrix();
gPerFrameConstants.viewMatrix = camera->ViewMatrix();
gPerFrameConstants.projectionMatrix = camera->ProjectionMatrix();
gPerFrameConstants.viewProjectionMatrix = camera->ViewProjectionMatrix();
UpdateConstantBuffer(gPerFrameConstantBuffer, gPerFrameConstants);
// Indicate that the constant buffer we just updated is for use in the vertex shader (VS), geometry shader (GS) and pixel shader (PS)
gD3DContext->VSSetConstantBuffers(0, 1, &gPerFrameConstantBuffer); // First parameter must match constant buffer number in the shader
gD3DContext->GSSetConstantBuffers(0, 1, &gPerFrameConstantBuffer);
gD3DContext->PSSetConstantBuffers(0, 1, &gPerFrameConstantBuffer);
gD3DContext->PSSetShader(gPixelLightingPixelShader, nullptr, 0);
////--------------- Render ordinary models ---------------///
// Select which shaders to use next
gD3DContext->VSSetShader(gPixelLightingVertexShader, nullptr, 0);
gD3DContext->PSSetShader(gPixelLightingPixelShader, nullptr, 0);
gD3DContext->GSSetShader(nullptr, nullptr, 0); // Switch off geometry shader when not using it (pass nullptr for first parameter)
// States - no blending, normal depth buffer and back-face culling (standard set-up for opaque models)
gD3DContext->OMSetBlendState(gNoBlendingState, nullptr, 0xffffff);
gD3DContext->OMSetDepthStencilState(gUseDepthBufferState, 0);
gD3DContext->RSSetState(gCullBackState);
// Render lit models, only change textures for each onee
gD3DContext->PSSetSamplers(0, 1, &gAnisotropic4xSampler);
gD3DContext->PSSetShaderResources(0, 1, &gGroundDiffuseSpecularMapSRV); // First parameter must match texture slot number in the shader
gGround->Render();
gD3DContext->PSSetShaderResources(0, 1, &gCrateDiffuseSpecularMapSRV); // First parameter must match texture slot number in the shader
gCrate->Render();
gD3DContext->PSSetShaderResources(0, 1, &gCubeDiffuseSpecularMapSRV); // First parameter must match texture slot number in the shader
gCube->Render();
////--------------- Render sky ---------------////
// Select which shaders to use next
gD3DContext->VSSetShader(gBasicTransformVertexShader, nullptr, 0);
gD3DContext->PSSetShader(gTintedTexturePixelShader, nullptr, 0);
// Using a pixel shader that tints the texture - don't need a tint on the sky so set it to white
gPerModelConstants.objectColour = { 1, 1, 1 };
// Stars point inwards
gD3DContext->RSSetState(gCullNoneState);
// Render sky
gD3DContext->PSSetShaderResources(0, 1, &gStarsDiffuseSpecularMapSRV);
gStars->Render();
////--------------- Render lights ---------------////
// Select which shaders to use next (actually same as before, so we could skip this)
gD3DContext->VSSetShader(gBasicTransformVertexShader, nullptr, 0);
gD3DContext->PSSetShader(gTintedTexturePixelShader, nullptr, 0);
// Select the texture and sampler to use in the pixel shader
gD3DContext->PSSetShaderResources(0, 1, &gLightDiffuseMapSRV); // First parameter must match texture slot number in the shaer
// States - additive blending, read-only depth buffer and no culling (standard set-up for blending)
gD3DContext->OMSetBlendState(gAdditiveBlendingState, nullptr, 0xffffff);
gD3DContext->OMSetDepthStencilState(gDepthReadOnlyState, 0);
gD3DContext->RSSetState(gCullNoneState);
// Render all the lights in the array
for (int i = 0; i < NUM_LIGHTS; ++i)
{
gPerModelConstants.objectColour = gLights[i].colour; // Set any per-model constants apart from the world matrix just before calling render (light colour here)
gLights[i].model->Render();
}
}
//**************************
// Select the appropriate shader plus any additional textures required for a given post-process
// Helper function shared by full-screen, area and polygon post-processing functions below
void SelectPostProcessShaderAndTextures(PostProcess postProcess)
{
if (postProcess == PostProcess::Copy)
{
gD3DContext->PSSetShader(gCopyPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::Tint)
{
gD3DContext->PSSetShader(gTintPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::Gradient)
{
gD3DContext->PSSetShader(gGradientPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::Blur)
{
gD3DContext->PSSetShader(gBlurPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::Underwater)
{
gD3DContext->PSSetShader(gUnderwaterPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::Retro)
{
gD3DContext->PSSetShader(gRetroPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::GaussianBlur)
{
gD3DContext->PSSetShader(gGaussianBlurPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::GaussianBlurVertical)
{
gD3DContext->PSSetShader(gGaussianBlurVerticalPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::Bloom)
{
gD3DContext->PSSetShader(gBloomPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::GreyNoise)
{
gD3DContext->PSSetShader(gGreyNoisePostProcess, nullptr, 0);
// Give pixel shader access to the noise texture
gD3DContext->PSSetShaderResources(1, 1, &gNoiseMapSRV);
gD3DContext->PSSetSamplers(1, 1, &gTrilinearSampler);
}
else if (postProcess == PostProcess::Burn)
{
gD3DContext->PSSetShader(gBurnPostProcess, nullptr, 0);
// Give pixel shader access to the burn texture (basically a height map that the burn level ascends)
gD3DContext->PSSetShaderResources(1, 1, &gBurnMapSRV);
gD3DContext->PSSetSamplers(1, 1, &gTrilinearSampler);
}
else if (postProcess == PostProcess::Distort)
{
gD3DContext->PSSetShader(gDistortPostProcess, nullptr, 0);
// Give pixel shader access to the distortion texture (containts 2D vectors (in R & G) to shift the texture UVs to give a cut-glass impression)
gD3DContext->PSSetShaderResources(1, 1, &gDistortMapSRV);
gD3DContext->PSSetSamplers(1, 1, &gTrilinearSampler);
}
else if (postProcess == PostProcess::Spiral)
{
gD3DContext->PSSetShader(gSpiralPostProcess, nullptr, 0);
}
else if (postProcess == PostProcess::HeatHaze)
{
gD3DContext->PSSetShader(gHeatHazePostProcess, nullptr, 0);
}
}
// Perform a full-screen post process from "scene texture" to back buffer
void FullScreenPostProcess(PostProcess postProcess)
{
// Select the back buffer to use for rendering. Not going to clear the back-buffer because we're going to overwrite it all
gD3DContext->OMSetRenderTargets(1, &gBackBufferRenderTarget, gDepthStencil);
// Give the pixel shader (post-processing shader) access to the scene texture
gD3DContext->PSSetShaderResources(0, 1, &gSceneTextureSRV);
gD3DContext->PSSetSamplers(0, 1, &gPointSampler); // Use point sampling (no bilinear, trilinear, mip-mapping etc. for most post-processes)
// Using special vertex shader that creates its own data for a 2D screen quad
gD3DContext->VSSetShader(g2DQuadVertexShader, nullptr, 0);
gD3DContext->GSSetShader(nullptr, nullptr, 0); // Switch off geometry shader when not using it (pass nullptr for first parameter)
// States - no blending, don't write to depth buffer and ignore back-face culling
gD3DContext->OMSetBlendState(gNoBlendingState, nullptr, 0xffffff);
gD3DContext->OMSetDepthStencilState(gDepthReadOnlyState, 0);
gD3DContext->RSSetState(gCullNoneState);
// No need to set vertex/index buffer (see 2D quad vertex shader), just indicate that the quad will be created as a triangle strip
gD3DContext->IASetInputLayout(NULL); // No vertex data
gD3DContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// Select shader and textures needed for the required post-processes (helper function above)
SelectPostProcessShaderAndTextures(postProcess);
// Set 2D area for full-screen post-processing (coordinates in 0->1 range)
gPostProcessingConstants.area2DTopLeft = { 0, 0 }; // Top-left of entire screen
gPostProcessingConstants.area2DSize = { 1, 1 }; // Full size of screen
gPostProcessingConstants.area2DDepth = 0; // Depth buffer value for full screen is as close as possible
// Pass over the above post-processing settings (also the per-process settings prepared in UpdateScene function below)
UpdateConstantBuffer(gPostProcessingConstantBuffer, gPostProcessingConstants);
gD3DContext->VSSetConstantBuffers(1, 1, &gPostProcessingConstantBuffer);
gD3DContext->PSSetConstantBuffers(1, 1, &gPostProcessingConstantBuffer);
// Draw a quad
gD3DContext->Draw(4, 0);
}
// Perform an area post process from "scene texture" to back buffer at a given point in the world, with a given size (world units)
void AreaPostProcess(PostProcess postProcess, CVector3 worldPoint, CVector2 areaSize)
{
// First perform a full-screen copy of the scene to back-buffer
FullScreenPostProcess(PostProcess::Copy);
// Now perform a post-process of a portion of the scene to the back-buffer (overwriting some of the copy above)
// Note: The following code relies on many of the settings that were prepared in the FullScreenPostProcess call above, it only
// updates a few things that need to be changed for an area process. If you tinker with the code structure you need to be
// aware of all the work that the above function did that was also preparation for this post-process area step
// Select shader/textures needed for required post-process
SelectPostProcessShaderAndTextures(postProcess);
// Enable alpha blending - area effects need to fade out at the edges or the hard edge of the area is visible
// A couple of the shaders have been updated to put the effect into a soft circle
// Alpha blending isn't enabled for fullscreen and polygon effects so it doesn't affect those (except heat-haze, which works a bit differently)
gD3DContext->OMSetBlendState(gAlphaBlendingState, nullptr, 0xffffff);
// Use picking methods to find the 2D position of the 3D point at the centre of the area effect
auto worldPointTo2D = gCamera->PixelFromWorldPt(worldPoint, gViewportWidth, gViewportHeight);
CVector2 area2DCentre = { worldPointTo2D.x, worldPointTo2D.y };
float areaDistance = worldPointTo2D.z;
// Nothing to do if given 3D point is behind the camera
if (areaDistance < gCamera->NearClip()) return;
// Convert pixel coordinates to 0->1 coordinates as used by the shader
area2DCentre.x /= gViewportWidth;
area2DCentre.y /= gViewportHeight;
// Using new helper function here - it calculates the world space units covered by a pixel at a certain distance from the camera.
// Use this to find the size of the 2D area we need to cover the world space size requested
CVector2 pixelSizeAtPoint = gCamera->PixelSizeInWorldSpace(areaDistance, gViewportWidth, gViewportHeight);
CVector2 area2DSize = { areaSize.x / pixelSizeAtPoint.x, areaSize.y / pixelSizeAtPoint.y };
// Again convert the result in pixels to a result to 0->1 coordinates
area2DSize.x /= gViewportWidth;
area2DSize.y /= gViewportHeight;
// Send the area top-left and size into the constant buffer - the 2DQuad vertex shader will use this to create a quad in the right place
gPostProcessingConstants.area2DTopLeft = area2DCentre - 0.5f * area2DSize; // Top-left of area is centre - half the size
gPostProcessingConstants.area2DSize = area2DSize;
// Manually calculate depth buffer value from Z distance to the 3D point and camera near/far clip values. Result is 0->1 depth value
// We've never seen this full calculation before, it's occasionally useful. It is derived from the material in the Picking lecture
// Having the depth allows us to have area effects behind normal objects
gPostProcessingConstants.area2DDepth = gCamera->FarClip() * (areaDistance - gCamera->NearClip()) / (gCamera->FarClip() - gCamera->NearClip());
gPostProcessingConstants.area2DDepth /= areaDistance;
// Pass over this post-processing area to shaders (also sends the per-process settings prepared in UpdateScene function below)
UpdateConstantBuffer(gPostProcessingConstantBuffer, gPostProcessingConstants);
gD3DContext->VSSetConstantBuffers(1, 1, &gPostProcessingConstantBuffer);
gD3DContext->PSSetConstantBuffers(1, 1, &gPostProcessingConstantBuffer);
// Draw a quad
gD3DContext->Draw(4, 0);
}
// Perform an post process from "scene texture" to back buffer within the given four-point polygon and a world matrix to position/rotate/scale the polygon
void PolygonPostProcess(PostProcess postProcess, const std::array<CVector3, 4>& points, const CMatrix4x4& worldMatrix)
{
// First perform a full-screen copy of the scene to back-buffer
FullScreenPostProcess(PostProcess::Copy);
// Now perform a post-process of a portion of the scene to the back-buffer (overwriting some of the copy above)
// Note: The following code relies on many of the settings that were prepared in the FullScreenPostProcess call above, it only
// updates a few things that need to be changed for an area process. If you tinker with the code structure you need to be
// aware of all the work that the above function did that was also preparation for this post-process area step
// Select shader/textures needed for required post-process
SelectPostProcessShaderAndTextures(postProcess);
// Loop through the given points, transform each to 2D (this is what the vertex shader normally does in most labs)
for (unsigned int i = 0; i < points.size(); ++i)
{
CVector4 modelPosition = CVector4(points[i], 1);
CVector4 worldPosition = modelPosition * worldMatrix;
CVector4 viewportPosition = worldPosition * gCamera->ViewProjectionMatrix();
gPostProcessingConstants.polygon2DPoints[i] = viewportPosition;
}
// Pass over the polygon points to the shaders (also sends the per-process settings prepared in UpdateScene function below)
UpdateConstantBuffer(gPostProcessingConstantBuffer, gPostProcessingConstants);
gD3DContext->VSSetConstantBuffers(1, 1, &gPostProcessingConstantBuffer);
gD3DContext->PSSetConstantBuffers(1, 1, &gPostProcessingConstantBuffer);
// Select the special 2D polygon post-processing vertex shader and draw the polygon
gD3DContext->VSSetShader(g2DPolygonVertexShader, nullptr, 0);
gD3DContext->Draw(4, 0);
}
//**************************
// Rendering the scene
void RenderScene()
{
//// Common settings ////
// Set up the light information in the constant buffer
// Don't send to the GPU yet, the function RenderSceneFromCamera will do that
gPerFrameConstants.light1Colour = gLights[0].colour * gLights[0].strength;
gPerFrameConstants.light1Position = gLights[0].model->Position();
gPerFrameConstants.light2Colour = gLights[1].colour * gLights[1].strength;
gPerFrameConstants.light2Position = gLights[1].model->Position();
gPerFrameConstants.ambientColour = gAmbientColour;
gPerFrameConstants.specularPower = gSpecularPower;
gPerFrameConstants.cameraPosition = gCamera->Position();
gPerFrameConstants.viewportWidth = static_cast<float>(gViewportWidth);
gPerFrameConstants.viewportHeight = static_cast<float>(gViewportHeight);
////--------------- Main scene rendering ---------------////
// Set the target for rendering and select the main depth buffer.
// If using post-processing then render to the scene texture, otherwise to the usual back buffer
// Also clear the render target to a fixed colour and the depth buffer to the far distance
if (gCurrentPostProcess != PostProcess::None)
{
gD3DContext->OMSetRenderTargets(1, &gSceneRenderTarget, gDepthStencil);
gD3DContext->ClearRenderTargetView(gSceneRenderTarget, &gBackgroundColor.r);
}
else
{
gD3DContext->OMSetRenderTargets(1, &gBackBufferRenderTarget, gDepthStencil);
gD3DContext->ClearRenderTargetView(gBackBufferRenderTarget, &gBackgroundColor.r);
}
gD3DContext->ClearDepthStencilView(gDepthStencil, D3D11_CLEAR_DEPTH, 1.0f, 0);
// Setup the viewport to the size of the main window
D3D11_VIEWPORT vp;
vp.Width = static_cast<FLOAT>(gViewportWidth);
vp.Height = static_cast<FLOAT>(gViewportHeight);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
gD3DContext->RSSetViewports(1, &vp);
// Render the scene from the main camera
RenderSceneFromCamera(gCamera);
////--------------- Scene completion ---------------////
// Run any post-processing steps
if (gCurrentPostProcess != PostProcess::None)
{
if (gCurrentPostProcessMode == PostProcessMode::Fullscreen)
{
FullScreenPostProcess(gCurrentPostProcess);
}
else if (gCurrentPostProcessMode == PostProcessMode::Area)
{
// Pass a 3D point for the centre of the affected area and the size of the (rectangular) area in world units
AreaPostProcess(gCurrentPostProcess, gLights[0].model->Position(), { 10, 10 });
}
else if (gCurrentPostProcessMode == PostProcessMode::Polygon)
{
// An array of four points in world space - a tapered square centred at the origin
const std::array<CVector3, 4> points = {{ {-3,5,0}, {-5,-5,0}, {3,5,0}, {5,-5,0} }}; // C++ strangely needs an extra pair of {} here... only for std:array...
// A rotating matrix placing the model above in the scene
static CMatrix4x4 polyMatrix = MatrixTranslation({ 20, 15, 0 });
polyMatrix = MatrixRotationY(ToRadians(1)) * polyMatrix;
// Pass an array of 4 points and a matrix. Only supports 4 points.
PolygonPostProcess(gCurrentPostProcess, points, polyMatrix);
}
// These lines unbind the scene texture from the pixel shader to stop DirectX issuing a warning when we try to render to it again next frame
ID3D11ShaderResourceView* nullSRV = nullptr;
gD3DContext->PSSetShaderResources(0, 1, &nullSRV);
}
// When drawing to the off-screen back buffer is complete, we "present" the image to the front buffer (the screen)
// Set first parameter to 1 to lock to vsync
gSwapChain->Present(lockFPS ? 1 : 0, 0);
}
//--------------------------------------------------------------------------------------
// Scene Update
//--------------------------------------------------------------------------------------
// Update models and camera. frameTime is the time passed since the last frame
void UpdateScene(float frameTime)
{
//***********
// Select post process on keys
if (KeyHit(Key_F1)) gCurrentPostProcessMode = PostProcessMode::Fullscreen;
if (KeyHit(Key_F2)) gCurrentPostProcessMode = PostProcessMode::Area;
if (KeyHit(Key_F3)) gCurrentPostProcessMode = PostProcessMode::Polygon;
// Controls
if (KeyHit(Key_1)) gCurrentPostProcess = PostProcess::Gradient; // 1 - Gradient
if (KeyHit(Key_2)) gCurrentPostProcess = PostProcess::Blur; // 2 - Blur
if (KeyHit(Key_3)) gCurrentPostProcess = PostProcess::Underwater; // 3 - Underwater
if (KeyHit(Key_4)) gCurrentPostProcess = PostProcess::Retro; // 4 - Retro
if (KeyHit(Key_5)) // 5 - Gaussian Blur
{
gCurrentPostProcess = PostProcess::GaussianBlur;
gCurrentPostProcess = PostProcess::GaussianBlurVertical;
}
if (KeyHit(Key_6)) gCurrentPostProcess = PostProcess::Bloom; // 6 - Bloom
if (KeyHit(Key_C) || KeyHit(Key_0)) gCurrentPostProcess = PostProcess::None; // C or 0 - Clear Effects
/*
if (KeyHit(Key_4)) gCurrentPostProcess = PostProcess::Tint;
if (KeyHit(Key_5)) gCurrentPostProcess = PostProcess::GreyNoise;
if (KeyHit(Key_6)) gCurrentPostProcess = PostProcess::Burn;
if (KeyHit(Key_7)) gCurrentPostProcess = PostProcess::Distort;
if (KeyHit(Key_8)) gCurrentPostProcess = PostProcess::Spiral;
if (KeyHit(Key_9)) gCurrentPostProcess = PostProcess::HeatHaze;
if (KeyHit(Key_9)) gCurrentPostProcess = PostProcess::Copy;
*/
// Post processing settings - all data for post-processes is updated every frame whether in use or not (minimal cost)
// Colour for tint shader
gPostProcessingConstants.tintColour = { 1, 0, 0 };
// Noise scaling adjusts how fine the grey noise is.
const float grainSize = 140; // Fineness of the noise grain
gPostProcessingConstants.noiseScale = { gViewportWidth / grainSize, gViewportHeight / grainSize };
// The noise offset is randomised to give a constantly changing noise effect (like tv static)
gPostProcessingConstants.noiseOffset = { Random(0.0f, 1.0f), Random(0.0f, 1.0f) };
// Set and increase the burn level (cycling back to 0 when it reaches 1.0f)
const float burnSpeed = 0.2f;
gPostProcessingConstants.burnHeight = fmod(gPostProcessingConstants.burnHeight + burnSpeed * frameTime, 1.0f);
// Set the level of distortion
gPostProcessingConstants.distortLevel = 0.03f;
// Set and increase the amount of spiral - use a tweaked cos wave to animate
static float wiggle = 0.0f;
const float wiggleSpeed = 1.0f;
gPostProcessingConstants.spiralLevel = ((1.0f - cos(wiggle)) * 4.0f );
wiggle += wiggleSpeed * frameTime;
// Update heat haze timer
gPostProcessingConstants.heatHazeTimer += frameTime;
//***********
// Orbit one light - a bit of a cheat with the static variable [ask the tutor if you want to know what this is]
static float lightRotate = 0.0f;
static bool go = true;
gLights[0].model->SetPosition({ 20 + cos(lightRotate) * gLightOrbitRadius, 10, 20 + sin(lightRotate) * gLightOrbitRadius });
if (go) lightRotate -= gLightOrbitSpeed * frameTime;
if (KeyHit(Key_L)) go = !go;
// Control of camera
gCamera->Control(frameTime, Key_Up, Key_Down, Key_Left, Key_Right, Key_W, Key_S, Key_A, Key_D);
// Toggle FPS limiting
if (KeyHit(Key_P)) lockFPS = !lockFPS;
// Show frame time / FPS in the window title //
const float fpsUpdateTime = 0.5f; // How long between updates (in seconds)
static float totalFrameTime = 0;
static int frameCount = 0;
totalFrameTime += frameTime;
++frameCount;
if (totalFrameTime > fpsUpdateTime)
{
// Displays FPS rounded to nearest int, and frame time (more useful for developers) in milliseconds to 2 decimal places
float avgFrameTime = totalFrameTime / frameCount;
std::ostringstream frameTimeMs;
frameTimeMs.precision(2);
frameTimeMs << std::fixed << avgFrameTime * 1000;
std::string windowTitle = "CO3303 Week 14: Area Post Processing - Frame Time: " + frameTimeMs.str() +
"ms, FPS: " + std::to_string(static_cast<int>(1 / avgFrameTime + 0.5f));
SetWindowTextA(gHWnd, windowTitle.c_str());
totalFrameTime = 0;
frameCount = 0;
}
}