1+ /* Copyright (c) 2025, Sascha Willems
2+ *
3+ * SPDX-License-Identifier: MIT
4+ *
5+ */
6+
7+ struct VSInput
8+ {
9+ float3 Pos;
10+ float3 Normal;
11+ };
12+
13+ struct VSOutput
14+ {
15+ float4 Pos : SV_POSITION;
16+ float3 UVW;
17+ float3 WorldPos;
18+ float3 Normal;
19+ float3 ViewVec;
20+ float3 LightVec;
21+ };
22+
23+ struct FSOutput
24+ {
25+ float4 Color0 : SV_TARGET0;
26+ float4 Color1 : SV_TARGET1;
27+ };
28+
29+ struct UBO {
30+ float4x4 projection;
31+ float4x4 modelview;
32+ float4x4 inverseModelview;
33+ float exposure;
34+ };
35+ ConstantBuffer < UBO> ubo;
36+
37+ SamplerCube samplerEnvMap;
38+
39+ [[SpecializationConstant]] const int objectType = 0 ;
40+
41+ [shader(" vertex" )]
42+ VSOutput vertexMain(VSInput input)
43+ {
44+ VSOutput output;
45+ output .UVW = input .Pos ;
46+
47+ switch (objectType) {
48+ case 0 : // Skybox
49+ output .WorldPos = mul((float4x3 )ubo .modelview , input .Pos ).xyz ;
50+ output .Pos = mul(ubo .projection , float4(output .WorldPos , 1 . 0 ));
51+ break ;
52+ case 1 : // Object
53+ output .WorldPos = mul(ubo .modelview , float4(input .Pos , 1 . 0 )).xyz ;
54+ output .Pos = mul(ubo .projection , mul(ubo .modelview , float4(input .Pos .xyz , 1 . 0 )));
55+ break ;
56+ }
57+ output .WorldPos = mul(ubo .modelview , float4(input .Pos , 1 . 0 )).xyz ;
58+ output .Normal = mul((float4x3 )ubo .modelview , input .Normal ).xyz ;
59+ float3 lightPos = float3(0 . 0 f , - 5 . 0 f , 5 . 0 f );
60+ output .LightVec = lightPos .xyz - output .WorldPos .xyz ;
61+ output .ViewVec = - output .WorldPos .xyz ;
62+ return output;
63+ }
64+
65+ [shader(" fragment" )]
66+ FSOutput fragmentMain(VSOutput input)
67+ {
68+ FSOutput output;
69+ float4 color;
70+ float3 wcNormal;
71+
72+ switch (objectType) {
73+ case 0 : // Skybox
74+ {
75+ float3 normal = normalize(input .UVW );
76+ color = samplerEnvMap .Sample (normal);
77+ }
78+ break ;
79+
80+ case 1 : // Reflect
81+ {
82+ float3 wViewVec = mul((float4x3 )ubo .inverseModelview , normalize(input .ViewVec )).xyz ;
83+ float3 normal = normalize(input .Normal );
84+ float3 wNormal = mul((float4x3 )ubo .inverseModelview , normal).xyz ;
85+
86+ float NdotL = max(dot(normal, input .LightVec ), 0 . 0 );
87+
88+ float3 eyeDir = normalize(input .ViewVec );
89+ float3 halfVec = normalize(input .LightVec + eyeDir);
90+ float NdotH = max(dot(normal, halfVec), 0 . 0 );
91+ float NdotV = max(dot(normal, eyeDir), 0 . 0 );
92+ float VdotH = max(dot(eyeDir, halfVec), 0 . 0 );
93+
94+ // Geometric attenuation
95+ float NH2 = 2 . 0 * NdotH;
96+ float g1 = (NH2 * NdotV) / VdotH;
97+ float g2 = (NH2 * NdotL) / VdotH;
98+ float geoAtt = min(1 . 0 , min(g1, g2));
99+
100+ const float F0 = 0 . 6 ;
101+ const float k = 0 . 2 ;
102+
103+ // Fresnel (schlick approximation)
104+ float fresnel = pow(1 . 0 - VdotH, 5 . 0 );
105+ fresnel *= (1 . 0 - F0);
106+ fresnel += F0;
107+
108+ float spec = (fresnel * geoAtt) / (NdotV * NdotL * 3 . 14 );
109+
110+ color = samplerEnvMap .Sample (reflect(- wViewVec, wNormal));
111+
112+ color = float4(color .rgb * NdotL * (k + spec * (1 . 0 - k)), 1 . 0 );
113+ }
114+ break ;
115+
116+ case 2 : // Refract
117+ {
118+ float3 wViewVec = mul((float4x3 )ubo .inverseModelview , normalize(input .ViewVec )).xyz ;
119+ float3 wNormal = mul((float4x3 )ubo .inverseModelview , input .Normal ).xyz ;
120+ color = samplerEnvMap .Sample (refract(- wViewVec, wNormal, 1 . 0 / 1 . 6 ));
121+ }
122+ break ;
123+ }
124+
125+
126+ // Color with manual exposure into attachment 0
127+ output .Color0 .rgb = float3(1 . 0 , 1 . 0 , 1 . 0 ) - exp(- color .rgb * ubo .exposure );
128+
129+ // Bright parts for bloom into attachment 1
130+ float l = dot(output .Color0 .rgb , float3(0 . 2126 , 0 . 7152 , 0 . 0722 ));
131+ float threshold = 0 . 75 ;
132+ output .Color1 .rgb = (l > threshold) ? output .Color0 .rgb : float3(0 . 0 , 0 . 0 , 0 . 0 );
133+ output .Color1 .a = 1 . 0 ;
134+ return output;
135+ }
0 commit comments