That version I posted with all source code does not have the FP40 option (I removed that as it was getting slower than FP30 option). But as your new ideas require the FP40 otion I will put it back and repost the demo later tonight.
yeah, use two colour channels for the min and max height, and recalculate that for all lods (store in the mipmaps for example). that way, you can quickly see if you can hit at all over big regions…
would be like a quadtree then.
I have experimented a bit with the NV40 path and got it about 2.5 times as fast as the last version I posted, still without the optimizations I proposed last time. It is now about 210 fps fullscreen on my GeForce 6800 GT compared to 230 fps for the fp30 path. But it does not support RM_DOUBLEDEPTH (code would become a mess) and RM_SHADOWS (had no time yet) at the moment. You should try it anyway just to see how nice it looks 
Since I have no webspace, I will post all of the changed code here (hopefully it is not bad post style). I renamed a lot of variables in the main fragment shader for myself so that I could understand it better (sorry!).
The only structural change is that ray_intersect_rm() now takes the 3d entry point and entry vector in heightmap space and returns the intersection point in heightmap space (because this makes a lot of things easier)
/////////////////
// RELIEF MAPPING
frag2screen main_frag_rm(
// interpolated fragment data
vert2frag IN,
// normalmap + heightmap
uniform sampler2D rmtex:TEXUNIT0, // rm texture map
// color map
uniform sampler2D colortex:TEXUNIT1, // color texture map
// these define heightmap space
uniform float4 axis_pos, // base vertex pos (xyz)
uniform float4 axis_x, // base x axis (xyz:normalized, w:length)
uniform float4 axis_y, // base y axis (xyz:normalized, w:length)
uniform float4 axis_z, // base z axis (xyz:normalized, w:length)
// these are in object space (hopefully)
uniform float4 camerapos, // camera position (xyz)
uniform float4 lightpos, // lightposition (xyz)
// material factor
uniform float4 specular, // specular color (xyz:rgb, w:exponent)
// for depth correction
uniform float4 modelviewprojz, // 3rd column from modelview projection matrix
uniform float4 planes) // near and far plane distances (near,far,near*far,1/(far-near))
{
frag2screen OUT;
// entry position (_Obj is object space, _Hgt is heightmap space)
float4 entryPos_Obj;
float3 entryPos_Hgt;
// entry vector (+normalization)
float3 entryVec_Obj, entryVec_Hgt;
// traced position
float4 tracePos_Obj, tracePos_Hgt;
// light vector
float3 lightVec_Obj;
// remove us
float d,dl;
// *** Ray Intersection ***
// calculate entry position
entryPos_Obj = IN.opos;
entryPos_Hgt = project_uvw(entryPos_Obj.xyz - axis_pos.xyz,axis_x,axis_y,axis_z);
// calculate entry vector
entryVec_Obj = normalize(IN.opos.xyz - camerapos.xyz);
entryVec_Hgt = project_uvw(entryVec_Obj,axis_x,axis_y,axis_z);
// perform raytracing
tracePos_Hgt = ray_intersect_rm(rmtex,entryPos_Hgt,entryVec_Hgt);
tracePos_Obj = tracePos_Hgt.x*axis_x + tracePos_Hgt.y*axis_y + tracePos_Hgt.z*axis_z + axis_pos;
// have we hit the texture?
if (tracePos_Hgt.w > 0)
{
// *** Specular Normal Mapping ***
// get rm and color texture points
float4 normal = f4tex2D(rmtex,tracePos_Hgt.xy);
float3 color = IN.color.xyz*f3tex2D(colortex,tracePos_Hgt.xy);
// expand normal from normal map in local polygon space
normal.xy = normal.xy*2.0 - 1.0;
normal.z = sqrt(1.0 - dot(normal.xy,normal.xy));
normal.xyz = normalize(normal.x*axis_x.xyz + normal.y*axis_y.xyz - normal.z*axis_z.xyz);
// compute diffuse and specular terms
lightVec_Obj = normalize(tracePos_Obj.xyz - lightpos.xyz);
float diff = saturate(dot(-lightVec_Obj,normal.xyz));
float spec = saturate(dot(normalize(-lightVec_Obj - entryVec_Obj),normal.xyz));
// compute final color
OUT.color.xyz = color*diff + specular.xyz*pow(spec,specular.w);
OUT.color.w = 1;
#ifdef RM_DEPTHCORRECT
// *** Depth Correction ***
tracePos_Obj.w = 1;
float depth = dot(-tracePos_Obj,modelviewprojz);
OUT.depth = (planes.z/depth + planes.y)*planes.w;
#endif
} else
OUT.color.w = 0;
return OUT;
}
// RAY INTERSECT DEPTH MAP WITH BINARY SEARCH
// RETURNS INTERSECTION POINT OR (0,0,0,0) ON MISS
float4 ray_intersect_rm(
in sampler2D rmtex,
in float3 entryPos_Hgt,
in float3 castVec_Hgt)
{
float4 res = float4(0, 0, 0, 0);
#ifdef RM_NV40
// *** NV 40 path ***
// currently no support for RM_DOUBLEDEPTH
const float linear_cast_radius = 0.005; // in uv plane (e.g. 0.1 = 10 steps to cross the whole texture)
const int binary_search_steps = 5;
float4 t;
// renormalize cast vector (to uv-length = linear_cast_radius, but smaller if vector would cross the ground plane)
castVec_Hgt = castVec_Hgt *= 1.0/castVec_Hgt.z;
castVec_Hgt *= min(1.0,rsqrt(dot(castVec_Hgt.xy, castVec_Hgt.xy))*linear_cast_radius);
// [ perform linear search ]
do
{
entryPos_Hgt += castVec_Hgt;
t = f4tex2D(rmtex, entryPos_Hgt.xy);
} while (entryPos_Hgt.z < t.w);
// [ perform binary search ]
for(int i=0;i<binary_search_steps;i++)
{
castVec_Hgt *= 0.5;
entryPos_Hgt += castVec_Hgt*sign(t.w - entryPos_Hgt.z);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
}
// store output
if (t.w <= 0.996)
{
res.xyz = entryPos_Hgt;
res.w = 1;
}
#else
// left it out at because of the changed parameters
res.xyz = entryPos_Hgt;
res.w = 1;
#endif
return res;
}
// PROJECT A 3D POINT INTO HEIGHT MAP
float3 project_uvw(in float3 p,in float4 u,in float4 v, in float4 w)
{
return float3(dot(p,u.xyz)/u.w, dot(p,v.xyz)/v.w, dot(p,w.xyz)/w.w);
}
In order to make it work, you must replace the original functions and change the appropriate function declarations.
Oh, and…
The layered speed optimization could look like the following pseudocode. It assumes that the filtered map is in the blue channel and that the filter size is fixed. Unfortunately my Visual Studio is completely messed up atm so I cannot built anything at all (-> no program for doing the filtering from me).
// *** NV 40 path ***
// currently no support for RM_DOUBLEDEPTH
const float linear_cast_radius = 0.005; // in uv plane
const float linear_filter_radius = 0.1; // e.g. 0.1 for a 512x512 map would be 51 pixels radius, same unit as linear_cast_radius
const int binary_search_steps = 5;
float4 t;
// renormalize cast vector
castVec_Hgt *= 1.0/castVec_Hgt.z;
castVec_Slow = castVec_Hgt * min(1.0,rsqrt(dot(castVec_Hgt.xy, castVec_Hgt.xy))*linear_cast_radius);
castVec_Fast = castVec_Hgt * min(1.0,rsqrt(dot(castVec_Hgt.xy, castVec_Hgt.xy))*linear_filter_radius);
// [ perform layered linear search ]
do
{
// walk fast (over the plateaus)
do
{
entryPos_Hgt += castVec_Fast;
t = f4tex2D(rmtex, entryPos_Hgt.xy);
} while (entryPos_Hgt.z < t.z);
// hit plateau -> recover to the last save position
entryPos_Hgt += castVec_Hgt*(t.z-entryPos_Hgt.z);
// OptMe: eliminate binary search if (t.z = t.w) here
// (will be very cool for larger flat areas)
// walk slow (through heightmap)
do
{
entryPos_Hgt += castVec_Slow;
t = f4tex2D(rmtex, entryPos_Hgt.xy);
} while (((entryPos_Hgt.z - t.w) * (entryPos_Hgt.z - t.z)) < 0); // while both comps have different results
} while (entryPos_Hgt.z < t.w);
// [ perform binary search ]
...
The OptMe optimization would make it faster (at least 25%, i guess) than fp30 when looking directly onto the surface, while the layering optimization would make it faster when viewing at sharp angles (could be about the same performance as fp30 then, but without the artifacts)
Thanks for your updates pro_otimizer… I’ve been too busy this week at work and did not have any time to check your new ideas yet.
I will try it out over the weekend together with some new ideas for using it with curved surfaces (generic meshes like a teapot for example).
I will post something here as soon as I have some good results…
OK, I’ve got my VS working again and tested all the mentioned stuff, but I must admit that it does NOT cut the cake. The two additional loops just create too much overhead so that it is even a bit slower than before. I did not expect that the conditional statements are that slow… But having realized this now, I did something totally different which gives up to 100% speed gain especially in the dreaded sharp-angle cases. Simply unroll the linear search loop by some factor and use the conditional operator instead:
// [ perform linear search ]
do
{
entryPos_Hgt += castVec_Hgt;
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
} while (entryPos_Hgt.z < t.w); // while both comps have different results
instead of:
// perform linear search
do
{
entryPos_Hgt += castVec_Hgt;
t = f4tex2D(rmtex, entryPos_Hgt.xy);
} while (entryPos_Hgt.z < t.w);
The same goes for the single if statement after the binary search.
And I have already another idea how to make the step size adaptive (with minimal overhead this time), maybe I implement it later this evening…
Btw, has anyone tested this shader on a GeForce 6800 Ultra?
Hi pro_optimizer… your suggestions are great and work good for the FP40 profile. Excelent work in the optimizations!
Much better fps now and the best thing is that it solves all the artifacts found when looking at sharp angles using the adaptative precision level in linear search based on viewangle and normal.
I have integrated the code to my original demo… please redownload again… includes a FP40 option in render menu now running that code.
We must implement support for self shadows now in FP40 version… the adaptative precision should also help on the shadow edges when light it as sharp angles.
Thanks, this is what I wanted to hear.
The shadowing code should be something like this:
(all you need to do is transform the incidence angle into heightmap space (project_uvw()) and basically subtract it from the traced position in heightmap space to determine the light entry position)
frag2screen main_frag_rm(
// interpolated fragment data
vert2frag IN,
// normalmap + heightmap
uniform sampler2D rmtex:TEXUNIT0, // rm texture map
// color map
uniform sampler2D colortex:TEXUNIT1, // color texture map
// these define heightmap space
uniform float4 axis_pos, // base vertex pos (xyz)
uniform float4 axis_x, // base x axis (xyz:normalized, w:length)
uniform float4 axis_y, // base y axis (xyz:normalized, w:length)
uniform float4 axis_z, // base z axis (xyz:normalized, w:length)
// these are in object space (hopefully)
uniform float4 camerapos, // camera position (xyz)
uniform float4 lightpos, // lightposition (xyz)
// material factor
uniform float4 specular, // specular color (xyz:rgb, w:exponent)
// for depth correction
uniform float4 modelviewprojz, // 3rd column from modelview projection matrix
uniform float4 planes) // near and far plane distances (near,far,near*far,1/(far-near))
{
frag2screen OUT;
// entry position
float4 entryPos_Obj;
float3 entryPos_Hgt;
// entry vector (+normalization)
float3 entryVec_Obj, entryVec_Hgt;
// traced position
float4 tracePos_Obj, tracePos_Hgt;
// light vector
float3 lightVec_Obj, lightVec_Hgt;
// light entry position
float3 lightPos_Hgt;
float shadow = 1.0;
const float ambient=0.2;
const float bias=0.03;
// *** Ray Intersection ***
// calculate entry position
entryPos_Obj = IN.opos;
entryPos_Hgt = project_uvw(entryPos_Obj.xyz - axis_pos.xyz,axis_x,axis_y,axis_z);
// calculate entry vector
entryVec_Obj = normalize(entryPos_Obj.xyz - camerapos.xyz);
entryVec_Hgt = project_uvw(entryVec_Obj,axis_x,axis_y,axis_z);
// perform raytracing
tracePos_Hgt = ray_intersect_rm(rmtex,entryPos_Hgt,entryVec_Hgt);
tracePos_Obj = tracePos_Hgt.x*axis_x + tracePos_Hgt.y*axis_y + tracePos_Hgt.z*axis_z + axis_pos;
// have we hit the texture?
if (tracePos_Hgt.w > 0)
{
// *** Specular Normal Mapping ***
// light vector
lightVec_Obj = normalize(tracePos_Obj.xyz - lightpos.xyz);
// compute diffuse color
float3 color = IN.color.xyz*f3tex2D(colortex,tracePos_Hgt.xy);
// load & expand from normal map
float4 normal = f4tex2D(rmtex,tracePos_Hgt.xy);
normal.xy = normal.xy*2.0 - 1.0;
normal.z = sqrt(1.0 - dot(normal.xy,normal.xy));
normal.xyz = normalize(normal.x*axis_x.xyz + normal.y*axis_y.xyz - normal.z*axis_z.xyz);
#ifdef RM_SHADOWS
// calculate light entry vector
lightVec_Hgt = project_uvw(lightVec_Obj, axis_x, axis_y, axis_z);
// calculate light entry pos
lightPos_Hgt = tracePos_Hgt - lightVec_Hgt*tracePos_Hgt.z/lightVec_Hgt.z;
//perform raytracing
lightPos_Hgt = ray_intersect_rm(rmtex,lightPos_Hgt,lightVec_Hgt);
shadow = (lightPos_Hgt.z < tracePos_Hgt.z-bias)?0:1;
// compute diffuse and specular influence
float diff = shadow*saturate(dot(-lightVec_Obj,normal.xyz));
float temp = saturate(dot(normalize(-lightVec_Obj - entryVec_Obj),normal.xyz));
float3 spec = specular.xyz*pow(shadow*temp,specular.w);
#else
// compute diffuse and specular influence
float diff = saturate(dot(-lightVec_Obj,normal.xyz));
float temp = saturate(dot(normalize(-lightVec_Obj - entryVec_Obj),normal.xyz));
float3 spec = specular.xyz*pow(temp,specular.w);
#endif
// compute final color
OUT.color.xyz = color*diff + spec;
OUT.color.w = 1;
#ifdef RM_DEPTHCORRECT
// *** Depth Correction ***
tracePos_Obj.w = 1;
float depth = dot(-tracePos_Obj,modelviewprojz);
OUT.depth = (planes.z/depth + planes.y)*planes.w;
#endif
} else
OUT.color.w = 0;
return OUT;
}
It works and it has no jagged edges,
but there is a small flaw in it which I could not fix yet (have no internet connection at home): A small shadowed hole sometimes appears where displacement=0).
And the performance varies a lot…
This is because at sharp light incidence angles the raytracer has much more to do while you still need to draw the same amount of fragments. (not like in the view-angle case)
We should try to combine the depth-correct relief mapping with shadow mapping (if the precision is not too lousy), so we can generate soft shadows (with random jittering, you know).
But you are not actualy doing the adaptative linear walk step in that latest code.
Also image looks good because you use 0.005 in linear search step (200 steps maximum). Would be better with variable size based on view direction like you said before.
OK pro_optimizer… last FP40 code was not good (wrong lighting and not that fast as was doing step size 0.005 all time!).
Posted a new version now with correct lighting and adaptative number of steps in linear search based on ray angle with normal.
Also shadows are working ok now in FP40 mode. Looks like the artifacts are gone with FP40 now for color and shadows. Please redownload demo from same url as last post.
Tell me if you get better framerates now in FP40 mode pro_optimizer.
Hi FPO,
as far as I can see, your step size adaption is essentially the same as in pro_optimizer’s code. In his version, it was implicitly done in the renormalization section of ray_intersect_rm.
Since the length of castVec_Hgt is constant only in two dimensions (uv plane), at a small angle, the vector would be very short (approaching linear_cast_radius) --> up to 200 steps to the ground, and at a greater angle (~ 90 degrees), the length of the vector approaches 1.0 (thanks to the usage of min) --> only one step to the ground.
Very cool technique, can we see more height maps or a new video?
Greetings, mbue
Thanks for clearing things mbue. And sorry pro_optimizer for the unfounded comments, but could not get your re-normalization code.
Here is a link for a 3dsmax 6 plugin that generates relief maps. This was the plugin used to create the sample files included in demo.
Just install it, run max and model your object in XY plane. Then from a top view, select all objects to compose the relief map and click the render relief button in utility panel. You can select the resolution and antialiase factor.
hehe, I simply do this with a procedural gradient texture, and set it from black to white as the geometrie get closer to the point of view. Then I set auto-illumination to the max, and render the scene.
(here’s a screenshot using my own opengl displacement routine: http://www.divideconcept.net/d2k4/render/d2k4logo.jpg )
Good video divide! But the parallax mapping should not look that bad I think. Good displace on cube anyway. But is that software rendering?
Please post some demo we could run and check it out. I would like to see a full room all using pixel displace maps like that and some lights. I will get a frind to model something for me soon in that direction…
Actually parallax looks really bad when you want to push the deepness a bit to enhance the displacement. All parallax examples you use to see has very few depth (bricks, misc. patterns, etc…). To fully establish a visual comparaison between parallax mapping and my method, I had to give the same depth to each one. That’s why parallax really looks bad on the comparaison.
First stage of my engine was software rendering, I wanted to establish a prototype if such a thing was possible in realtime. Now I’m moving on the hardware adaptation, and begin to have some nice results.
Don’t worry I’ll post a demo when it will be advanced enough… But for now still optimizing the thing 
Thanks, mbue. Sorry if my comments were a bit brief in the renormalization section.
Fpo, Looking at your code, is seems as if entryVec_Hgt becomes infinitely small when it gets parallel to the texture plane. And castVec_Hgt cannot become greater than 0.1 (due to max(rayangle*0.1, 0.02)), which is unfortunate when you look directly onto the surface. But this is only theoretical since I could not test it at home yet (I will do this tonight and tell you the results tomorrow).
Anyways, good that you got the shadows right, my code was admittedly a bit lame.
Nice work, divide! Your video is quite impressive. Do you use the same basic algorithm as fpo?
If yes, do you have some optimizations which we haven’t found yet? After having tested two very different ways of accelerating it depending on the actual geometry (with preprocessing) with no success, I come to the conclusion that one cannot make it much faster than it is now :-/
Apart from this there are a few things which are still to be solved. One thing is rendering polygons wich show the heightmap from the side or from an arbitrary angle, therefore we must detect if the ray leaves the heightmap (or better the range covered by the top polygon). This becomes hard when texture repeat is on and when we want to render only parts of the heightmap (I am seeing this shader already running on heightmapped characters ;-).
And second, maybe someone finds a way to do this on curved surfaces.
Last thing: The textures+heightmaps which are used in humus’ well-known parallax mapping demo look incredibly real when rendered with relief mapping (especially the floor texture). You should have a look at them!
Found the problem with the lighting in FP40 profile. It was getting darker than all other versions (bump, parallax, fp30).
The problem was when projecting back to object space after the ray intersection. We must multiply by the axis length (w component) as all ray tracing is in normalized [0,1] texture and depth space.
So correct way to do it is:
...
// perform raytracing
tracePos_Hgt = ray_intersect_rm(rmtex,entryPos_Hgt,entryVec_Hgt,rayangle);
tracePos_Obj = axis_pos +
tracePos_Hgt.x*axis_x*axis_x.w +
tracePos_Hgt.y*axis_y*axis_y.w +
tracePos_Hgt.z*axis_z*axis_z.w;
...
I have already updated the demo zip with this fix… just re-download.
Yes pro_optimizer, the original parallax mapping rockwall sample looks great here (and can also tile properly and generate nice shadows).
I had to invert the depth map (as in my shader I consider 0 at the top and 1 at bottom). I pasted the normal and inverted depth maps together and used a depth range of 4% of width. Looks excelent!
Just posted demo again including that new relief map. Hope there is no problem is using that images… enjoy!
Originally posted by pro_optimizer:
Nice work, divide! Your video is quite impressive. Do you use the same basic algorithm as fpo?
If yes, do you have some optimizations which we haven’t found yet? After having tested two very different ways of accelerating it depending on the actual geometry (with preprocessing) with no success, I come to the conclusion that one cannot make it much faster than it is now :-/
I use a different approach to the problem, which use a few preprocessing to achieve the displacement. However I’ll tell more about this when it will be fast enough to run @20fps fullscreen on my fx5200.
Originally posted by pro_optimizer:
Apart from this there are a few things which are still to be solved. One thing is rendering polygons wich show the heightmap from the side or from an arbitrary angle, therefore we must detect if the ray leaves the heightmap (or better the range covered by the top polygon). This becomes hard when texture repeat is on and when we want to render only parts of the heightmap (I am seeing this shader already running on heightmapped characters ;-).
I also thought of theses problems, and I’m gonna implement this after I fully optimized the first step.
However IMO rendering only part of the height map using a mask isn’t a good answer. If we had to create a full head, I would rather think of displacing a cube with each side using a different displacement map. There would be no border effect because of the answer to the first problem (stoping the ray after it exited the volume defined for each polygon).
Originally posted by pro_optimizer:
And second, maybe someone finds a way to do this on curved surfaces.
Yes that’s the point number one to generalize use of displacement mapping…
I think I have some answer to this problem too, which is very close to the problem of stopping the ray outside of his displacement volume.
But not time yet to think about it
Originally posted by pro_optimizer:
Last thing: The textures+heightmaps which are used in humus’ well-known parallax mapping demo look incredibly real when rendered with relief mapping (especially the floor texture). You should have a look at them!
Yeah I know this demo, I was really impressed when I saw it the first time. Parallax is a nice trick to render 3d patterns !
It was obviously a very good decision to generalize the raytracer so far that it only takes the ray direction and a point to start from: What can you do when you have a heightmap and a raytracer if not raytraced reflections?
This is probably the sickest shader you have seen for quite a time, but it is only a slight variation of the original version. It renders the relief map with a reflection depth of 2 (effectively doing 3 raytraces (+3 when you turn shadows on))
Here is the changed fragment shader:
const int bounces = 3;
#ifdef RM_DOUBLEPRECISION
const float view_res = 0.005;
const float shadow_res = 0.01;
#else
const float view_res = 0.0075;
const float shadow_res = 0.015;
#endif
/////////////////
// RELIEF MAPPING
frag2screen main_frag_rm(
// interpolated fragment data
vert2frag IN,
// normalmap + heightmap
uniform sampler2D rmtex:TEXUNIT0, // rm texture map
// color map
uniform sampler2D colortex:TEXUNIT1, // color texture map
// these define heightmap space (we should pass this as matrix plus its inverse transpose to save the costly project_uvw()s)
uniform float4 axis_pos, // base vertex pos (xyz)
uniform float4 axis_x, // base x axis (xyz:normalized, w:length)
uniform float4 axis_y, // base y axis (xyz:normalized, w:length)
uniform float4 axis_z, // base z axis (xyz:normalized, w:length)
// these are in object space (hopefully)
uniform float4 camerapos, // camera position (xyz)
uniform float4 lightpos, // lightposition (xyz)
// material factor
uniform float4 specular, // specular color (xyz:rgb, w:exponent)
// for depth correction
uniform float4 modelviewprojz, // 3rd column from modelview projection matrix
uniform float4 planes) // near and far plane distances (near,far,near*far,1/(far-near))
{
frag2screen OUT;
// entry position (_Obj is object space, _Hgt is heightmap space)
float4 entryPos_Obj;
float3 entryPos_Hgt;
// entry vector (+normalization)
float3 entryVec_Obj, entryVec_Hgt;
// traced position
float4 tracePos_Obj, tracePos_Hgt;
// light vector
float3 lightVec_Obj, lightVec_Hgt;
// light position
float3 lightPos_Hgt;
// surface normal (NEW)
float3 normal_Hgt, normal_Obj;
// summed color for this fragment (NEW)
float4 finalColor = (0,0,0,0);
const float shadow_threshold=0.02;
const float shadow_intensity=0.4;
// current ray-trace bounce (NEW)
float bounce = 1;
// *** Ray Intersection ***
// calculate entry position
entryPos_Obj = IN.opos;
entryPos_Hgt = project_uvw(entryPos_Obj.xyz - axis_pos.xyz,axis_x,axis_y,axis_z);
// calculate entry vector
entryVec_Obj = normalize(IN.opos.xyz - camerapos.xyz);
entryVec_Hgt = project_uvw(entryVec_Obj,axis_x,axis_y,axis_z);
// manually unrolling this would be faster unless someone hints "-unroll all" or so to the compiler
for (int i=0;i<bounces;i++)
{
// perform raytracing
tracePos_Hgt = ray_intersect_rm(rmtex,entryPos_Hgt,entryVec_Hgt, view_res);
tracePos_Obj = tracePos_Hgt.x*axis_x*axis_x.w + tracePos_Hgt.y*axis_y*axis_y.w + tracePos_Hgt.z*axis_z*axis_z.w + axis_pos;
// have we hit the texture?
if (tracePos_Hgt.w > 0)
{
// *** Specular Normal Mapping ***
// light vector
lightVec_Obj = normalize(tracePos_Obj.xyz - lightpos.xyz);
// compute diffuse color
float3 color = IN.color.xyz*f3tex2D(colortex,tracePos_Hgt.xy);
// load & expand from normal map
normal_Hgt = f3tex2D(rmtex,tracePos_Hgt.xy)*2 - 1;
normal_Hgt.z = sqrt(1.0 - dot(normal_Hgt.xy,normal_Hgt.xy));
normal_Obj = normal_Hgt.x*axis_x.xyz + normal_Hgt.y*axis_y.xyz - normal_Hgt.z*axis_z.xyz; // this will be wrong for an unnormalized texture matrix
#ifdef RM_SHADOWS
// calculate light entry vector
lightVec_Hgt = project_uvw(lightVec_Obj, axis_x, axis_y, axis_z);
// calculate light entry pos
lightPos_Hgt = tracePos_Hgt.xyz - lightVec_Hgt*tracePos_Hgt.z/lightVec_Hgt.z;
// perform shadow raytracing
float4 shadowhit = ray_intersect_rm(rmtex,lightPos_Hgt,lightVec_Hgt,shadow_res);
shadowhit.w = shadowhit.z<tracePos_Hgt.z-shadow_threshold?shadow_intensity:1.0;
color *= shadowhit.w;
specular *= shadowhit.w>0.998?1.0:0.0;
#endif
// compute diffuse and specular influence
float diff = saturate(dot(-lightVec_Obj,normal_Obj));
float temp = saturate(dot(normalize(-lightVec_Obj - entryVec_Obj),normal_Obj));
float3 spec = specular.xyz*pow(temp,specular.w);
// compute final color (NEW)
finalColor.xyz += (1/bounce)*(color*diff + spec);
finalColor.w = 1;
// prepare for reflection (NEW)
entryPos_Hgt = tracePos_Hgt;
entryVec_Obj = reflect(entryVec_Obj, normal_Obj);
entryVec_Hgt = project_uvw(entryVec_Obj,axis_x,axis_y,axis_z);
};
bounce++;
}
OUT.color = finalColor; // writing to this register is relatively slow
#ifdef RM_DEPTHCORRECT
// *** Depth Correction ***
tracePos_Obj.w = 1;
float depth = dot(-tracePos_Obj,modelviewprojz);
OUT.depth = (planes.z/depth + planes.y)*planes.w;
#endif
return OUT;
}
And the raytracer now takes an additional quality parameter (so we don’t need to run it with highest perecision in every case):
// RAY INTERSECT DEPTH MAP WITH BINARY SEARCH
// RETURNS INTERSECTION POINT OR (0,0,0,0) ON MISS
float4 ray_intersect_rm(
in sampler2D rmtex,
in float3 entryPos_Hgt,
in float3 castVec_Hgt, in float linear_cast_radius)
{
float4 t, res = float4(0, 0, 0, 0);
// after this, cast vector reaches from polygon to ground plane
castVec_Hgt *= 1.0/abs(castVec_Hgt.z);
// after this, it has a length of linear_cast_radius IN THE UV-PLANE, which can be quit long in 3d
// but it won't become (due to min(0.2, ...)) longer than 1/5 (linear search unroll factor) of the poly->ground vector
castVec_Hgt *= min(0.2,rsqrt(dot(castVec_Hgt.xy, castVec_Hgt.xy))*linear_cast_radius);
// [ perform linear search ]
do
{
entryPos_Hgt += castVec_Hgt;
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
entryPos_Hgt += (entryPos_Hgt.z >= t.w?0:castVec_Hgt);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
// uv-clipping (so no infinitely long surface-parallel rays occur)
//t.w = (clamp(entryPos_Hgt.x,0, 1) != entryPos_Hgt.x?-1:t.w);
//t.w = (clamp(entryPos_Hgt.y,0, 1) != entryPos_Hgt.y?-1:t.w);
// needed for upwards facing cast vector (e.g. after reflection or when using an arbitrary heightmap matrix)
t.w = (entryPos_Hgt.z<0?-1:t.w);
entryPos_Hgt.z = max(entryPos_Hgt.z, -1);
} while (entryPos_Hgt.z < t.w);
if (t.w > -1)
{
// [ perform binary search, manually unrolled this time ]
castVec_Hgt *= 0.5;
entryPos_Hgt += castVec_Hgt*sign(t.w - entryPos_Hgt.z);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
castVec_Hgt *= 0.5;
entryPos_Hgt += castVec_Hgt*sign(t.w - entryPos_Hgt.z);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
castVec_Hgt *= 0.5;
entryPos_Hgt += castVec_Hgt*sign(t.w - entryPos_Hgt.z);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
castVec_Hgt *= 0.5;
entryPos_Hgt += castVec_Hgt*sign(t.w - entryPos_Hgt.z);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
castVec_Hgt *= 0.5;
entryPos_Hgt += castVec_Hgt*sign(t.w - entryPos_Hgt.z);
t = f4tex2D(rmtex, entryPos_Hgt.xy);
} else t.w = 1.0;
// store output
res.w = (t.w <= 0.996?1:0);
res.xyz = entryPos_Hgt;
return res;
}
It has a slightly improved bounds checking in it (for arbitrary rays). And it is also a bit faster because the compiler did not unroll the binary search loop, so I did it manually.
The absolutely best map for this is tile1.rm. When you want to render an open map (like angel.rm) and have texture clamping enabled, you need to uncomment the uv-clipping in the raytracer. Otherwise it will be very slow.
If you have an idea how to make this faster or more beatiful, please let me know.