diff --git a/Effects/urban.eff b/Effects/urban.eff
index f8eecd141..d73d5b2ea 100644
--- a/Effects/urban.eff
+++ b/Effects/urban.eff
@@ -7,6 +7,7 @@
0.008
0.75 0.59 0.05
+ 10
15
@@ -68,12 +69,24 @@
-
-
-
+
2
+
+ nearest-mipmap-nearest
+
+
+
+
+ average
+ average
+ average
+ min
+
+
+
+ 3
noise
@@ -102,10 +115,15 @@
sampler-2d
1
+
+ QDMTex
+ sampler-2d
+ 2
+
NoiseTex
sampler-3d
- 2
+ 3
depth_factor
@@ -132,6 +150,11 @@
float
+
+ max_lod_level
+ float
+
+
diff --git a/Shaders/urban.frag b/Shaders/urban.frag
index 7e207cf44..f61faa35a 100644
--- a/Shaders/urban.frag
+++ b/Shaders/urban.frag
@@ -2,9 +2,15 @@
// Licence: GPL v2
// Author: Frederic Bouvier.
// Adapted from the paper by F. Policarpo et al. : Real-time Relief Mapping on Arbitrary Polygonal Surfaces
+// Adapted from the paper and sources by M. Drobot in GPU Pro : Quadtree Displacement Mapping with Height Blending
#version 120
+#define TEXTURE_MIP_LEVELS 10
+#define TEXTURE_PIX_COUNT 1024 //pow(2,TEXTURE_MIP_LEVELS)
+#define BINARY_SEARCH_COUNT 10
+#define BILINEAR_SMOOTH_FACTOR 2.0
+
varying vec4 rawpos;
varying vec4 ecPosition;
varying vec3 VNormal;
@@ -16,6 +22,7 @@ varying vec4 constantColor;
uniform sampler3D NoiseTex;
uniform sampler2D BaseTex;
uniform sampler2D NormalTex;
+uniform sampler2D QDMTex;
uniform float depth_factor;
uniform float tile_size;
uniform float quality_level; // From /sim/rendering/quality-level
@@ -23,52 +30,103 @@ uniform float snowlevel; // From /sim/rendering/snow-level-m
uniform vec3 night_color;
const float scale = 1.0;
-int linear_search_steps = 10;
+int GlobalIterationCount = 0;
+int gIterationCap = 64;
-float ray_intersect(sampler2D reliefMap, vec2 dp, vec2 ds)
+void QDM(inout vec3 p, inout vec3 v)
{
- float size = 1.0 / float(linear_search_steps);
- float depth = 0.0;
- float best_depth = 1.0;
+ const int MAX_LEVEL = TEXTURE_MIP_LEVELS;
+ const float NODE_COUNT = TEXTURE_PIX_COUNT;
+ const float TEXEL_SPAN_HALF = 1.0 / NODE_COUNT / 2.0;
- for(int i = 0; i < linear_search_steps - 1; ++i)
+ float fDeltaNC = TEXEL_SPAN_HALF * depth_factor;
+
+ vec3 p2 = p;
+ float level = MAX_LEVEL;
+ vec2 dirSign = (sign(v.xy) + 1.0) * 0.5;
+ GlobalIterationCount = 0;
+ float d = 0;
+
+ while (level >= 0 && GlobalIterationCount < gIterationCap)
{
- depth += size;
- float t = step(0.95, texture2D(reliefMap, dp + ds * depth).a);
- if(best_depth > 0.996)
- if(depth >= t)
- best_depth = depth;
- }
- depth = best_depth;
+ vec4 uv = vec4(p2.xyz, level);
+ d = texture2DLod(QDMTex, uv.xy, uv.w).w;
- const int binary_search_steps = 5;
-
- for(int i = 0; i < binary_search_steps; ++i)
- {
- size *= 0.5;
- float t = step(0.95, texture2D(reliefMap, dp + ds * depth).a);
- if(depth >= t)
+ if (d > p2.z)
{
- best_depth = depth;
- depth -= 2.0 * size;
+ //predictive point of ray traversal
+ vec3 tmpP2 = p + v * d;
+
+ //current node count
+ float nodeCount = pow(2.0, (MAX_LEVEL - level));
+ //current and predictive node ID
+ vec4 nodeID = floor(vec4(p2.xy, tmpP2.xy)*nodeCount);
+
+ //check if we are crossing the current cell
+ if (nodeID.x != nodeID.z || nodeID.y != nodeID.w)
+ {
+ //calculate distance to nearest bound
+ vec2 a = p2.xy - p.xy;
+ vec2 p3 = (nodeID.xy + dirSign) / nodeCount;
+ vec2 b = p3.xy - p.xy;
+
+ vec2 dNC = (b.xy * p2.z) / a.xy;
+ //take the nearest cell
+ d = min(d,min(dNC.x, dNC.y))+fDeltaNC;
+
+ level++;
+
+ //use additional convergence speed-up
+ #ifdef USE_QDM_ASCEND_INTERVAL
+ if(frac(level*0.5) > EPSILON)
+ level++;
+ #elseif USE_QDM_ASCEND_CONST
+ level++;
+ #endif
+ }
+ p2 = p + v * d;
}
- depth += size;
+ level--;
+ GlobalIterationCount++;
}
- return(best_depth);
+ //
+ // Manual Bilinear filtering
+ //
+ float rayLength = length(p2.xy - p.xy) + fDeltaNC;
+
+ float dA = p2.z * (rayLength - BILINEAR_SMOOTH_FACTOR * TEXEL_SPAN_HALF) / rayLength;
+ float dB = p2.z * (rayLength + BILINEAR_SMOOTH_FACTOR * TEXEL_SPAN_HALF) / rayLength;
+
+ vec4 p2a = vec4(p + v * dA, 0);
+ vec4 p2b = vec4(p + v * dB, 0);
+ dA = texture2DLod(NormalTex, p2a.xy, p2a.w).w;
+ dB = texture2DLod(NormalTex, p2b.xy, p2b.w).w;
+
+ dA = abs(p2a.z - dA);
+ dB = abs(p2b.z - dB);
+
+ p2 = mix(p2a.xyz, p2b.xyz, dA / (dA + dB));
+
+ p = p2;
+}
+
+float ray_intersect(vec2 dp, vec2 ds)
+{
+ vec3 p = vec3( dp, 0.0 );
+ vec3 v = vec3( ds, 1.0 );
+ QDM( p, v );
+ return p.z;
}
void main (void)
{
- if ( quality_level >= 3.5 ) {
- linear_search_steps = 20;
- }
vec3 ecPos3 = ecPosition.xyz / ecPosition.w;
vec3 V = normalize(ecPos3);
vec3 s = vec3(dot(V, VTangent), dot(V, VBinormal), dot(VNormal, -V));
vec2 ds = s.xy * depth_factor / s.z;
vec2 dp = gl_TexCoord[0].st - ds;
- float d = ray_intersect(NormalTex, dp, ds);
+ float d = ray_intersect(dp, ds);
vec2 uv = dp + ds * d;
vec3 N = texture2D(NormalTex, uv).xyz * 2.0 - 1.0;
@@ -89,7 +147,7 @@ void main (void)
vec3 sl = normalize( vec3( dot( l, VTangent ), dot( l, VBinormal ), dot( -l, VNormal ) ) );
ds = sl.xy * depth_factor / sl.z;
dp -= ds * d;
- float dl = ray_intersect(NormalTex, dp, ds);
+ float dl = ray_intersect(dp, ds);
if ( dl < d - 0.05 )
shadow_factor = dot( constantColor.xyz, vec3( 1.0, 1.0, 1.0 ) ) * 0.25;
}