Skip to content

Commit e06d93e

Browse files
committed
renderer: compute normalmap from heightmap when missing normalmap, aka bumpmap
compute normalmap from heightmap when missing normalmap, also known as bumpmapping two algorithms available: - one naive that does the job, uses 3 samples - one advanced that does better job, uses 8 samples (sobel operation) a new cvar is added: r_sobelFiltering, which would be used to disable or enable various sobel filtering operation including this one the engine is likely to implement other sobel filters in the future, for example DarkPlaces has a postprocessing effect that does such kind of compute, maybe Dæmon based games will want such kind of effect, in this case a common cvar to enable or disable various sobel compute does not look bad the r_sobelFiltering cvar is enabled by default
1 parent fc3a49b commit e06d93e

File tree

7 files changed

+232
-10
lines changed

7 files changed

+232
-10
lines changed

src/engine/renderer/gl_shader.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,11 @@ static std::string GenEngineConstants() {
573573
AddConst( str, "MAX_GLSL_BONES", 4 );
574574
}
575575

576+
if ( r_sobelFiltering->integer )
577+
{
578+
AddDefine( str, "r_sobelFiltering", 1 );
579+
}
580+
576581
if ( r_wrapAroundLighting->value )
577582
AddConst( str, "r_WrapAroundLighting", r_wrapAroundLighting->value );
578583

@@ -1496,6 +1501,7 @@ GLShader_lightMapping::GLShader_lightMapping( GLShaderManager *manager ) :
14961501
GLCompileMacro_USE_LIGHT_MAPPING( this ),
14971502
GLCompileMacro_USE_DELUXE_MAPPING( this ),
14981503
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1504+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
14991505
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
15001506
GLCompileMacro_USE_REFLECTIVE_SPECULAR( this ),
15011507
GLCompileMacro_USE_PHYSICAL_SHADING( this )
@@ -1560,6 +1566,7 @@ GLShader_forwardLighting_omniXYZ::GLShader_forwardLighting_omniXYZ( GLShaderMana
15601566
GLCompileMacro_USE_VERTEX_SKINNING( this ),
15611567
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
15621568
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1569+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
15631570
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
15641571
GLCompileMacro_USE_SHADOWING( this )
15651572
{
@@ -1620,6 +1627,7 @@ GLShader_forwardLighting_projXYZ::GLShader_forwardLighting_projXYZ( GLShaderMana
16201627
GLCompileMacro_USE_VERTEX_SKINNING( this ),
16211628
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
16221629
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1630+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
16231631
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
16241632
GLCompileMacro_USE_SHADOWING( this )
16251633
{
@@ -1683,6 +1691,7 @@ GLShader_forwardLighting_directionalSun::GLShader_forwardLighting_directionalSun
16831691
GLCompileMacro_USE_VERTEX_SKINNING( this ),
16841692
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
16851693
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1694+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
16861695
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
16871696
GLCompileMacro_USE_SHADOWING( this )
16881697
{
@@ -1767,6 +1776,7 @@ GLShader_reflection::GLShader_reflection( GLShaderManager *manager ):
17671776
GLCompileMacro_USE_VERTEX_SKINNING( this ),
17681777
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
17691778
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1779+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
17701780
GLCompileMacro_USE_PARALLAX_MAPPING( this )
17711781
{
17721782
}
@@ -2035,6 +2045,7 @@ GLShader_liquid::GLShader_liquid( GLShaderManager *manager ) :
20352045
u_LightGridOrigin( this ),
20362046
u_LightGridScale( this ),
20372047
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
2048+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
20382049
GLCompileMacro_USE_PARALLAX_MAPPING( this )
20392050
{
20402051
}

src/engine/renderer/gl_shader.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
3030
#define USE_UNIFORM_FIREWALL 1
3131

3232
// *INDENT-OFF*
33-
static const unsigned int MAX_SHADER_MACROS = 9;
33+
static const unsigned int MAX_SHADER_MACROS = 10;
3434
static const unsigned int GL_SHADER_VERSION = 3;
3535

3636
class ShaderException : public std::runtime_error
@@ -787,6 +787,7 @@ class GLCompileMacro
787787
USE_LIGHT_MAPPING,
788788
USE_DELUXE_MAPPING,
789789
USE_HEIGHTMAP_IN_NORMALMAP,
790+
USE_NORMALMAP_FROM_HEIGHTMAP,
790791
USE_PARALLAX_MAPPING,
791792
USE_REFLECTIVE_SPECULAR,
792793
USE_SHADOWING,
@@ -1087,6 +1088,31 @@ class GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP :
10871088
}
10881089
};
10891090

1091+
class GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP :
1092+
GLCompileMacro
1093+
{
1094+
public:
1095+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( GLShader *shader ) :
1096+
GLCompileMacro( shader )
1097+
{
1098+
}
1099+
1100+
const char *GetName() const
1101+
{
1102+
return "USE_NORMALMAP_FROM_HEIGHTMAP";
1103+
}
1104+
1105+
EGLCompileMacro GetType() const
1106+
{
1107+
return EGLCompileMacro::USE_NORMALMAP_FROM_HEIGHTMAP;
1108+
}
1109+
1110+
void SetNormalMapFromHeightMap( bool enable )
1111+
{
1112+
SetMacro( enable );
1113+
}
1114+
};
1115+
10901116
class GLCompileMacro_USE_PARALLAX_MAPPING :
10911117
GLCompileMacro
10921118
{
@@ -2245,6 +2271,7 @@ class GLShader_lightMapping :
22452271
public GLCompileMacro_USE_LIGHT_MAPPING,
22462272
public GLCompileMacro_USE_DELUXE_MAPPING,
22472273
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2274+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
22482275
public GLCompileMacro_USE_PARALLAX_MAPPING,
22492276
public GLCompileMacro_USE_REFLECTIVE_SPECULAR,
22502277
public GLCompileMacro_USE_PHYSICAL_SHADING
@@ -2284,6 +2311,7 @@ class GLShader_forwardLighting_omniXYZ :
22842311
public GLCompileMacro_USE_VERTEX_SKINNING,
22852312
public GLCompileMacro_USE_VERTEX_ANIMATION,
22862313
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2314+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
22872315
public GLCompileMacro_USE_PARALLAX_MAPPING,
22882316
public GLCompileMacro_USE_SHADOWING //,
22892317
{
@@ -2323,6 +2351,7 @@ class GLShader_forwardLighting_projXYZ :
23232351
public GLCompileMacro_USE_VERTEX_SKINNING,
23242352
public GLCompileMacro_USE_VERTEX_ANIMATION,
23252353
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2354+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
23262355
public GLCompileMacro_USE_PARALLAX_MAPPING,
23272356
public GLCompileMacro_USE_SHADOWING //,
23282357
{
@@ -2364,6 +2393,7 @@ class GLShader_forwardLighting_directionalSun :
23642393
public GLCompileMacro_USE_VERTEX_SKINNING,
23652394
public GLCompileMacro_USE_VERTEX_ANIMATION,
23662395
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2396+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
23672397
public GLCompileMacro_USE_PARALLAX_MAPPING,
23682398
public GLCompileMacro_USE_SHADOWING //,
23692399
{
@@ -2413,6 +2443,7 @@ class GLShader_reflection :
24132443
public GLCompileMacro_USE_VERTEX_SKINNING,
24142444
public GLCompileMacro_USE_VERTEX_ANIMATION,
24152445
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2446+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
24162447
public GLCompileMacro_USE_PARALLAX_MAPPING
24172448
{
24182449
public:
@@ -2620,6 +2651,7 @@ class GLShader_liquid :
26202651
public u_LightGridOrigin,
26212652
public u_LightGridScale,
26222653
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2654+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
26232655
public GLCompileMacro_USE_PARALLAX_MAPPING
26242656
{
26252657
public:

src/engine/renderer/glsl_source/reliefMapping_fp.glsl

Lines changed: 166 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,181 @@ uniform sampler2D u_NormalMap;
2929
uniform vec3 u_NormalScale;
3030
#endif // r_normalMapping
3131

32-
#if defined(USE_PARALLAX_MAPPING)
33-
#if !defined(USE_HEIGHTMAP_IN_NORMALMAP)
32+
#if (defined(USE_PARALLAX_MAPPING) && !defined(USE_HEIGHTMAP_IN_NORMALMAP)) || defined(USE_NORMALMAP_FROM_HEIGHTMAP)
3433
uniform sampler2D u_HeightMap;
35-
#endif // !USE_HEIGHTMAP_IN_NORMALMAP
34+
#endif // (USE_PARALLAX_MAPPING && !USE_HEIGHTMAP_IN_NORMALMAP) || USE_NORMALMAP_FROM_HEIGHTMAP
35+
36+
#if defined(USE_PARALLAX_MAPPING)
3637
uniform float u_ParallaxDepthScale;
3738
uniform float u_ParallaxOffsetBias;
3839
#endif // USE_PARALLAX_MAPPING
3940

41+
#if defined(USE_NORMALMAP_FROM_HEIGHTMAP)
42+
vec3 NormalFromHeightMap(vec2 texNormal)
43+
{
44+
vec3 normal;
45+
46+
/* Major inspiration was this post by jollyjeffers:
47+
https://www.gamedev.net/forums/topic/475213-generate-normal-map-from-heightmap-algorithm/#post_4117038
48+
49+
and this Wikipedia article:
50+
https://en.wikipedia.org/wiki/Sobel_operator
51+
52+
Various people recommends doing abs() on sample read in case of float image format:
53+
http://www.catalinzima.com/2008/01/converting-displacement-maps-into-normal-maps/
54+
https://community.khronos.org/t/heightmap-to-normalmap/58862
55+
56+
Or report issues by not doing it:
57+
https://gamedev.stackexchange.com/questions/165575/calculating-normal-map-from-height-map-using-sobel-operator
58+
59+
Other people say texture2D clamps it but it's safer to do this.
60+
*/
61+
62+
// Get texel size
63+
vec2 texelSize = 1.0 / vec2(textureSize(u_HeightMap, 0));
64+
65+
#if defined(r_sobelFiltering)
66+
/* Useful things to know:
67+
- It's required to get height samples (S) surrounding the current texel (T) to do a sobel filter:
68+
69+
S S S
70+
S T S
71+
S S S
72+
73+
- Sobel X kernel is:
74+
75+
[ 1 0 -1 ]
76+
[ 2 0 -2 ]
77+
[ 1 0 -1 ]
78+
79+
See https://en.wikipedia.org/wiki/Sobel_operator#Formulation
80+
81+
- Sobel Y kernel is:
82+
83+
[ 1 2 1 ]
84+
[ 0 0 0 ]
85+
[ -1 -2 -1 ]
86+
87+
Which is the rotated sobel X kernel.
88+
89+
- Tangent space normals are +Z.
90+
*/
91+
92+
ivec3 offsets;
93+
mat3 xCoords;
94+
mat3 yCoords;
95+
mat3 heights;
96+
mat3 sobel;
97+
98+
// Components will be computed by accumulating values.
99+
normal = vec3(0.0, 0.0, 0.0);
100+
101+
// Set offsets:
102+
offsets = ivec3(-1, 0, 1);
103+
104+
// Set sobel X kernel (beware, line is column with this notation):
105+
sobel[0] = vec3( 1.0, 2.0, 1.0);
106+
sobel[1] = vec3( 0.0, 0.0, 0.0);
107+
sobel[2] = vec3(-1.0, -2.0, -1.0);
108+
109+
for (int i = 0; i < 3; i++)
110+
{
111+
for (int j = 0; j < 3; j++)
112+
{
113+
if (i != 1 || j != 1)
114+
{
115+
// Compute coordinates:
116+
xCoords[i][j] = texNormal.x + texelSize.x * offsets[i];
117+
yCoords[i][j] = texNormal.y + texelSize.y * offsets[j];
118+
119+
// Get surrounding samples:
120+
heights[i][j] = abs(texture2D(u_HeightMap, vec2(xCoords[i][j], yCoords[i][j])).r);
121+
122+
if (i != 1)
123+
{
124+
// Sobel computation for X component (use the X sobel kernel):
125+
normal.x += heights[i][j] * sobel[i][j];
126+
}
127+
128+
if (j != 1)
129+
{
130+
// Sobel computation for Y component (use the rotated X sobel kernel as Y one):
131+
normal.y += heights[i][j] * sobel[j][i];
132+
}
133+
}
134+
}
135+
}
136+
137+
/* Reconstruct Z component while making sure to not take the square root
138+
of a negative number. That may occur because of compression artifacts.
139+
140+
Xonotic texture known to produce black normalmap artifacts when doing
141+
Z reconstruction from X and Y computed from jpg heightmap:
142+
textures/map_glowplant/sand_bump
143+
*/
144+
145+
normal.z = sqrt(max(0.0, 1.0 - dot(normal.xy, normal.xy)));
146+
147+
normal.xy = 2.0 * normal.xy;
148+
#else // !r_sobelFiltering
149+
/* Useful thing to know:
150+
- Only three samples are required to determine two vectors
151+
they would be used to generate the normal at this texel:
152+
153+
T S
154+
S
155+
156+
The texel itself is used as sample.
157+
*/
158+
159+
vec2 hOffsets;
160+
vec2 vOffsets;
161+
vec2 hCoords;
162+
vec2 vCoords;
163+
vec3 heights;
164+
vec3 xVector;
165+
vec3 yVector;
166+
167+
// Set horizontal and vertical offsets:
168+
hOffsets = vec2(texelSize.x, 0.0);
169+
vOffsets = vec2(0.0, texelSize.y);
170+
171+
// Compute coordinates:
172+
hCoords = vec2(texNormal.x + hOffsets.x, texNormal.y + hOffsets.y);
173+
vCoords = vec2(texNormal.x + vOffsets.x, texNormal.y + vOffsets.y);
174+
175+
// Get samples:
176+
heights.x = texture2D(u_HeightMap, texNormal).r; // No offset.
177+
heights.y = texture2D(u_HeightMap, hCoords).r;
178+
heights.z = texture2D(u_HeightMap, vCoords).r;
179+
180+
xVector = vec3(hOffsets.x, vOffsets.x, heights.y - heights.x);
181+
yVector = vec3(hOffsets.y, vOffsets.y, heights.z - heights.x);
182+
183+
normal = cross(xVector, yVector);
184+
#endif // !r_sobelFiltering
185+
186+
return normal;
187+
}
188+
#endif // USE_NORMALMAP_FROM_HEIGHTMAP
189+
40190
// compute normal in tangent space
41191
vec3 NormalInTangentSpace(vec2 texNormal)
42192
{
43193
vec3 normal;
44194

45195
#if defined(r_normalMapping)
46-
#if defined(USE_HEIGHTMAP_IN_NORMALMAP)
196+
#if defined(USE_NORMALMAP_FROM_HEIGHTMAP)
197+
normal = NormalFromHeightMap(texNormal);
198+
#elif defined(USE_HEIGHTMAP_IN_NORMALMAP)
47199
// alpha channel contains the height map so do not try to reconstruct normal map from it
48200
normal = texture2D(u_NormalMap, texNormal).rgb;
49201
normal = 2.0 * normal - 1.0;
202+
203+
// HACK: the GLSL code is currently assuming
204+
// DirectX normal map format (+X -Y +Z)
205+
// but engine is assuming the OpenGL way (+X +Y +Z)
206+
normal.y *= -1;
50207
#else // !USE_HEIGHTMAP_IN_NORMALMAP
51208
// the Capcom trick abusing alpha channel of DXT1/5 formats to encode normal map
52209
// https://github.com/DaemonEngine/Daemon/issues/183#issuecomment-473691252
@@ -74,6 +231,11 @@ vec3 NormalInTangentSpace(vec2 texNormal)
74231
// This might happen with other formats too. So we must take care not to
75232
// take the square root of a negative number here.
76233
normal.z = sqrt(max(0, 1.0 - dot(normal.xy, normal.xy)));
234+
235+
// HACK: the GLSL code is currently assuming
236+
// DirectX normal map format (+X -Y +Z)
237+
// but engine is assuming the OpenGL way (+X +Y +Z)
238+
normal.y *= -1;
77239
#endif // !USE_HEIGHTMAP_IN_NORMALMAP
78240
/* Disable normal map scaling when normal Z scale is set to zero.
79241
@@ -87,11 +249,6 @@ vec3 NormalInTangentSpace(vec2 texNormal)
87249
{
88250
normal *= u_NormalScale;
89251
}
90-
91-
// HACK: the GLSL code is currently assuming
92-
// DirectX normal map format (+X -Y +Z)
93-
// but engine is assuming the OpenGL way (+X +Y +Z)
94-
normal.y *= -1;
95252
#else // !r_normalMapping
96253
// Flat normal map is {0.5, 0.5, 1.0} in [ 0.0, 1.0]
97254
// which is stored as {0.0, 0.0, 1.0} in [-1.0, 1.0].

src/engine/renderer/tr_init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
171171
cvar_t *r_glowMapping;
172172
cvar_t *r_reflectionMapping;
173173

174+
cvar_t *r_sobelFiltering;
174175
cvar_t *r_wrapAroundLighting;
175176
cvar_t *r_halfLambertLighting;
176177
cvar_t *r_rimLighting;
@@ -1203,6 +1204,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p
12031204
r_glowMapping = ri.Cvar_Get( "r_glowMapping", "1", CVAR_LATCH );
12041205
r_reflectionMapping = ri.Cvar_Get( "r_reflectionMapping", "0", CVAR_CHEAT );
12051206

1207+
r_sobelFiltering = ri.Cvar_Get( "r_sobelFiltering", "1", CVAR_LATCH );
12061208
r_wrapAroundLighting = ri.Cvar_Get( "r_wrapAroundLighting", "0.7", CVAR_CHEAT | CVAR_LATCH );
12071209
r_halfLambertLighting = ri.Cvar_Get( "r_halfLambertLighting", "1", CVAR_CHEAT | CVAR_LATCH );
12081210
r_rimLighting = ri.Cvar_Get( "r_rimLighting", "0", CVAR_LATCH | CVAR_ARCHIVE );

src/engine/renderer/tr_local.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,7 @@ static inline void halfToFloat( const f16vec4_t in, vec4_t out )
11431143
bool hasNormalMap;
11441144
bool hasHeightMap;
11451145
bool isHeightMapInNormalMap;
1146+
bool isNormalMapFromHeightMap;
11461147
bool hasMaterialMap;
11471148
bool isMaterialPhysical;
11481149
bool hasGlowMap;
@@ -2895,6 +2896,7 @@ static inline void halfToFloat( const f16vec4_t in, vec4_t out )
28952896
extern cvar_t *r_glowMapping;
28962897
extern cvar_t *r_reflectionMapping;
28972898

2899+
extern cvar_t *r_sobelFiltering;
28982900
extern cvar_t *r_wrapAroundLighting;
28992901
extern cvar_t *r_halfLambertLighting;
29002902
extern cvar_t *r_rimLighting;

0 commit comments

Comments
 (0)