ue4 4.21 shader notes(1)

写在前面
经过大概三天的折磨,我希望从一个简单地切入点学习渲染的想法泡汤了。没有银弹,还是老老实实看ue4源码好了。

正文
4.21/Engine/Shaders/Public/FP16Math.ush

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
/** 定义使用16位浮点half,32位浮点float,还是32位浮点的低16位min16float */

/*=============================================================================
FP16Math.usf: Defines for using FP16 or FP32
=============================================================================*/
#pragma once

#ifndef EXPERIMENTAL_FP16 // 默认开启16位 有两种选择:half 和 min16float
#define EXPERIMENTAL_FP16 1
#endif

#ifndef USE_MIN16FLOAT // 默认不使用 min16float
#define USE_MIN16FLOAT 0
#endif

#if EXPERIMENTAL_FP16
#if USE_MIN16FLOAT
#define EXP_FLT min16float // 开启USE_MIN16FLOAT宏,则使用 min16float
#define EXP_FLT2 min16float2
#define EXP_FLT3 min16float3
#define EXP_FLT4 min16float4
#define EXP_FLT3x3 min16float3x3
#define EXP_FLT4x4 min16float4x4
#define EXP_FLT4x3 min16float4x3
#else
#define EXP_FLT half // 否则使用 half
#define EXP_FLT2 half2
#define EXP_FLT3 half3
#define EXP_FLT4 half4
#define EXP_FLT3x3 half3x3
#define EXP_FLT4x4 half4x4
#define EXP_FLT4x3 half4x3
#endif
#else
#define EXP_FLT float // 关闭EXPERIMENTAL_FP16宏,则使用 float
#define EXP_FLT2 float2
#define EXP_FLT3 float3
#define EXP_FLT4 float4
#define EXP_FLT3x3 float3x3
#define EXP_FLT4x4 float4x4
#define EXP_FLT4x3 float4x3
#endif

4.21/Engine/Shaders/Public/Platform.ush

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
/** 开启平台相关的宏,以及加入少量辅助方法 */

/*=============================================================================
Prefix.usf: USF file automatically included by shader preprocessor.
=============================================================================*/

#pragma once

#include "FP16Math.ush"

// ---------------------------------------------------- Profile or compiler specific includes
// TODO: Have shader compiler including these platform specific USF files, that needs to work
// with ShaderCore.cpp's GetShaderIncludes().
// 引入平台对应的头文件
#if PS4_PROFILE // ps4 平台
// always #include PS4Common.usf so it can #define override anything in any other included file.
#include "Platform/PS4/PS4Common.ush"
#endif

#if XBOXONE_PROFILE // xbox1 平台
#include "Platform/XboxOne/XboxOneCommon.ush"
#endif

#if SWITCH_PROFILE || SWITCH_PROFILE_FORWARD // switch 平台
#include "Platform/Switch/SwitchCommon.ush"
#endif

#if COMPILER_METAL // metal 底层
// Helps with iteration when changing Metal shader code generation backend.
#include "Platform/Metal/MetalCommon.ush"
#endif

#if COMPILER_VULKAN // vulkan 底层
// Helps with iteration when changing Vulkan shader code generation backend.
#include "Platform/Vulkan/VulkanCommon.ush"
#endif


// ---------------------------------------------------- DDC invalidation
// to support the console command "r.InvalidateShaderCache"
#include "ShaderVersion.ush"


// ---------------------------------------------------- COMPILE_* and *_PROFILE defaults
// 定义shader语言宏
#ifndef COMPILER_HLSLCC // hlslcc 语言
#define COMPILER_HLSLCC 0
#endif

#ifndef COMPILER_HLSL // hlsl 语言
#define COMPILER_HLSL 0
#endif

#ifndef COMPILER_GLSL // glsl 语言
#define COMPILER_GLSL 0
#endif

#ifndef COMPILER_GLSL_ES2 //glsl es2 语言
#define COMPILER_GLSL_ES2 0
#endif

#ifndef COMPILER_GLSL_ES3_1 //glsl es3.1语言
#define COMPILER_GLSL_ES3_1 0
#endif

#ifndef COMPILER_GLSL_ES3_1_EXT //glsl es3.1 ext 语言
#define COMPILER_GLSL_ES3_1_EXT 0
#endif

#ifndef COMPILER_METAL //metel 语言
#define COMPILER_METAL 0
#endif
// 定义语言特性宏
#ifndef COMPILER_SUPPORTS_ATTRIBUTES
#define COMPILER_SUPPORTS_ATTRIBUTES 0
#endif

#ifndef PLATFORM_SUPPORTS_SRV_UB
#define PLATFORM_SUPPORTS_SRV_UB 0
#endif

#ifndef SM5_PROFILE
#define SM5_PROFILE 0
#endif

#ifndef SM4_PROFILE
#define SM4_PROFILE 0
#endif

#ifndef OPENGL_PROFILE
#define OPENGL_PROFILE 0
#endif

#ifndef ES2_PROFILE
#define ES2_PROFILE 0
#endif

#ifndef ES3_1_PROFILE
#define ES3_1_PROFILE 0
#endif

#ifndef METAL_PROFILE
#define METAL_PROFILE 0
#endif

#ifndef METAL_MRT_PROFILE
#define METAL_MRT_PROFILE 0
#endif

#ifndef METAL_SM5_NOTESS_PROFILE
#define METAL_SM5_NOTESS_PROFILE 0
#endif

#ifndef METAL_SM5_PROFILE
#define METAL_SM5_PROFILE 0
#endif

#ifndef COMPILER_VULKAN
#define COMPILER_VULKAN 0
#endif

#ifndef VULKAN_PROFILE
#define VULKAN_PROFILE 0
#endif

#ifndef VULKAN_PROFILE_SM4
#define VULKAN_PROFILE_SM4 0
#endif

#ifndef VULKAN_PROFILE_SM5
#define VULKAN_PROFILE_SM5 0
#endif

#ifndef IOS
#define IOS 0
#endif

#ifndef MAC
#define MAC 0
#endif
// 依据上面定义的宏,开启对应平台特性
// 'static' asserts
#if COMPILER_GLSL || COMPILER_GLSL_ES2 || COMPILER_GLSL_ES3_1 || COMPILER_VULKAN || COMPILER_METAL
#if !COMPILER_HLSLCC // 以上平台需要编译 hlslcc
#error "Missing COMPILER_HLSLCC define!"
#endif
#endif


#if PLATFORM_SUPPORTS_SRV_UB
#define PLATFORM_SUPPORTS_SRV_UB_MACRO(...) __VA_ARGS__ // __VA_ARGS__表示可变参数
#else
#define PLATFORM_SUPPORTS_SRV_UB_MACRO(...)
#endif

// ---------------------------------------------------- Alternative floating point types

#ifndef FORCE_FLOATS
#define FORCE_FLOATS 0
#endif
// 只对es2编译器开启 float
#if (!(COMPILER_GLSL_ES2 || COMPILER_GLSL_ES3_1 || METAL_PROFILE) || FORCE_FLOATS)
// Always use floats when not using the ES2 compiler, because low precision modifiers are currently only tweaked for ES2,
// And we don't want potential side effects on other platforms
#define half float
#define half1 float1
#define half2 float2
#define half3 float3
#define half4 float4
#define half3x3 float3x3
#define half4x4 float4x4
#define half4x3 float4x3
#define fixed float
#define fixed1 float1
#define fixed2 float2
#define fixed3 float3
#define fixed4 float4
#define fixed3x3 float3x3
#define fixed4x4 float4x4
#define fixed4x3 float4x3
#endif

// ---------------------------------------------------- Profile config
// 定义shader model 等级
// Values of FEATURE_LEVEL.
#define FEATURE_LEVEL_ES2 1
#define FEATURE_LEVEL_ES3_1 2
#define FEATURE_LEVEL_SM3 3
#define FEATURE_LEVEL_SM4 4
#define FEATURE_LEVEL_SM5 5
#define FEATURE_LEVEL_MAX 6

#if PS4_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM5

#elif SM5_PROFILE
// SM5 = full dx11 features (high end UE4 rendering)
#define FEATURE_LEVEL FEATURE_LEVEL_SM5

#elif SM4_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM4

#elif SWITCH_PROFILE || SWITCH_PROFILE_FORWARD
#undef ES3_1_PROFILE

#if SWITCH_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM5
#else
#define FEATURE_LEVEL FEATURE_LEVEL_ES3_1
// @todo switch: maybe all uses of this should check feature level not profile?
#define ES3_1_PROFILE 1
#endif

#elif VULKAN_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_ES3_1

#elif VULKAN_PROFILE_SM4
#define FEATURE_LEVEL FEATURE_LEVEL_SM4

#elif VULKAN_PROFILE_SM5
#define FEATURE_LEVEL FEATURE_LEVEL_SM5
#define STENCIL_COMPONENT_SWIZZLE .x // 这里定义stencil 返回x分量
// swizzle的用法举个例子:v.xxyz


#elif GL3_PROFILE || GL4_PROFILE
#if GL3_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM4
#elif GL4_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM5
#endif

// A8 textures when sampled have their component in R.
#define A8_SAMPLE_MASK .r // 采样8bit a分量时返回的时r分量

// hacks until the shader compiler supports those
#if GL4_PROFILE
#define class struct // hack class 为 struct
#endif

#elif METAL_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_ES3_1
// @todo metal: remove this and make sure all uses handle METAL_PROFILE
#undef ES3_1_PROFILE
#define ES3_1_PROFILE 1
#define FCOLOR_COMPONENT_SWIZZLE .bgra
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#define STENCIL_COMPONENT_SWIZZLE .x

#elif METAL_ES2_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_ES2
// @todo metal: remove this and make sure all uses handle METAL_ES2_PROFILE
#undef ES2_PROFILE
#define ES2_PROFILE 1
#define FCOLOR_COMPONENT_SWIZZLE .bgra
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#define STENCIL_COMPONENT_SWIZZLE .x

#elif METAL_MRT_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM5
#define FCOLOR_COMPONENT_SWIZZLE .bgra
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#define STENCIL_COMPONENT_SWIZZLE .x

#elif METAL_SM5_NOTESS_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM5
#define FCOLOR_COMPONENT_SWIZZLE .bgra
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#define STENCIL_COMPONENT_SWIZZLE .x

#elif METAL_SM5_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_SM5
#define FCOLOR_COMPONENT_SWIZZLE .bgra
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#define STENCIL_COMPONENT_SWIZZLE .x

#elif ES2_PROFILE || ES3_1_PROFILE
#if ES3_1_PROFILE
#define FEATURE_LEVEL FEATURE_LEVEL_ES3_1
#else
//@todo ES3_1 GL
#define FEATURE_LEVEL FEATURE_LEVEL_ES2
#endif

#if COMPILER_GLSL_ES2 || COMPILER_GLSL_ES3_1
// Swizzle as we only support GL_BGRA on non-ES2 platforms that have that extension
#define FCOLOR_COMPONENT_SWIZZLE .bgra
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#else
#define FCOLOR_COMPONENT_SWIZZLE .rgba
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#if COMPILER_GLSL
// A8 textures when sampled have their component in R
#define A8_SAMPLE_MASK .r
#endif
#endif

#else

#error Add your platform here

#define FEATURE_LEVEL FEATURE_LEVEL_MAX

#endif


// ---------------------------------------------------- Swizzle defaults

// If we didn't request color component swizzling, just make it empty
#ifndef FCOLOR_COMPONENT_SWIZZLE
#define FCOLOR_COMPONENT_SWIZZLE .rgba
#endif

#ifndef FMANUALFETCH_COLOR_COMPONENT_SWIZZLE
#define FMANUALFETCH_COLOR_COMPONENT_SWIZZLE .bgra
#endif

#ifndef STENCIL_COMPONENT_SWIZZLE
#define STENCIL_COMPONENT_SWIZZLE .g
#endif

#ifndef A8_SAMPLE_MASK
#define A8_SAMPLE_MASK .a
#endif

// ---------------------------------------------------- Platform dependent supports

// non-editor platforms generally never want development/editor features.
#ifndef PLATFORM_SUPPORTS_DEVELOPMENT_SHADERS
#define PLATFORM_SUPPORTS_DEVELOPMENT_SHADERS !ESDEFERRED_PROFILE
#endif

#ifndef MOBILE_EMULATION
#define MOBILE_EMULATION ((FEATURE_LEVEL == FEATURE_LEVEL_ES2 || FEATURE_LEVEL == FEATURE_LEVEL_ES3_1) && (!(COMPILER_GLSL_ES3_1 || COMPILER_GLSL_ES2) && USE_DEVELOPMENT_SHADERS && (!(METAL_PROFILE) || MAC) && !VULKAN_PROFILE))
#endif

// Whether the platform supports independent texture and samplers
// When enabled, different texture lookups can share samplers to allow more artist samplers in the base pass
// Ideally this would just be enabled for all SM4 and above feature level platforms
// @todo metal mrt: No reason this can't work with Metal, once cross compiler is fixed
#ifndef SUPPORTS_INDEPENDENT_SAMPLERS
#define SUPPORTS_INDEPENDENT_SAMPLERS (PS4_PROFILE || SM5_PROFILE || SM4_PROFILE || METAL_MRT_PROFILE || METAL_SM5_NOTESS_PROFILE || METAL_SM5_PROFILE || VULKAN_PROFILE_SM5 || VULKAN_PROFILE_SM4 || VULKAN_PROFILE)
#endif

// Whether the platform supports a global clip plane through SV_ClipDistance
// Ideally this would just be enabled for all SM4 and above feature level platforms, but not tested everywhere yet
#define PLATFORM_SUPPORTS_GLOBAL_CLIP_PLANE (PS4_PROFILE || SM5_PROFILE || SM4_PROFILE || METAL_PROFILE || METAL_MRT_PROFILE || METAL_SM5_NOTESS_PROFILE || METAL_SM5_PROFILE || GL4_PROFILE || GL3_PROFILE || VULKAN_PROFILE_SM4 || VULKAN_PROFILE_SM5)

// Whether the platform support pixel coverage on MSAA targets (SV_Coverage).
#define SUPPORTS_PIXEL_COVERAGE (FEATURE_LEVEL >= FEATURE_LEVEL_SM5 && !COMPILER_GLSL && !MOBILE_EMULATION)

// Must match C++ RHISupports4ComponentUAVReadWrite
// D3D11 does not support multi-component loads from a UAV: "error X3676: typed UAV loads are only allowed for single-component 32-bit element types"
#define PLATFORM_SUPPORTS_4COMPONENT_UAV_READ_WRITE (PS4_PROFILE || XBOXONE_PROFILE || COMPILER_METAL)


// ---------------------------------------------------- Compiler specific defaults and fallbacks

/** Defined only for Metal's combined Vertex + Hull shader */
#ifndef TESSELLATIONSHADER
#define TESSELLATIONSHADER 0
#endif

// Hlslcc platforms ignore the uniform keyword as it can't properly optimize flow
#if COMPILER_HLSLCC
#define uniform
#endif

// If compiler lane management in a wave.
// WaveGetLaneCount()
// WaveGetLaneIndex()
// if (WaveOnce()) { ... }
#ifndef COMPILER_SUPPORTS_WAVE_ONCE
#define COMPILER_SUPPORTS_WAVE_ONCE 0
#endif

// Whether the compiler exposes voting on all lanes:
// WaveAnyTrue(MyBool)
// WaveAnyTrue(MyBool)
// WaveAllEqual(MyBool)
#ifndef COMPILER_SUPPORTS_WAVE_VOTE
#define COMPILER_SUPPORTS_WAVE_VOTE 0
#endif

// Whether the compiler exposes min max instructions across all lane of the wave.
// WaveAllMin(MyFloat)
// WaveAllMin(MyInt)
// WaveAllMin(MyUint)
// WaveAllMax(MyFloat)
// WaveAllMax(MyInt)
// WaveAllMax(MyUint)
#ifndef COMPILER_SUPPORTS_WAVE_MINMAX
#define COMPILER_SUPPORTS_WAVE_MINMAX 0
#endif

// Whether the compiler exposes OR and AND bit operation all lanes:
// WaveAllBitAnd(MyMask)
// WaveAllBitOr(MyMask)
#ifndef COMPILER_SUPPORTS_WAVE_BIT_ORAND
#define COMPILER_SUPPORTS_WAVE_BIT_ORAND 0
#endif

// Whether the compiler exposes GCN's ds_swizzle_b32 instruction.
// float WaveLaneSwizzleGCN(float x, const uint and_mask, const uint or_mask, const uint xor_mask)
#ifndef COMPILER_SUPPORTS_WAVE_SWIZZLE_GCN
#define COMPILER_SUPPORTS_WAVE_SWIZZLE_GCN 0
#endif

// Mirrors GRHISupportsRectTopology.
#ifndef PLATFORM_SUPPORTS_RECT_LIST
#define PLATFORM_SUPPORTS_RECT_LIST 0
#endif


// ---------------------------------------------------- Compiler attributes

#if SM5_PROFILE || COMPILER_SUPPORTS_ATTRIBUTES

/** Avoids flow control constructs. */
#define UNROLL [unroll]

/** Gives preference to flow control constructs. */
#define LOOP [loop]

/** Performs branching by using control flow instructions like jmp and label. */
#define BRANCH [branch]

/** Performs branching by using the cnd instructions. */
#define FLATTEN [flatten]

/** Allows a compute shader loop termination condition to be based off of a UAV read. The loop must not contain synchronization intrinsics. */
#define ALLOW_UAV_CONDITION [allow_uav_condition]

#endif // SM5_PROFILE || COMPILER_SUPPORTS_ATTRIBUTES

#if SM5_PROFILE || METAL_MRT_PROFILE || METAL_SM5_PROFILE || METAL_SM5_NOTESS_PROFILE
#define EARLYDEPTHSTENCIL [earlydepthstencil]
#endif


// ---------------------------------------------------- Compiler attribute fallbacks
// 对于不支持的特性,在这里抹去宏
#ifndef UNROLL
#define UNROLL
#endif

#ifndef LOOP
#define LOOP
#endif

#ifndef BRANCH
#define BRANCH
#endif

#ifndef FLATTEN
#define FLATTEN
#endif

#ifndef ALLOW_UAV_CONDITION
#define ALLOW_UAV_CONDITION
#endif

#ifndef INVARIANT
#define INVARIANT
#endif

#ifndef ENABLE_RE_Z
#define ENABLE_RE_Z
#endif

#ifndef EARLYDEPTHSTENCIL
#define EARLYDEPTHSTENCIL
#endif

#ifndef STRONG_TYPE
#define STRONG_TYPE
#endif

#ifndef StrongTypedBuffer
#define StrongTypedBuffer Buffer
#endif

// ---------------------------------------------------- Interpolator attribute fallbacks

#ifndef COMPRESSED_16_FLOAT
#define COMPRESSED_16_FLOAT
#endif

#ifndef COMPRESSED_16_UNORM
#define COMPRESSED_16_UNORM
#endif

#ifndef COMPRESSED_16_SNORM
#define COMPRESSED_16_SNORM
#endif

#ifndef COMPRESSED_16_UINT
#define COMPRESSED_16_UINT
#endif

#ifndef COMPRESSED_16_INT
#define COMPRESSED_16_INT
#endif

#ifndef COMPRESSED_8_UNORM
#define COMPRESSED_8_UNORM
#endif

#ifndef COMPRESSED_8_SNORM
#define COMPRESSED_8_SNORM
#endif

#ifndef COMPRESSED_8_UINT
#define COMPRESSED_8_UINT
#endif


// ---------------------------------------------------- Global uses

#define USE_DEVELOPMENT_SHADERS (COMPILE_SHADERS_FOR_DEVELOPMENT && PLATFORM_SUPPORTS_DEVELOPMENT_SHADERS)


// ---------------------------------------------------- Standard sizes of the indirect parameter structs

// sizeof(FRHIDispatchIndirectParameters) / sizeof(uint)
#define DISPATCH_INDIRECT_UINT_COUNT 3

// sizeof(FRHIDrawIndirectParameters) / sizeof(uint)
#define DRAW_INDIRECT_UINT_COUNT 4

// sizeof(FRHIDrawIndexedIndirectParameters) / sizeof(uint)
#define DRAW_INDEXED_INDIRECT_UINT_COUNT 5


// ---------------------------------------------------- Compiler missing implementations
// 在特定平台补充辅助方法,求行列式
#if COMPILER_GLSL_ES2 || (COMPILER_METAL && MAX_SHADER_LANGUAGE_VERSION < 2) || COMPILER_SWITCH

float determinant(float3x3 M)
{
return
M[0][0] * (M[1][1] * M[2][2] - M[1][2] * M[2][1]) -
M[1][0] * (M[0][1] * M[2][2] - M[0][2] * M[2][1]) +
M[2][0] * (M[0][1] * M[1][2] - M[0][2] * M[1][1]);
}

#endif

#if COMPILER_HLSLCC // 补充 求log10(x)
#define log10(x) log((x)) / log(10.0)
#endif


#if !COMPILER_SUPPORTS_MINMAX3 //补充 求min max 方法

float min3( float a, float b, float c )
{
return min( a, min( b, c ) );
}

float max3( float a, float b, float c )
{
return max( a, max( b, c ) );
}

float2 min3( float2 a, float2 b, float2 c )
{
return float2(
min3( a.x, b.x, c.x ),
min3( a.y, b.y, c.y )
);
}

float2 max3( float2 a, float2 b, float2 c )
{
return float2(
max3( a.x, b.x, c.x ),
max3( a.y, b.y, c.y )
);
}

float3 max3( float3 a, float3 b, float3 c )
{
return float3(
max3( a.x, b.x, c.x ),
max3( a.y, b.y, c.y ),
max3( a.z, b.z, c.z )
);
}

float3 min3( float3 a, float3 b, float3 c )
{
return float3(
min3( a.x, b.x, c.x ),
min3( a.y, b.y, c.y ),
min3( a.z, b.z, c.z )
);
}

float4 min3( float4 a, float4 b, float4 c )
{
return float4(
min3( a.x, b.x, c.x ),
min3( a.y, b.y, c.y ),
min3( a.z, b.z, c.z ),
min3( a.w, b.w, c.w )
);
}

float4 max3( float4 a, float4 b, float4 c )
{
return float4(
max3( a.x, b.x, c.x ),
max3( a.y, b.y, c.y ),
max3( a.z, b.z, c.z ),
max3( a.w, b.w, c.w )
);
}

#endif


#if COMPILER_HLSLCC //抹平求偏差方法
#define ddx_fine(x) ddx(x)
#define ddy_fine(y) ddy(y)
#endif

// Give hint to compiler to move one value to scalar unit.
#if !defined(ToScalarMemory) && !defined(COMPILER_SUPPORTS_TO_SCALAR_MEMORY)
#define ToScalarMemory(x) (x)
#endif

#if FEATURE_LEVEL < FEATURE_LEVEL_ES3_1 && !COMPILER_METAL
// DX11 (feature levels >= 10) feature sets natively supports uints in shaders; we just use floats on other platforms.
#define uint4 int4 //抹平uint4支持
#endif

#if COMPILER_HLSLCC || PS4_PROFILE
#define SNORM
#define UNORM
#else
#define SNORM snorm
#define UNORM unorm
#endif

4.21/Engine/Shaders/Public/ShaderVersion.ush

1
2
3
4
5
6
/** shader 版本号,用于版本校验 */
// This file is automatically generated by the console command r.InvalidateCachedShaders
// Each time the console command is executed it generates a new GUID. As this file is included
// in Platform.ush (which should be included in any shader) it allows to invalidate the shader DDC.
//
// GUID = F7C9D61CA78A4854B1D6EE5D3F987298

4.21/Engine/Shaders/Public/WaveBroadcastIntrinsics.ush
Wave-Intrinsics

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
/** 不太清楚,msdn上说可以提升多线程中的性能。*/
/*=============================================================================
WaveBroadcastIntrinsics.ush: Exposes intrisics to perform broadcasting
within lanes of a same wave.
=============================================================================*/

#pragma once

#include "Platform.ush"


#define PLATFORM_SUPPORTS_WAVE_BROADCAST (COMPILER_SUPPORTS_WAVE_SWIZZLE_GCN)

#if PLATFORM_SUPPORTS_WAVE_BROADCAST

/** Swap left lane with righ lanes within lane group (size is power of two in [2; 64]).
*
* If a lane is not active, the VGPR value returned is 0.
*
* LaneGroupSize = 8
* LaneId = 1
*
* | lane group (size=8) |
* x = | 0 1 2 3| 4 5 6 7| 8 9 ...
*
* return | 4 5 6 7| 0 1 2 3|12 13 ...
*/
float WaveSwapWithinLaneGroup(float x, const uint LaneGroupSize)
{
const uint and_mask = 0x1F;
const uint or_mask = 0x00;
const uint xor_mask = LaneGroupSize >> 1;
return WaveLaneSwizzleGCN(x, and_mask, or_mask, xor_mask);
}

/** Broadcast inner lane group over a lane group (size is power of two in [2; 64]).
*
* If a lane is not active, the VGPR value returned is 0.
*
* LaneGroupSize = 8
* InnerLaneGroupSize = 2
* InnerLaneGroupId = 1
*
* | lane group (size=8) |
* x = | 0 1 2 3 4 5 6 7| 8 9 ...
*
* return | 2 3 2 3 2 3 2 3|10 11 ...
*/
float WaveBroadcastLaneGroup(float x, const uint LaneGroupSize, const uint InnerLaneGroupSize, const uint InnerLaneGroupId)
{
const uint InnerGroupCount = LaneGroupSize / InnerLaneGroupSize;

const uint and_mask = ~((InnerGroupCount - 1) * InnerLaneGroupSize);
const uint or_mask = InnerLaneGroupId * InnerLaneGroupSize;
const uint xor_mask = 0x00;
return WaveLaneSwizzleGCN(x, and_mask, or_mask, xor_mask);
}


#define __WaveBroadcastOverrideType(Type) \
Type WaveSwapWithinLaneGroup(Type x, const uint LaneGroupSize) \
{ \
return as##Type(WaveSwapWithinLaneGroup(asfloat(x), LaneGroupSize)); \
} \
Type WaveBroadcastLaneGroup(Type x, const uint LaneGroupSize, const uint InnerLaneGroupSize, const uint InnerLaneGroupId) \
{ \
return as##Type(WaveBroadcastLaneGroup(asfloat(x), LaneGroupSize, InnerLaneGroupSize, InnerLaneGroupId)); \
} \


__WaveBroadcastOverrideType(int)
__WaveBroadcastOverrideType(uint)


#undef __WaveBroadcastOverrideType

#endif // PLATFORM_SUPPORTS_WAVE_BROADCAST

1.2 photorealistic rendering and the ray-tracing algorithm

写在前面
我一直在为gpu gem系列的书找一个学习渲染的平台,据说今天是creator 3d 2.10 正式版发布的日子,等发布了可以试一下。

正文

1.2 照片级渲染以及光线追踪算法

照片级渲染的目标就是:渲染一张让人无法区分真假的图片。在我们介绍渲染的过程之前,我们要明白,对于现实世界的观察是因人而异的,对于真实的感受也同样因人而异。所以尽管这本书试图去涵盖一部分人类感官上的内容。但是给予人们精确的真实感渲染仍旧存在大量科学问题亟待解决。只能说,我们尽力而为。
几乎所有的照片级渲染系统背后的算法都是光线追踪,光线追踪的思路很简单:就是跟踪进入相机(眼球)的光线,观察光线在场景中反弹的过程中,光自身属性上的变化。尽管有多种不同方式来实现光线追踪器,它们还是具备一些共通的点:

  • 相机:相机决定了场景如何被观察以及从哪里被观察。许多渲染系统从相机开始生成追踪光线,射向场景。
  • 光线与场景求交点:我们需要知道发射的光线打在场景中哪个物体身上了。然后获取这个交点一些基本信息,比如表面的法线或材质。当我们求交的时候,发现光线穿过了多个物体时,通常我们取最近的交点。
  • 光源: 没有光源我们就啥都看不到了,一个光线追踪器肯定要知道光能在场景中的分布,那首先我们需要知道光源的位置,以及光源表面光能的分布。
  • 可见性: 我们需要知道场景中某个点,是否被光源照射到了,很简单,看一下这个点和光源的连线之间有没遮挡物就好了。
  • 表面散射: 场景中的物体需要告诉光线,当光线打到自己身上时,光线会发生什么变化:光线可能会发生方向的变化(反射,折射,弹向多个方向),还可能发生能量的变化(变暗淡,变色,弹向不同方向的能量变化不一)
  • 间接光照: 光线打到场景的物体上会发生反弹,反弹的光线可以继续打向场景中其他物体,这时我们需要进一步跟踪,来获取更详细的光信息。
  • 光线传播: 光在真空中传播时保持能量守恒,但是讲道理地球上真空环境是不常见的,我们还需要模拟光线通过烟,雾,地球大气等的表现。
1.2.1 相机

相机对我们来说,不陌生:我们按下快门,然后图像就被记录到胶卷或者SD卡中了。让我们从一个简单的相机说起:针孔摄像机。针孔摄像机由一个开了小孔的小盒子组成(图 1.1)。
光线通过小孔,然后打到针孔摄像机内部的胶卷上,尽管针孔摄像机很简陋,但还是有人在使用它,用于拍摄具有艺术观赏性的照片吧。当然,以这种方式拍照,需要让相机放置足够长的时间,让光线进入小孔,以让足够多的光能驻留在胶卷上(曝光?)。
尽管现代的相机要比针孔摄像机复杂的多了,但对我们来说,一切从简是一个很好的开始。
对于相机来说,很重要的一个点是,我们要决定拍下场景中的哪一部分。在图1.1中,我们看到,连接了胶卷和针孔的连线,形成了两个金字塔形状,外侧的金字塔不断延申向场景中。
场景中的物体,不在外侧的金字塔内的,自然就不会被拍进胶卷内了。现代的相机可以拍摄到的角度肯定比金字塔形状要复杂的多了,所以我们换个名词,我们把相机看到的角度范围称为视野(viewing volume)

1.0 introduction

正文

01 介绍

渲染是这样的一个过程:它将一个3D场景描述成一张图片。显然,这像是在泛泛而谈,因为我们有很多种方式来实现渲染过程,并且每种方式的侧重点都不同。对于基于物理的方式,我们力求去表达场景的真实性——利用物理学中光学的知识。听起来使用基于物理的方式来渲染是一个直观的方式,但是这种渲染方式也仅在近10年才被广泛使用。在本章节最后的1.7小节,我们将简明地聊一下基于物理渲染的历史,以及近年来离线渲染在电影,游戏行业的发展。

这本书的书名叫 pbrt——基于物理的渲染系统(physical based rendering system),而这个系统背后的渲染算法是一个名为光线追踪的算法。让我们纵观一下其他讲述计算机图形学的书籍,它们大多数在行文时更侧重于算法思想和图形学理论,有时在关键的地方加入一小段代码辅助理解。读者在面对这些书学习时,通常会感到不踏实,无从下手。为了打破这种现状,我们这本书除了涵盖理论层面的讲解,还随书附带了一个功能完整的渲染系统实现!这个系统的源码(以及范例场景,材质等),都可以在官方网站 pbrt.org 上找到。

1.1 面向思路的编程

当 Donald Knuth 在开发TEX 排版系统时,发明了一种新的编程方法论,这种方法论极其简单而具有革命性——它认为程序的表达应该更遵照人类阅读的思路去表达,而不仅仅遵照计算机所理解的方式。Donald Knuth 称这种方法论为面向思路的编程(literate programming)。其实这本书(包括读者正在阅读的当前章节)涵盖了一个冗长的面向思路的程序(literate program)。这意味着,读者在阅读这本书的过程中,实际上可以阅读到pbrt渲染系统的完整实现,而不仅仅是抽象的描述。

面向思路的编程同时使用了文本格式的语言(比方说 TEX 或者 HTML) 以及高级编程语言(比方说 C++)。而且这两种语言可以互相转换,当我们需要理解这段代码发生了什么时,可以将高级编程语言转成文本格式来方便理解(思路上的理解),而当我们需要执行这段代码看效果时,也能无缝地将文本语言转换为高级编程语言让计算机执行(代码实现上的理解)。

举个简单的例子,考虑这样的一个函数 InitGlobals() 它用来初始化全局变量

1
2
3
4
5
void InitGlobals() {
nMarbles = 25.7;
shoeSize = 13;
dielectric = true;
}

尽管看起来很整洁,但是在缺乏上下文的情况下,我们很难理解这段代码的含义。比方说,为什么变量 nMarbles(弹珠的数目)的值是一个浮点数?为了理解这个变量的含义,我们可能要去找遍整个程序出现这个变量的地方,进一步猜测它的作用。尽管这种表达方式对编译器来说是友好的,但是它却不利于阅读。
对于面向思路的编程,我们可以这么表达 InitGlobals():

1
2
3
4
<Function Definitions> = 
void InitGlobals() {
<Initialize Global Variables 2>
}

我们定义了一个文本段,称为 <Funcgion Definitions>,它包含了一个函数 InitGlobals()。而在 InitGlobals 的函数体内,定义了另外一个文本段 <Initialize Global Variables>。由于函数体内的文本段还未定义,所以我们还无法完全理解整段代码的含义。
但,这是介于文本语言和编程语言之间一个恰当的抽象。当我们需要定义shoeSize时,可以这么写

1
2
<Initialize Global Variables> = 
shoeSize = 13;

在实际编译代码的时候,我们只要将文本段一一替换成代码即可(类似于C++宏)。当我们需要定义dielectric时,我们可以将文本段拼接在一起:

1
2
<Initialize Global Variables> += 
dielectric = true;

我们使用 += 符号来表达文本段的拼接。当我们编译的时候,这三个文本段会合成这个样子:

1
2
3
4
void InitGlobals() {
shoeSize = 13;
dielectric = true;
}

使用文本段,我们就可以将复杂的函数按思路拆解成文本段,比方说,一个复杂的函数可以这么写:

1
2
3
4
5
6
7
8
9
<Function Definitions> +=
void complexFunc(int x, int y, double *values) {
<Check validity of arguments>
if (x < y) {
<Swap parameter values>
}
<Do precomputation before loop>
<Loop through and update values array>
}

同样的,在编译的时候,这些文本段都会被宏展开。在表述思路的时候,我们可以按思路依次去表达每个文本段的含义以及代码。这种拆分方式可以让读者一眼看出代码的思路,不至于陷入细节。通常一个文本段不会超过10行。

1.1.1 索引和交叉引用

下面我们将介绍一个特性,让我们更方便地找到需要的文本段:在定义文本段时,我们在边缘写下了页码,表示在这些页数引用了当前文本段,在附录C,我们收集了所有文本段的定义所在的页码。让我们看个定义文本段例子:

1
2
<A fascinating fragment> = 184,690
mMarbles += .001;

这意味着在184页和690页使用了这个文本段。有一部分文本段已经在之前的页码出现过,或者过于重复,我们就没有把页码列出。让我们看一个引用文本段的例子:

1
2
3
4
<Do something interesting> += 500
InitializeSomethingInteresting();
<Do something else Interesting 486>
CleanUp();

这表明我们引用的文本段在486页定义。

0.0 setup

写在前面
昨天简要的浏览了下《ray tracing in one week》 以及之后的 《ray tracing in next week》和 《ray tracing the rest of your life》的基本内容,我感觉后面的代码开始出现紊乱,不利于学习,需要先暂停梳理一下结构。
在纠结中,打算直接开启pbr的学习,那就不得不从备受推崇的《physical base rendering》开始学习了,全书1000+页。
要看完整本书并且实现自己的离线渲染器,我发现不做笔记,不写心得,应该是决然写不成的了。
我选择了一个偷懒的方式,打算以翻译作为切入点。

正文
关于搭建pbr配套的源码环境pbrt-v3,已经有@miccall详尽的介绍:PBRT-v3 在windows下的编译和使用
昨天我也参考了这篇文章做了win64环境下的搭建,在这里补充一些信息:
1.doxygen并非必要安装项,我发现这是一个自动生成文档的工具,理论上不应该影响编译,事实上也如此
2.当我试图运行 “pbrt F:\PBRT\pbrt-v3\scenes\killeroo-simple.pbrt”时,发现弹出abort()错误,后发现这个报错在git bash下会出现,当我换成power shell重新运行时,这个问题便迎刃而解。
3.我使用了mrViewer浏览最后生成的exr格式的图片

最后实例场景的渲染图如下:

2 the vec3 class

写在前面:
19年的ggj要到了,来广州之前还信誓旦旦地说这次的ggj要用unreal!现在看来这个计划应该是要鸽了。可能会用回creator吧?可能当一个观众?God knows

正文
《ray tracing in one weekend》的第二章,造了一个3D向量轮子:Vec3
向量的作用,顾名思义就是表示方向的一个度量
打个比方,我们面前有一个盒子,为了描述盒子的体积,需要几个数字?
简单,三个数字,长(depth),宽(width),高(heigth),就可以描述清楚了。

很好,让我们换个表诉方式,如果盒子最内侧那个点是原点(0,0,0)
表述盒子最外侧那个点的坐标,是啥呢?
不出意外,应该就等于盒子的长宽高(x=width,y=height,z=depth)

继续,我们再换个表述方式,从最内侧的点(原点)指向最外侧的点的方向,如何去表达呢?
简单,将两个点的坐标相减就可以得到向量v = (x=width,y=height,z=depth) - (x=0,y=0,z=0)

在这里就会引入一个问题:
表达方向的向量和表达位置的坐标,用三个数字来表示的时候,可能会是相同的表达式(比如上面的情况,任何坐标减去原点得到的向量,数值和坐标一样),一个无头无尾,没有上下文的三个数(x,y,z),我们如何知道它表示的是向量,还是坐标呢?
通常我们会引入第四个数 w,用(x,y,z,w)来解决上面的问题
w = 0 表示这是一个向量,w = 1 表示这是一个坐标
对于(x,y,z,w)这种形式的四元组,我们称为齐次坐标。
怎么理解齐次坐标?
只要x,y,z 和 w 保持相同的倍数,都是同一个齐次坐标,
打个简单的比方: 我们称 (x,y,z,w) 和 (2 x, 2 y, 2 z, 2 w)是同一个东西
因为他们都可以通过除以最后一个数得到相同的表达式(x/w,y/w,z/w,1)
对于w = 0 表示一个向量,我们怎么去理解呢?
我们试试 (x/w, y/w, z/w, w/w)
当然 0 是不能除以 0 的
不过如果假设w是一个无限接近于0的正数
这时(x/w,y/w,z/w)就会变成一个非常大的数,那么这就像一条超长的射线,射向无限远,是不是就有种向量的感觉,指向某个地方
虽然听起来像科幻片,不过这段理解是在阅读《real time shadows》中,构建shadow volumns时看到的说法。或许有误。但也不是什么见不得光的想法。
当然 w = 0 和 w = 1,在仿射变换中,是一个开关translate矩阵作用的开关,设计得非常精妙,能解决问题当然也就足够了。这背后的想法很简单,坐标依赖于参考点,在移动的时候值会变化,而向量不论如何移动,值都是不变的。

回到标题,第二章并没有构建Vec4,而是一个Vec3,向量的基本操作不外乎操作x,y,z属性,做加减乘除调制,求长度,求归一化,求点乘和叉乘。

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
class Vec3 {
// 构造
constructor(x = 0, y = 0, z = 0) {
this.x = x
this.y = y
this.z = z
}
// 加
add(vec) {
return new Vec3(this.x + vec.x, this.y + vec.y, this.z + vec.z)
}
// 减
sub(vec) {
return new Vec3(this.x - vec.x, this.y - vec.y, this.z - vec.z)
}
// 乘
mul(num) {
return new Vec3(this.x * num, this.y * num, this.z * num)
}
// 除
div(num) {
let inv = 1 / num
return new Vec3(this.x * inv, this.y * inv, this.z * inv)
}
// 调制
mod(vec) {
return new Vec3(this.x * vec.x, this.y * vec.y, this.z * vec.z)
}
addSelf(vec) {
this.x += vec.x,this.y += vec.y,this.z += vec.z
return this
}
subSelf(vec) {
this.x -= vec.x,this.y -= vec.y,this.z -= vec.z
return this
}
mulSelf(num) {
this.x *= num, this.y *= num, this.z *= num
return this
}
divSelf(num) {
let inv = 1 / num
this.x *= inv, this.y *= inv, this.z *= inv
return this
}
modSelf(vec) {
this.x *= vec.x, this.y *= vec.y, ths.z *= vec.z
return this
}
// 长度
length() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
}
sqrLength() {
return this.x * this.x + this.y * this.y + this.z * this.z
}
// 归一化
normalize() {
let invLen = 1/Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
return new Vec3(this.x * invLen, this.y * invLen, this.z * invLen)
}
normalizeSelf() {
let invLen = 1/Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
this.x *= invLen, this.y *= invLen, this.z *= invLen
return this
}
// 点乘
dot(vec) {
return this.x * vec.x + this.y * vec.y + this.z * vec.z
}

//叉乘
cross(vec) {
return new Vec3(
this.y * vec.z - this.z * vec.y,
this.z * vec.x - this.x * vec.z,
this.x * vec.y - this.y * vec.x,
)
}
}

最后对第一章的绘制做了点小修改,使用Vec3来存储每个像素的结果,当然最后的画面也和第一张是一样的

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
<html>
<canvas width="256" height="256" id="screen"></canvas>
<!-- 引入向量模块 -->
<script src="./vec.js"></script>
<script>
function render(canvas)
{
let ctx = canvas.getContext("2d")
let w = canvas.attributes.width.value
let h = canvas.attributes.height.value
ctx.fillStyle = "rgb(0,0,0)"
ctx.fillRect(0, 0, w, h)
let imgdata = ctx.getImageData(0, 0, w, h)
let pixels = imgdata.data
let i = 0
for(let y = 0; y < h; y++) {
let sy = 1 - y / h
for(let x = 0; x < w; x++)
{
let sx = x / w
// 使用向量存储每个向量的结果
vec = new Vec3(sx,sy,0.2)
pixels[i ] = vec.x * 255
pixels[i + 1] = vec.y * 255
pixels[i + 2] = vec.z * 255
pixels[i + 3] = 1 * 255
i+=4
}
}
ctx.putImageData(imgdata, 0, 0)
}
let canvas = document.getElementById('screen')
render(canvas)
</script>
</html>

1 output an image

写在前面:
之前一直在想,是不是可以使用简单的方式作为切入点学习渲染?然而在寻找答案这条路上走了很多弯路:
考虑过Ogre,然而不喜欢那些接口。
考虑过U3D,然而只考虑了一瞬间哈哈。
考虑过UE4,但是面对可视化材质编辑器和纷繁复杂,一日一变的usf,又感觉无从下手
考虑过Creator3D,然而看了一眼论坛的公测日志,我又犹豫了
考虑过shader toy,使用pixel shader,直觉告诉我这不是我想要的
考虑过DX12,可是复杂的资源管理让我感到在戴着枷锁跳舞

在这之前看了不少书:《全局光照技术》,《Game Engine Architecture 3th》,《Real Time Rendering 4th》,都是很棒的书,看得似懂非懂,看的过程中不乏有时激动得很想实践一番,奈何不知道从哪里开始。

昨晚辗转反侧之后,突然想到之前@MiloYip在知乎的回答:用JavaScript玩转计算机图形学(一)光线追踪入门
今天来尝试了一番之后,我相信这就是我想要的。@MiloYip在更新了两章用js玩光追的内容就鸽了,可能是发现了更有挑战的领域了吧。但是对我来说,一切从简是一个很好的开始。

正文
从这里开始,就是《ray tracing in one weekend》的冒险之旅了。
所有的内容,都将使用javascript实践。

光线追踪,简单的理解,我们不断的去跟踪进入眼球的光线,查看每条光线的辐照度,辐照度可以RGBA表示,至于RGBA到一个能看到的画面的映射,我们暂时不去深究,让我们立足于RGBA这一边界。
抽象来看,每条进入眼球的光线,相当于都带了一个RGBA值,这些光线在进入眼球的过程中,会先打向一个方形的平面(并将光线的RGBA值打在交点上(叠加),于是平面上那个点就有了颜色),这个平面就是我们最终看到的画面。

好了,问题来了,最基础的,我们如何去显示一个画面?
最简单的方式,一个方形的画面,我们可以用数组来存储这些信息(RGBA值)。通过修改这个数组的信息,理论上我们就相当于在修改画面的表现了。
刚刚好,canvas就有这样的接口。

获取像素数组

1
2
3
4
5
let ctx = canvas.getContext('2d')
let width = canvas.attributes.width.value
let height = canvas.attributes.height.value
let imgdata = ctx.getImageData(0,0,width,height)
let pixels = imgdata.data // 像素数组(一维)

使用像素数组绘制画面

1
ctx.putImageData(imgdata, 0, 0)

这是一个很好的开始,剩下的就只要从pixels里面做文章了。

回到标题,让我们输出一个画面,将数组坐标绘制成颜色!

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
<html>
<canvas width="256" height="256" id="screen"></canvas>
<script>
function render(canvas)
{
// 获取像素数组
let ctx = canvas.getContext("2d")
let w = canvas.attributes.width.value
let h = canvas.attributes.height.value
ctx.fillStyle = "rgb(0,0,0)"
ctx.fillRect(0, 0, w, h)
let imgdata = ctx.getImageData(0, 0, w, h)
let pixels = imgdata.data

//将数组坐标(通过除以数组长度映射到[0,1])当作颜色绘制
let i = 0
for(let y = 0; y < h; y++) {
let sy = 1 - y / h
for(let x = 0; x < w; x++)
{
let sx = x / w
pixels[i ] = sx * 255
pixels[i + 1] = sy * 255
pixels[i + 2] = 0.2 * 255
pixels[i + 3] = 1 * 255
i+=4
}
}
// 使用像素数组绘制画面
ctx.putImageData(imgdata, 0, 0)
}
let canvas = document.getElementById('screen')
render(canvas)
</script>
</html>

最终画面如下: