Skip to content

Commit 7b71475

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 f45f3ec commit 7b71475

File tree

6 files changed

+190
-4
lines changed

6 files changed

+190
-4
lines changed

src/engine/renderer/gl_shader.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,11 @@ static std::string GenEngineConstants() {
562562
AddConst( str, "MAX_GLSL_BONES", 4 );
563563
}
564564

565+
if ( r_sobelFiltering->integer )
566+
{
567+
AddDefine( str, "r_sobelFiltering", 1 );
568+
}
569+
565570
if ( r_wrapAroundLighting->value )
566571
AddConst( str, "r_WrapAroundLighting", r_wrapAroundLighting->value );
567572

@@ -1472,6 +1477,7 @@ GLShader_lightMapping::GLShader_lightMapping( GLShaderManager *manager ) :
14721477
GLDeformStage( this ),
14731478
GLCompileMacro_USE_DELUXE_MAPPING( this ),
14741479
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1480+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
14751481
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
14761482
GLCompileMacro_USE_PHYSICAL_SHADING( this )
14771483
{
@@ -1528,6 +1534,7 @@ GLShader_vertexLighting_DBS_entity::GLShader_vertexLighting_DBS_entity( GLShader
15281534
GLCompileMacro_USE_VERTEX_SKINNING( this ),
15291535
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
15301536
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1537+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
15311538
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
15321539
GLCompileMacro_USE_REFLECTIVE_SPECULAR( this ),
15331540
GLCompileMacro_USE_PHYSICAL_SHADING( this )
@@ -1588,6 +1595,7 @@ GLShader_vertexLighting_DBS_world::GLShader_vertexLighting_DBS_world( GLShaderMa
15881595
u_Lights( this ),
15891596
GLDeformStage( this ),
15901597
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1598+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
15911599
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
15921600
GLCompileMacro_USE_PHYSICAL_SHADING( this )
15931601
{
@@ -1648,6 +1656,7 @@ GLShader_forwardLighting_omniXYZ::GLShader_forwardLighting_omniXYZ( GLShaderMana
16481656
GLCompileMacro_USE_VERTEX_SKINNING( this ),
16491657
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
16501658
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1659+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
16511660
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
16521661
GLCompileMacro_USE_SHADOWING( this )
16531662
{
@@ -1708,6 +1717,7 @@ GLShader_forwardLighting_projXYZ::GLShader_forwardLighting_projXYZ( GLShaderMana
17081717
GLCompileMacro_USE_VERTEX_SKINNING( this ),
17091718
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
17101719
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1720+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
17111721
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
17121722
GLCompileMacro_USE_SHADOWING( this )
17131723
{
@@ -1771,6 +1781,7 @@ GLShader_forwardLighting_directionalSun::GLShader_forwardLighting_directionalSun
17711781
GLCompileMacro_USE_VERTEX_SKINNING( this ),
17721782
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
17731783
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1784+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
17741785
GLCompileMacro_USE_PARALLAX_MAPPING( this ),
17751786
GLCompileMacro_USE_SHADOWING( this )
17761787
{
@@ -1855,6 +1866,7 @@ GLShader_reflection::GLShader_reflection( GLShaderManager *manager ):
18551866
GLCompileMacro_USE_VERTEX_SKINNING( this ),
18561867
GLCompileMacro_USE_VERTEX_ANIMATION( this ),
18571868
GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP( this ),
1869+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( this ),
18581870
GLCompileMacro_USE_PARALLAX_MAPPING( this )
18591871
{
18601872
}

src/engine/renderer/gl_shader.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ class GLCompileMacro
785785
USE_TCGEN_LIGHTMAP,
786786
USE_DELUXE_MAPPING,
787787
USE_HEIGHTMAP_IN_NORMALMAP,
788+
USE_NORMALMAP_FROM_HEIGHTMAP,
788789
USE_PARALLAX_MAPPING,
789790
USE_REFLECTIVE_SPECULAR,
790791
USE_SHADOWING,
@@ -1035,6 +1036,31 @@ class GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP :
10351036
}
10361037
};
10371038

1039+
class GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP :
1040+
GLCompileMacro
1041+
{
1042+
public:
1043+
GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP( GLShader *shader ) :
1044+
GLCompileMacro( shader )
1045+
{
1046+
}
1047+
1048+
const char *GetName() const
1049+
{
1050+
return "USE_NORMALMAP_FROM_HEIGHTMAP";
1051+
}
1052+
1053+
EGLCompileMacro GetType() const
1054+
{
1055+
return EGLCompileMacro::USE_NORMALMAP_FROM_HEIGHTMAP;
1056+
}
1057+
1058+
void SetNormalMapFromHeightMap( bool enable )
1059+
{
1060+
SetMacro( enable );
1061+
}
1062+
};
1063+
10381064
class GLCompileMacro_USE_PARALLAX_MAPPING :
10391065
GLCompileMacro
10401066
{
@@ -2163,6 +2189,7 @@ class GLShader_lightMapping :
21632189
public GLDeformStage,
21642190
public GLCompileMacro_USE_DELUXE_MAPPING,
21652191
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2192+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
21662193
public GLCompileMacro_USE_PARALLAX_MAPPING,
21672194
public GLCompileMacro_USE_PHYSICAL_SHADING
21682195
{
@@ -2196,6 +2223,7 @@ class GLShader_vertexLighting_DBS_entity :
21962223
public GLCompileMacro_USE_VERTEX_SKINNING,
21972224
public GLCompileMacro_USE_VERTEX_ANIMATION,
21982225
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2226+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
21992227
public GLCompileMacro_USE_PARALLAX_MAPPING,
22002228
public GLCompileMacro_USE_REFLECTIVE_SPECULAR,
22012229
public GLCompileMacro_USE_PHYSICAL_SHADING
@@ -2228,6 +2256,7 @@ class GLShader_vertexLighting_DBS_world :
22282256
public u_Lights,
22292257
public GLDeformStage,
22302258
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2259+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
22312260
public GLCompileMacro_USE_PARALLAX_MAPPING,
22322261
public GLCompileMacro_USE_PHYSICAL_SHADING
22332262
{
@@ -2266,6 +2295,7 @@ class GLShader_forwardLighting_omniXYZ :
22662295
public GLCompileMacro_USE_VERTEX_SKINNING,
22672296
public GLCompileMacro_USE_VERTEX_ANIMATION,
22682297
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2298+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
22692299
public GLCompileMacro_USE_PARALLAX_MAPPING,
22702300
public GLCompileMacro_USE_SHADOWING //,
22712301
{
@@ -2305,6 +2335,7 @@ class GLShader_forwardLighting_projXYZ :
23052335
public GLCompileMacro_USE_VERTEX_SKINNING,
23062336
public GLCompileMacro_USE_VERTEX_ANIMATION,
23072337
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2338+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
23082339
public GLCompileMacro_USE_PARALLAX_MAPPING,
23092340
public GLCompileMacro_USE_SHADOWING //,
23102341
{
@@ -2346,6 +2377,7 @@ class GLShader_forwardLighting_directionalSun :
23462377
public GLCompileMacro_USE_VERTEX_SKINNING,
23472378
public GLCompileMacro_USE_VERTEX_ANIMATION,
23482379
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2380+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
23492381
public GLCompileMacro_USE_PARALLAX_MAPPING,
23502382
public GLCompileMacro_USE_SHADOWING //,
23512383
{
@@ -2395,6 +2427,7 @@ class GLShader_reflection :
23952427
public GLCompileMacro_USE_VERTEX_SKINNING,
23962428
public GLCompileMacro_USE_VERTEX_ANIMATION,
23972429
public GLCompileMacro_USE_HEIGHTMAP_IN_NORMALMAP,
2430+
public GLCompileMacro_USE_NORMALMAP_FROM_HEIGHTMAP,
23982431
public GLCompileMacro_USE_PARALLAX_MAPPING
23992432
{
24002433
public:

src/engine/renderer/glsl_source/reliefMapping_fp.glsl

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ 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
@@ -43,7 +44,142 @@ vec3 NormalInTangentSpace(vec2 texNormal)
4344
vec3 normal;
4445

4546
#if defined(r_normalMapping)
46-
#if defined(USE_HEIGHTMAP_IN_NORMALMAP)
47+
#if defined(USE_NORMALMAP_FROM_HEIGHTMAP)
48+
// inspired from various examples read on many places, in order to make the most complete code possible.
49+
//
50+
// major inspiration was this 2007 post by jollyjeffers:
51+
// https://www.gamedev.net/forums/topic/475213-generate-normal-map-from-heightmap-algorithm/4117038/
52+
//
53+
// various pages like this one recommends doing abs() on sample read in case of float image format:
54+
// http://www.catalinzima.com/2008/01/converting-displacement-maps-into-normal-maps/
55+
// other people does the same there:
56+
// https://community.khronos.org/t/heightmap-to-normalmap/58862
57+
// and one tells he had issues by not doing it:
58+
// https://gamedev.stackexchange.com/questions/165575²/calculating-normal-map-from-height-map-using-sobel-operator
59+
// other people say texture2D clamps it but it's safer to do this, unfortunately no one GLSL code was read for comparison
60+
// since all the examples found were using other languages: C++, HLSL, etc.
61+
62+
vec2 pixelSize = 1.0 / vec2(textureSize(u_HeightMap, 0));
63+
64+
#if defined(r_sobelFiltering)
65+
// useful things to know:
66+
//
67+
// * it's required to get height samples (S) surrounding the current pixel (P) to do a sobel filter:
68+
//
69+
// S S S
70+
// S P 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+
ivec3 offsets;
92+
mat3 xCoords;
93+
mat3 yCoords;
94+
mat3 heights;
95+
mat3 sobel;
96+
97+
// components will be computed by accumylating values
98+
normal = vec3(0.0, 0.0, 0.0);
99+
100+
// set offsets
101+
offsets = ivec3(-1, 0, 1);
102+
103+
// set sobel X kernel
104+
// beware, X is line and Y is column in 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 coords
116+
xCoords[i][j] = texNormal.x + pixelSize.x * offsets[i];
117+
yCoords[i][j] = texNormal.y + pixelSize.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
138+
normal.z = sqrt(1.0 - dot(normal.xy, normal.xy));
139+
140+
// HACK: prevent artifacts that may produce black normalmap pixels
141+
// Xonotic's textures/map_glowplant/sand_bump triggers this bug
142+
normal.z = clamp(normal.z, 0, 1);
143+
144+
normal.xy = 2.0 * normal.xy;
145+
#else // !r_sobelFiltering
146+
// useful thing to know:
147+
//
148+
// * only three samples are required to determine two vectors
149+
// they would be used to generate the normal at this pixel:
150+
//
151+
// P S
152+
// S
153+
//
154+
// the pixel itself is used as sample
155+
156+
vec2 hOffsets;
157+
vec2 vOffsets;
158+
vec2 hCoords;
159+
vec2 vCoords;
160+
vec3 heights;
161+
vec3 xVector;
162+
vec3 yVector;
163+
164+
// set horizontal and vertical offsets
165+
hOffsets = vec2(pixelSize.x, 0.0);
166+
vOffsets = vec2(0.0, pixelSize.y);
167+
168+
// compute coords
169+
hCoords = vec2(texNormal.x + hOffsets.x, texNormal.y + hOffsets.y);
170+
vCoords = vec2(texNormal.x + vOffsets.x, texNormal.y + vOffsets.y);
171+
172+
// get samples
173+
heights.x = texture2D(u_HeightMap, texNormal).r; // no offset
174+
heights.y = texture2D(u_HeightMap, hCoords).r;
175+
heights.z = texture2D(u_HeightMap, vCoords).r;
176+
177+
xVector = vec3(hOffsets.x, vOffsets.x, heights.y - heights.x);
178+
yVector = vec3(hOffsets.y, vOffsets.y, heights.z - heights.x);
179+
180+
normal = cross(xVector, yVector);
181+
#endif // !r_sobelFiltering
182+
#elif defined(USE_HEIGHTMAP_IN_NORMALMAP)
47183
// alpha channel contains the height map so do not try to reconstruct normal map from it
48184
normal = texture2D(u_NormalMap, texNormal).rgb;
49185
normal = 2.0 * normal - 1.0;

src/engine/renderer/tr_init.cpp

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

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

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

src/engine/renderer/tr_local.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2862,6 +2862,7 @@ static inline void halfToFloat( const f16vec4_t in, vec4_t out )
28622862
extern cvar_t *r_glowMapping;
28632863
extern cvar_t *r_reflectionMapping;
28642864

2865+
extern cvar_t *r_sobelFiltering;
28652866
extern cvar_t *r_wrapAroundLighting;
28662867
extern cvar_t *r_halfLambertLighting;
28672868
extern cvar_t *r_rimLighting;

src/engine/renderer/tr_shade.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,8 @@ static void Render_lightMapping( int stage )
12381238

12391239
gl_lightMappingShader->SetHeightMapInNormalMap( hasHeightMapInNormalMap );
12401240

1241+
gl_lightMappingShader->SetNormalMapFromHeightMap( hasHeightMap && !hasNormalMap );
1242+
12411243
gl_lightMappingShader->SetParallaxMapping( parallaxMapping );
12421244

12431245
tess.vboVertexSprite = false;

0 commit comments

Comments
 (0)