Dark Bit Factory & Gravity
PROGRAMMING => Freebasic => Topic started by: ninogenio on May 05, 2013
-
hey folks another boring sunday == some more code ;D
ive coded some grayscale bumpmapping. now i remember seeing this done years ago but heres my take on it.. google was a poor source for info so i had too wing it.. im not 100% its correct so would be very greatfull if those with the knowledge could give some pointers.
cheers guys.
very slow atm just want it too work properly first.
(-edit Removed exe too keep forum clean as they were rather large check further down )
-
think ive got it :)
i noticed while tinkering that rgb channels of the normalmap = x y z direction vectors for light reflections i was thinking of them in color space not vector. so a dot product later and here we go. if you guys see anything wrong with it please let me know so i can mess around and learn. cheers..
(-Edit removed exe too keep forum clean as it was rather large check further down )
-
Nice one, Nino.
The pure 2d bump usually just offsets the light-texture coordinate according to the normal (that's how it's done here (http://www.youtube.com/watch?v=OEWZG7eYYMU&t=0m48s)).
The light-map was made big enough that you didn't have to care about clipping, so you don't have to recalculate it every frame.
for y= ...
for x= ...
col= colorMap[y][x]
normal= normalMap[y][x]
nx= normal shr 16 and 255
ny= normal shr 8 and 255
light= lightMap[y+ny+lightPosY][x+nx+lightPosX]
dst[y][x]= blend(col, light)
If you really want to work in 3d, you can handle diffuse and reflection separately, though.
-
what an elegant and cheep way of getting the effect. thanks very much hellfire..
im wanting to do specular and diffuse seprate though manly for learning. ive got the diffuse working properly it cycles through diffrent colors.. just wondering how specular works i would like it too look kind of wet like like so many modern games..
its manly the part where you calculate angle between eye and normal vector that i get lost.
ive got an idea but everytime i try and put it down it doesnt go well.
(-Edit removed exe too keep forum clean as it was rather large check further down )
-
im wanting to do specular and diffuse seprate though manly for learning.
its manly the part where you calculate angle between eye and normal vector that i get lost.
Let's say your bitmap is on a plane in 3d space, eg. from (-320,-240,0) - (320,240,0).
The camera is in the center, somewhere above, eg. at (0,0,50).
And there's a position of the light-source somewhere between the plane and the camera.
For each pixel you've got a normal vector (stored in the normal map), a view direction (vector from the camera to the pixel) and a light direction (vector from the light source to the pixel).
You've already figured out the diffuse lighting which is
diffuse= dot3(lightdir, normal)
For the specular term you reflect (http://mathworld.wolfram.com/Reflection.html) the view direction at the normal:
reflectdir= viewDir - normal * 2 * dot3(normal, viewDir);
(viewDir and normal must be normalized, so it might be cheaper to use blinn's half angle apprroximation instead)
Check if it's facing the light source and use some glossiness function:
specular= dot3(reflectdir, lightdir)
if (specular > 0.0) specular= pow(specular, 40.0)
The final color is something like
textureColor * diffuse * lightColor + specular * specularColor
-
wow thats awsome mate k++ i think ive cracked it and finally understand specular lighting!!
your explenation of how it all goes together into the final color helped hugely. does this look about right.
(-Edit removed exe too keep forum clean as it was rather large check further down )
-
Nice!
Looks like the pixels which are facing away from the light-source are now getting most of the specular term, though.
So I think your reflection vector is probably negated.
It's also useful to have another map (or abuse the alpha-channel of the color- or normal-map) to store a "specular level".
This way you can give less specular to the dirty parts between the stones.
-
yeah your right Mate it wasnt right.
i slowed down a little and really went over all you wrote and im 72.594% sure i finaly have it :).
i changed the bump map image too clearly show the specular term working. im intrested in what you wrote about packing a specular level into the alpha channel too stop incorrect lighting going on would it be possible for you too elaborate a little further?
thanks for all the help so far. there's no way i would have gotten this far without.
(-Edit removed exe too keep forum clean as it was rather large check further down )
-
Looks great nino! Never coded bumps myself but use them extensively in my 3d work. Now I can't wait to give it a try as well, interesting thread. :)
-
Thanks kirl!
yeah thats pretty much the same as me i use them a lot without even thinking how they work. my aim over this next while is too try and get behind lots of stuff i have seen and try and understand how they work.
glad you like the thread. maybe if you find anything on your bumpmapping quest that isnt covered you can share. that would be awsome.
in this version i have tried height mapping if im correct height mapping just makes a grey scale copy of the original image and each grey level represents a height vector. this height vector then gets normalised and mixed in with the z normals to extend each normal by heightmap factor. does that sound right i am getting a nice effect but maybe not to the extent i had hoped for.
(-Edit removed exe too keep forum clean as it was rather large check further down )
-
im 72.594% sure i finaly have it :)
Looks pretty cool but this piece is certainly wrong because you're just using the normal vector to calculate the reflection:
SpecDot = ( Nx * -Nx + -Ny * Ny + Nz * -Nz )
Rx = (-Nx)-Nx*2.0*SpecDot
Ry = (-Ny)-Ny*2.0*SpecDot
Rz = (-Nz)-Nz*2.0*SpecDot
-
woops..
it should be
specDot( pixellocation(-320 to 320)*Nx.... etc )
Rx = PixelLocation( -320 to 320 )-Nx*2.0*SpecDot
etc..
i was looking at some half angle stuff and got my wires extremely mixed up i hope ive got it close now. it looks much better at least.
(- Edit changed a few bits again added grey scale height mapp etc.modded exe attached )
-
im intrested in what you wrote about packing a specular level into the alpha channel too stop incorrect lighting going on would it be possible for you too elaborate a little further?
In Reply #4 I suggested a exponential function for the specular term:
if (specular > 0.0) specular= pow(specular, 40.0)The exponent is supposed to simulate the reflection of a light source.
A higher exponent creates a smaller highlight and makes the material appear more shiny (for example see here (http://udn.epicgames.com/Three/rsrc/Three/MaterialExamples/ex_shiny_specular.jpg)).
Now your surface might not be equally shiny everywhere, eg. imagine some rusty spots on a metal plane (like this (http://matrep.parastudios.de/mats/fullsize/463d74a6d4847.jpg)).
One way to do this is to have a separate (grey-scale) map to define the brightness of the specular term, like this:
if (specular > 0.0) specular= pow(specular, 40.0) * specularLevel[pixelPosition]
Another way is to store the specular exponent in the map:
if (specular > 0.0) specular= pow(specular, specularLevel[pixelPosition])
Since the exponential function is somewhat expensive, you'd probably use the first version in a software renderer and pick the pow-function with a constant exponent from a lookup table.
For the color- and normal-maps you're typically working with 32bit rgba-colors and the alpha-channel is often unused.
And as the specular-map just contains a single scalar value for each pixel, it can be stored in the alpha-component of one of the other maps to avoid another texture fetch.
-
Great stuff ninogenio :) And a ton of really insightful stuff from Hellfire too! Double win :)
K++
-
cheers mate! :)
@hellfire thanks for picking that up freebasic didnt have a pow func and i wasnt quite sure how that part should go ive replaced it with this SpecDot = (Exp(10*log(SpecDot)))*Height
ive added grey scale height mapping and also got rid of the lightmap as with the specular term there was no need so its running much faster ive stripped out a lot of the fudgy stuff as it was quite hard too read which resulted in me finding a few bugs.
-
i've replaced it with this Exp(10*log(SpecDot))
Since floating point numbers are represented as x*2^exp, pow2 and log2 can be approximated by fiddling with the exponent-bits within the float.
And because
pow(x, exp) = pow2(exp * log2(x))you can do a *very* rough approximation with:
const float inv23= 1.0f / (1 << 23);
const float bias= 126.94269504f;
// approximate log2(x)
int *ip= (int*)&x; // cast x to integer
float y = ip[0];
y= y * inv23 - bias;
e*=y;
// approximate 2.0^e
int i= (1 << 23) * (e + bias);
float *fp= (float*)&i; // cast i to float
float result= fp[0];
Be aware that you can be off by a factor of 2 but for a shading function that usually doesn't really matter.
And if you're limiting this to positive integer exponents, it gets quite another bit tighter.
-
thanks very much hellfire k++, some of this stuff you post is pure gold. using your pow function the specular light now bends round certain surfaces and does a bit of scattering. in my mind it behaves in a quite realistic manner now.
-
hey again.
is this sort of specular term only possible when viewing from different angles and distances.
http://www.keithlantz.net/2011/10/tangent-space-normal-mapping-with-glsl/
the specular i have now is just a bit too overpowering but everything i try to get it more speckled and scattered just fades the whole specular term out. im even yousing the same texture and normals as the link posted.
-
If you're looking from (almost) the same angle as the light source hits the surface, diffuse and specular are on the same spot and hard to distinguish.
A typically specular-only setting is the sun going down over the ocean (example (http://briefhiatus.files.wordpress.com/2008/09/sun-over-the-ocean.jpg)) as the diffuse term gets almost zero but the light reflects towards the viewer.
-
just had a big break through with this it just all come together! looks like glass now :).
thanks hellfire mate!! might have a go at mapping this onto a cube and see what it looks like in 3d space with a dynamic camera proper light etc.
-
:clap:
-
cheers mate! :)
right last version i promise lol.. ive made look up tables for almost everything and doing lots of precalcs also split the bump map function into two parts and running them on seperate threads. only down side is, it will run really really bad on single core cpus now.
the fps on this system shot up from around 15-20 to 62-66.. still to change all the array accesses too ptr addition and figure out how too pre calc pow. i should be able too hit the golden 70fps mark. Doing this much calcs, 307200 times a frame. im amazed at the speed so far. it just might be possible to make a little 3d demo with a few bump textures at 128*128 or something at reasonable speed.
could any one who tries this tell me there cpu and fps please.
ps to see fps run in windowed mode and look at the black box.
cheers.
-
33 fps on Intel Core2 Quad 2.8GHz.
Feels much faster than the previous versions.
And my magic crystal ball foretells twice the speed if you kick the floating point stuff out of the main loop and use only integers :)
-
cheers helfire.
yep your right mate its bottle necked at the fpu atm.
ill confese fixed point scares me a bit. its been years since i did any. if im right you shift all ints and floats left by 8 bits, mulling the floats. add subtract multiply them together then shift right at the end again to clip off the fraction and bring everything back in range.
-
you shift all ints and floats left by 8 bits,
add subtract multiply them together
then shift right at the end again
Exactly.
Those values which contain integers anyway won't need any additional bits, though.
For the rest you usually have to check, how much precision is actually required.
The trick is to keep track of the number of fractional bits.
For example if you multiply to values with 8bits of fractional part, the result has 16bits fraction - so you need to shr8 to get back to 8bits of precision.
For the diffuse part I would quantize the vectors to 8bits (so a normalized vector ranges from -255..+255):
DiffDot = ( Nx * Lx + Ny * Ly + Nz * Lz ) shr 8;So you get an 8bit (0..255) value to shade your texture color, which fits nicely into mmx.
For the specular part you might need more bits because the exponent keeps only a small piece of the range (eg. 0.8 - 1.0), all the rest is black (below 1/255) anyway.
And the integer value makes it much easier to pick the pow-function from a table.
Another thing that makes your code slower at the moment is that you precalculated everything into double-arrays, which increases your memory bandwidth by a factor of 8.
I had a look at your code and noticed that this part:
VdirX = (PixyX) * ReciOX
VdirX = CamX-VdirX
VdirY = (PixyY) * ReciOY
VdirY = CamY-VdirY
VdirZ = TextureZ - CamZ
SpecDot = ( Nx*VDirX + Ny*VDirY + Nz*VDirZ )
Rx = VDirX-Nx*2.0*SpecDot
Ry = VDirY-Ny*2.0*SpecDot
Rz = VDirZ-Nz*2.0*SpecDot...is constant for each pixel and can be precalculated just as the normal map.
-
cheers hellfire,
and thanks for looking at the code. excellent spot with the static veiwport and texture optimization, im wanting too try this dynamically though as it will be mapped onto a cube at some point so will be moving around.
well a fun night tonight i brushed off the fixed point cobwebs and after about 2 hours the whole thing is integerized with ptr addition included also.. the only part that i cant get my head around fixed pointing is the pow function. also there is loads of shifts in there now so that will slow things down a bit.
i had too use ten point shifts for precision as i was loosing too much specular term. even the diffuse part was suffering at eight.
Im Chuffed so far. at this point im up by about 5 fps at my end but im sure ive opened up loads of optimisation options now.
i just cant see them at this point too many shifts and a few rounding errors does that though :)
oh and thanks for the quantize on the diffuse suggestion ill give that a bash next as i think it could get rid of a few calculations k+
-
This version runs at ~50fps on my machine, that's almost twice as fast as the previous one.
the only part that i cant get my head around fixed pointing is the pow function.
That's actually super easy:
If both vectors (Rx,Ry,Rz) and (Lx,Ly,Lz) are normalized to 10 bits, SpecDot is an integer in the range -1023..+1023.
Since you're only interested in positive values and (with an exponent of 40) all values <800 are zero anyway, you can look up the pow-function from a really small table.
But I noticed that your view direction vector (VDirX,VDirY,VDirZ) is not normalized (and I'm a bit surprised that it still works so well), so you have to be a bit careful with the actual numeric range of the dot-products.
And code like this:
Rx = VDirX-Nx*2*SpecDot
Ry = VDirY-Ny*2*SpecDot
Rz = VDirZ-Nz*2*SpecDot...is predestinated for mmx, you just have to make sure that input and output fits into signed 16bit values.
If you extend your vectors to have a 4th coordinate (which just stays 0), it's much easier to load data into mmx registers.
-
excellent thanks hellfire,
im trying too pull specdot into the range of -1 1 for a precalced pow, normalizing Vdir and light Vectors but there must be something off some where else because i always get around 1.4 -1.4. unless i normalize the reflection vector, light vector and Vdir then i get -1 1. just for clarity Can the light vector Be unormalized until after the diffuse angle is worked out?.
im surprised the extra sqrts dont hamper performance too much.
how would i go about making the reflection vector fit into a 16 bit number? that would mean only 4 bits of precision do you think this would be enough.
i was having a little look here..
http://www.dbfinteractive.com/forum/index.php?topic=1726.msg26106#msg26106
i see what you mean about padding 4Dvectors with w being 0, it seems like the lesser of 2 evils.
-
im trying too pull specdot into the range of -1 1 for a precalced pow, normalizing Vdir and light Vectors but there must be something off some where else because i always get around 1.4 -1.4. unless i normalize the reflection vector, light vector and Vdir then i get -1 1.
the dot-product of two vectors v1 and v2 (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z) is in the range -1..+1 only if the length of both vectors is 1.0.
If that's not the case, the diffuse term is calculated by
dot= (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)
diffuse= dot / ( length(v1) * length(v2) )To avoid the sqaure root associated with calculating the length, one tries to have both vectors *almost* normalized beforehand, so that length(v1) * length(v2) becomes ~1.0 and can be skipped.
Nobody will notice if the length of your vectors is off by a few percent, so a very rough approximation for 1/sqrt is totally sufficent (for floating point values the fast inverse square root (http://en.wikipedia.org/wiki/Fast_inverse_square_root) function is popular).
On the other hand, why bother with normalization if your shading function gives good results with unnormalized vectors?
All that happens is that your dot products deliver somewhat larger (or smaller) values. So if you want to use it for a lookup-table, your array must be somewhat larger...
-
To avoid the sqaure root associated with calculating the length, one tries to have both vectors *almost* normalized beforehand, so that length(v1) * length(v2) becomes ~1.0 and can be skipped.
Nobody will notice if the length of your vectors is off by a few percent
im glad you said that as thats exactly what i was hoping for originally by reciprocally dividing each of the vectors elements by there know length. im still struggling with a lookup table for pow as it is my specdot is producing numbers in the range of 4 -4 so i thought great just multiply everything you wrote by a factor of 4 giving a lookup table of just over 800 elements. but i was wrong :).
what i have noticed though is using the look up table even though the specular term isnt working correctly the fps has shot up too 110ish so this is going too be a big optimization i think.
-
well after lots and lots of tinkering i think i have the whole thing completely working. on ints with pow arrayed im getting around 91 fps now. and its still decent quality.
-
Nice work, Nino!
Runs at about 85fps here.
This got me all a bit curious and I wanted to see how fast I could get it myself.
So I started from scratch with a glsl shader as it's much easier to figure out the math, check the required precisions and try different variations.
That code looks like this and runs at >5000 fps in 640x480 on my gtx560-ti:
uniform sampler2D colorTex; // color texture
uniform sampler2D normalTex; // normal texture
uniform vec3 lightPos; // light position
uniform vec4 lightColor; // light color (1.0, 0.4, 0.4, 1.0);
uniform vec4 specColor; // specular color (1.0, 0.9, 0.9, 1.0)
varying vec2 fragPos; // 2d pixel position (0..1, 0..1)
varying vec2 uv; // 2d texture coordinate
void main()
{
vec4 result= vec4(0.0, 0.0, 0.0, 0.0);
// get color and normal from textures
vec4 color= texture2D(colorTex, uv);
vec3 normal= texture2D(normalTex, uv).xyz * 2.0 - 1.0;
// make sure normal is actually normalized
normal= normalize( normal );
// get height from alpha channel of color map
float height= 2.0 - color.a;
// 3d fragment position
vec3 pos= vec3(fragPos, height);
// camera (0,0,0) to pixel
vec3 viewDir= normalize(pos);
// light to pixel
vec3 lightDir= normalize(pos - lightPos);
vec3 refl= normal*2.0*dot(normal, viewDir) - viewDir;
// distance attenuation (1/dist^2) * 2.0 (disabled)
// float dist= 2.0 / length(lightPos - pos);
// diffuse term
float diffuse= dot(normal, lightDir);
if (diffuse > 0.0)
result= color * lightColor * diffuse; // * dist;
// specular term
float specular= dot(refl, lightDir);
if (specular > 0.8)
result+= specColor * pow( specular, 20.0 );
// store color
gl_FragColor= result;
}
So I started to write this down in C, using 4d 16bit integer vectors to give the compiler a clue to use mmx and replaced the normalization- and pow-part with a lookup table.
That version ran at about 70fps using no multithreading. Looking at the disassembly it actually used mmx vectorization but wasn't very clever at it.
So I successively replaced all parts of the inner-loop with hand-crafted mmx blocks which got me at around 85fps and there's probably a good chance to make that a few percent faster.
Finally I added multi-core support to make it use all my cpu-cores and got at about >200fps (exe attached).
I must admit that it uses all my 4 cores to the max while your version only uses ~50%.
The c code I started from looks like this:
typedef struct
{
short x,y,z,w;
} ShortVector4;
void drawBump2d_c(
unsigned int *dst, // destination buffer
unsigned int *src, // interleaved color/normal data per pixel
int width, // width of buffer
int height, // height of buffer
ShortVector4 lightPos, // light position
ShortVector4 camera, // camera position (w/2, h/2, 0, 0)
ShortVector4 lightColor, // light color
ShortVector4 specColor // specular color
)
{
// run scanlines in parallel:
#pragma omp parallel for
for (int y=0; y<height; y++)
{
scanlineBump2d_c(
dst + y * width,
src + y * width * 2,
width,
y,
lightPos,
lightColor,
specColor,
camera
);
}
}
void scanlineBump2d_c(
unsigned int* dst,
unsigned int* src,
int width,
int y,
ShortVector4 lightPos,
ShortVector4 lightColor,
ShortVector4 specColor,
ShortVector4 camera)
{
ShortVector4 pos;
ShortVector4 norm;
ShortVector4 viewdir;
ShortVector4 refl;
ShortVector4 col;
ShortVector4 lightdir;
pos.x= 0;
pos.y= y;
pos.z= 0;
pos.w= 0;
for (int x=0; x<width; x++)
{
// start with black color
col.x= 0;
col.y= 0;
col.z= 0;
col.w= 0;
unsigned int pixelColor= src[0];
pos.z= (pixelColor >> 24 & 255); // height stored in alpha
// pos is the pixel's 3d coordinate
// normal vector in 7bit fractional
unsigned int nrm= src[1];
norm.x= (nrm & 255) - 128;
norm.y= (nrm >> 8 & 255) - 128;
norm.z= (nrm >> 16 & 255) - 128;
norm.w= (nrm >> 24 & 255) - 128;
// light direction
lightdir.x= pos.x - lightPos.x;
lightdir.y= pos.y - lightPos.y;
lightdir.z= pos.z - lightPos.z;
lightdir.w= pos.w - lightPos.w;
// normalize
int t;
short inv;
t= lightdir.x*lightdir.x + lightdir.y*lightdir.y + lightdir.z*lightdir.z + lightdir.w*lightdir.w;
inv= invSqrt[t>>10];
// rescale this to end up with 16bit of fraction to match mmx' pmulhw
lightdir.x= (lightdir.x<<3)*inv>>16;
lightdir.y= (lightdir.y<<3)*inv>>16;
lightdir.z= (lightdir.z<<3)*inv>>16;
lightdir.w= (lightdir.w<<3)*inv>>16;
// calculate diffuse term - result: -16383..+16383
int diffuse= norm.x*lightdir.x + norm.y*lightdir.y + norm.z*lightdir.z + norm.w*lightdir.w;
if (diffuse > 0)
{
diffuse= diffuse>>5;
col.x+= (pixelColor >> 0 & 255) * lightColor.x * diffuse >> 16;
col.y+= (pixelColor >> 8 & 255) * lightColor.y * diffuse >> 16;
col.z+= (pixelColor >> 16 & 255) * lightColor.z * diffuse >> 16;
col.w+= (pixelColor >> 24 & 255) * lightColor.w * diffuse >> 16;
}
// view direction vector: camera -> pixel
viewdir.x= pos.x - camera.x;
viewdir.y= pos.y - camera.y;
viewdir.z= pos.z - camera.z;
viewdir.w= pos.w - camera.w;
// normalize
t= viewdir.x*viewdir.x + viewdir.y*viewdir.y + viewdir.z*viewdir.z + viewdir.w*viewdir.w;
inv= invSqrt[t>>10];
viewdir.x= (viewdir.x<<3)*inv>>16;
viewdir.y= (viewdir.y<<3)*inv>>16;
viewdir.z= (viewdir.z<<3)*inv>>16;
viewdir.w= (viewdir.w<<3)*inv>>16;
// reflection vector
t= (norm.x*viewdir.x + norm.y*viewdir.y + norm.z*viewdir.z + norm.w*viewdir.w);
refl.x= ((norm.x<<3) * t >> 16) - viewdir.x;
refl.y= ((norm.y<<3) * t >> 16) - viewdir.y;
refl.z= ((norm.z<<3) * t >> 16) - viewdir.z;
refl.w= ((norm.w<<3) * t >> 16) - viewdir.w;
// specular term. result: -16383..+16383
int specular= refl.x*lightdir.x + refl.y*lightdir.y + refl.z*lightdir.z + refl.w*lightdir.w;
if (specular > 12288) // 16383 * 0.75 -> pow(0.75, 2.0) < 1/255
{
specular= specular >> 7;
unsigned int s= powTable[specular] & 255;
col.x+= (s * specColor.x >> 8);
col.y+= (s * specColor.y >> 8);
col.z+= (s * specColor.z >> 8);
col.w+= (s * specColor.w >> 8);
}
// saturate
if (col.x>255) col.x=255;
if (col.y>255) col.y=255;
if (col.z>255) col.z=255;
if (col.w>255) col.w=255;
*dst= (col.z<<16)|(col.y<<8)|col.x;
dst++;
src+=2;
pos.x++;
}
}
The two tables look like this:
int invSqrt[65536]; // way too much
unsigned int powTable[2048];
for (int i=0; i<2048; i++)
{
double p= pow(i/128.0, 20.0)*2.0;
if (p<0.0) p=0.0;
if (p>1.0) p=1.0;
int v= p*255.0;
powTable[i]= (v<<24)|(v<<16)|(v<<8)|v;
}
for (int i=0; i<65536; i++)
{
double t= 32767.0 * 32.0 / sqrt(i*1024.0);
if (t > 0x7fff) t= 0x7fff;
invSqrt[i]= (int)t;
}
As I don't want to kill the suspense I'm not going to add the mmx code for now ;)
-
amazing :clap: best sunday morning ever :D!!!
so you managed to get up too 85fps in your c code using only 1 core, then when you spread the work load across all cores you got >200, im getting 336fps at my end core i7 3.2 8).
i honestly never imagined this could be made that quick. and your specular and diffuse terms look lovely!!
what value do you hold in light w? is the norm.w Packed with your grey level height map, and integrated into the diffuse term dot product, as your diffuse term is much more prominent than mine.
just noticed you were able too make a look up table for normalization that is amazing. it was annoying me that mine wasn't correct but i couldn't afford the extra sqrt's. i would never have thought of your solution.
ill take a little while to digest all your code, already i can see lots of areas i can improve mine k++
-
what value do you hold in light w?
i'm on my mobile, so just a short reply:
all w components are zero and are just there to make the compiler use a single vector instruction on the whole 4 values.
otherwise it tries to mask the 4th component away and ends up slower...
i just use alpha of the colormap to store 255-grey.
i also renormalize the normalmap after loading because it didn't really fit.
have to look up the z components of the light and camera when i'm back home.
-
thanks mate, no problem.
i just noticed my threading isnt working properly your example makes my cpu run at 100% full wack. i just tried to splice mine into 4 sections and 4 core it. my fps went too 136 but with only 36% cpu usage im guessing its because im not doing my multi threading on a scanline by scanline basis as you do, and as a result for whatever reason a lot of the time my code makes the cpu sit idle.
-
The rest of the parameters are:
w= width of bitmap (640)
h= height of bitmap (480)
time= time in seconds
lightPos.xyzw= ( (sin(time*1.6)+1)*w/2, (cos(time*1.8)+1)*h/2, 50, 0)
cameraPos.xyzw= (w/2, h/2, 0, 0)
lightColor.xyzw= (127, 127, 255, 0) // blue, green, red, alpha
specColor.xyzw= (255, 192, 192, 0) // blue, green, red, alpha
And the two textures are modified the following way:
// "height" into alpha channel:
color[i].w= 255 - ((color[i].x*30 + color[i].y*150 + color[i].z*76) >> 8);
// renormalize
int t= 32767 / sqrt( normal[i].x*normal[i].x + normal[i].y*normal[i].y + normal[i].z*normal[i].z);
normal[i].x= -(normal[i].x * t >> 8); // negated!
normal[i].y= (normal[i].y * t >> 8);
normal[i].z= (normal[i].z * t >> 8);
the color/normal-buffers got interleaved into a separate buffer, so i can read both with a single movq and save an adress register.
As all vectors are normalized to signed 8bits (-128..+127) and the normalization-precision is very rough, you can see some quantization noise in the shading (which is actually good, if it wasn't there you'd see color banding) which could be removed by using more bits of the available range - but it's probably getting a bit trickier with mmx then...
im not doing my multi threading on a scanline by scanline basis as you do
open-mp doesn't schedule one job per scanline. instead it distributes the whole number of loop iterations (in this case 0..479) over the number of available cores.
So scanlines 0..119 are processed by core0, 120..239 by core1, 240..359 by core2, 360..479 by core3.
This gives minimal scheduling overhead but if one core gets interrupted by another task and thus finishes later, all other cores must wait until the last one finished.
-
Excellent thanks mate ive taken a step back too the float version and redone all the base calcs watching there floor and roof values too make sure they stay in the correct ranges and it works nicely. the specular and diffuse terms look and behave the same as yours.
next step is too change the fixed point version too behave the same then ill change the size of my shifts too suit mmx.
its a pitty freebasic doesn't have a version of open mp. the standard freebasic threading commands don't seem too use more than two of my cores. so the cpu usage only ever gets as high as 36%. ill have too go a bit deeper into that.
As all vectors are normalized to signed 8bits (-128..+127) and the normalization-precision is very rough, you can see some quantization noise in the shading (which is actually good, if it wasn't there you'd see color banding) which could be removed by using more bits of the available range - but it's probably getting a bit trickier with mmx then...
glad you wrote this as i just couldnt produce the nice speckled effect ( in the float version ) around the shading and it was driving me nuts. :)
-
ive taken a step back too the float version
next step is too change the fixed point version too behave the same
Another option is to keep everything in floating point and take the sse route...
-
i think ill probably go the fixed point mmx way mostly for memory bandwidth.
well i have too admit i didnt fully understand what was going on with your all your shifts etc so have spent the whole night tearing all my code down too a number by number basis and watching all the values in real time. i've learned stuff like fixed point reciprocal divides etc.. its really been a while :).
ive fixed my range normalizing issues i had, tided it all up a bit and now its 4 core, i dont think it will use all the cpu though still haven't got round too getting into that. im still using 10 point shifts atm. my next job is too bring everthing into mmx range and try a bit of asm out.
150fps on my end at the moment.
-
well i have too admit i didnt fully understand what was going on with your all your shifts
Well, what's probably not so straight is the normalization part - which would usually look like this:
int t= lightdir.x*lightdir.x + lightdir.y*lightdir.y + lightdir.z*lightdir.z + lightdir.w*lightdir.w;
// factor to normalize to -128..+128 with 8bit of fractional part
int invSqrt= (128*256) / sqrt(t);
lightdir.x= lightdir.x * invSqrt >> 8;
lightdir.y= lightdir.y * invSqrt >> 8;
lightdir.z= lightdir.z * invSqrt >> 8;
lightdir.w= lightdir.w * invSqrt >> 8;
But when multiplying two 16bit values with mmx you can only keep the upper or the lower word, like this:
pmullw: lightdir= lightdir * invSqrt;
pmulhw: lightdir= lightdir * invSqrt >> 16;
With pmullw you get an overflow when the vector exceed 0..255 (which it does),
with pmulhw the result gets 256x smaller than it's supposed to.
So I distributed the factor of 256 to both values, *8 to the vector and *32 to the invSqrt, ending up at:
invSqrt= (128*256*32) / sqrt(t);
viewdir= (viewdir<<3) * invSqrt >> 16;Now the invSqrt doesn't fit into 16bit anymore for small values of t.
But that's not a big problem because it's impossible to normalize very small vector anyway (because 1/sqrt(0) = infinity).
So I just clamp the table-values at 32767 and accept that vectors shorter than 0.25 (32 at 7bits fractional) will be too short.
I choose a factor of 32 because at that point the lookup-table for invSqrt was much larger and I was looking up invSqrt[t>>5], so only the value of invSqrt[0] got clamped (which must be clamped anyway).
Now that it's looking up invSqrt[t>>10], it makes more sense to put the whole *256 within the invSqrt-table and remove the shift...
-
yeah i can see how they all work now, cheers Hellfire!
i just wanted too make sure i knew 100% what was going on first. now that everything is pretty much ready for mmx. Can mmx be used for almost everthing in the loop reflection vector invsqrt r,g,b saturation Etc. or is it better too use mmx for the color modulation part and saturation only?
-
Well, the main trick is to make your vectors fit into 4x short, so you can use mmx for vector and color-processing.
I'd suggest to convert your code to asm in very very small steps.
Write all intermediate values back into variables so you can check them.
Start with the simple stuff, for example:
ShortVector4 col;
/*
col.x= 0;
col.y= 0;
col.z= 0;
col.w= 0;
*/
_asm {
pxor mm7,mm7
movq [col], mm7
};
And
// calc light direction
/*
lightDir.x= pos.x - lightPos.x;
lightDir.y= pos.y - lightPos.y;
lightDir.z= pos.z - lightPos.z;
lightDir.w= pos.w - lightPos.w;
*/
_asm {
movq mm3, [pos]
movq mm4, [lightPos]
psubw mm3, mm4
movq [lightDir],mm3
};
Once you've converted the whole innerloop, you can remove most of the loading/storing from and to variables.
That's the point where your code suddenly gets much faster.
-
cheers hellfire will do mate ive already started ;).
ive 4dvectorized all my variables and shifted in the correct ranges also started converting all the simple parts too asm might take me a while thought..
when you say all the load and store instruction is where the code slows down you probably mean some thing like this i guess :)
_BUMP1@4:
push ebp
mov ebp, esp
sub esp, 140
push ebx
.Lt_00DF:
mov dword ptr [ebp-4], 0
mov dword ptr [ebp-8], 0
mov dword ptr [ebp-12], 0
mov dword ptr [ebp-16], 0
mov dword ptr [ebp-20], 0
mov dword ptr [ebp-24], 0
mov dword ptr [ebp-28], 0
mov dword ptr [ebp-32], 0
mov dword ptr [ebp-36], 0
mov dword ptr [ebp-40], 0
mov dword ptr [ebp-44], 0
mov dword ptr [ebp-48], 0
mov dword ptr [ebp-52], 0
mov dword ptr [ebp-56], 0
mov dword ptr [ebp-60], 0
mov dword ptr [ebp-64], 0
mov dword ptr [ebp-68], 0
mov dword ptr [ebp-72], 0
mov dword ptr [ebp-76], 0
mov dword ptr [ebp-80], 0
mov dword ptr [ebp-84], 0
mov dword ptr [ebp-88], 0
mov dword ptr [ebp-92], 0
mov dword ptr [ebp-96], 0
mov dword ptr [ebp-100], 0
mov dword ptr [ebp-104], 0
mov dword ptr [ebp-108], 0
mov dword ptr [ebp-112], 0
mov dword ptr [ebp-116], 0
mov dword ptr [ebp-120], 0
mov dword ptr [ebp-124], 0
mov dword ptr [ebp-128], 0
mov dword ptr [ebp-132], 0
mov dword ptr [ebp-136], 0
fld qword ptr [_Lt_00D9]
fmul qword ptr [_DIFFUSER]
fistp dword ptr [ebp-96]
fld qword ptr [_Lt_00D9]
fmul qword ptr [_DIFFUSEG]
fistp dword ptr [ebp-100]
fld qword ptr [_Lt_00D9]
fmul qword ptr [_DIFFUSEB]
fistp dword ptr [ebp-104]
mov dword ptr [ebp-92], 0
mov eax, dword ptr [ebp-92]
lea ebx, [_BUFFER+eax*4]
mov dword ptr [ebp-4], ebx
mov ebx, dword ptr [ebp-92]
lea eax, [_NORMX+ebx*4]
mov dword ptr [ebp-32], eax
mov eax, dword ptr [ebp-92]
lea ebx, [_NORMY+eax*4]
mov dword ptr [ebp-36], ebx
mov ebx, dword ptr [ebp-92]
lea eax, [_NORMZ+ebx*4]
mov dword ptr [ebp-40], eax
mov eax, dword ptr [ebp-92]
lea ebx, [_BUMPR+eax*4]
mov dword ptr [ebp-8], ebx
mov ebx, dword ptr [ebp-92]
lea eax, [_BUMPG+ebx*4]
mov dword ptr [ebp-12], eax
mov eax, dword ptr [ebp-92]
lea ebx, [_BUMPB+eax*4]
mov dword ptr [ebp-16], ebx
mov ebx, dword ptr [ebp-92]
lea eax, [_RNORM+ebx*4]
mov dword ptr [ebp-20], eax
mov eax, dword ptr [ebp-92]
lea ebx, [_GNORM+eax*4]
mov dword ptr [ebp-24], ebx
mov ebx, dword ptr [ebp-92]
lea eax, [_BNORM+ebx*4]
mov dword ptr [ebp-28], eax
mov eax, dword ptr [ebp-92]
lea ebx, [_HEIGHTMAP+eax*4]
mov dword ptr [ebp-44], ebx
mov dword ptr [ebp-140], 0
mov dword ptr [ebp-52], 0
.Lt_00E4:
mov dword ptr [ebp-48], 0
.Lt_00E8:
mov ebx, dword ptr [ebp-32]
mov eax, dword ptr [ebx]
mov dword ptr [ebp-68], eax
mov eax, dword ptr [ebp-36]
mov ebx, dword ptr [eax]
mov dword ptr [ebp-72], ebx
mov ebx, dword ptr [ebp-40]
mov eax, dword ptr [ebx]
mov dword ptr [ebp-76], eax
mov eax, dword ptr [ebp-44]
mov ebx, dword ptr [eax]
add ebx, -20480
sar ebx, 10
mov dword ptr [ebp-120], ebx
fild dword ptr [ebp-48]
fsub qword ptr [_LIGHTX]
fistp dword ptr [ebp-112]
fild dword ptr [ebp-52]
fsub qword ptr [_LIGHTY]
fistp dword ptr [ebp-116]
mov ebx, dword ptr [ebp-112]
imul ebx, dword ptr [ebp-112]
mov eax, dword ptr [ebp-116]
imul eax, dword ptr [ebp-116]
add ebx, eax
mov eax, dword ptr [ebp-120]
imul eax, dword ptr [ebp-120]
add ebx, eax
mov dword ptr [ebp-140], ebx
mov ebx, dword ptr [ebp-140]
sar ebx, 10
mov eax, dword ptr [_INVSQRT+ebx*4]
mov dword ptr [ebp-140], eax
and this was just a tiny snippet of the freebasic generated code from the bump function..
also do you think its possible i might have mutual exchange issues with my threading and that might make the cpu sit around a lot of the time each frame and do nothing. would passing the the same segment of memory too different threads even though they were working on different cells cause any binding issues. i have all my external arrays globally created atm and just let my bump1,2,3,4 pull them in and process different locations in them. there is actually a chance that my threads try and read variables such as lightx,y,z and address the same invsqrt and powtable indexes at the same time.
-
do you think its possible i might have mutual exchange issues with my threading and that might make the cpu sit around a lot of the time each frame and do nothing.
would passing the the same segment of memory too different threads even though they were working on different cells cause any binding issues.
I guess you're not using any kind of mutexing, so there's no reason why any of the threads should wait.
And as every thread works on his own block of data, the threads cannot interfere.
However, every thread needs his own set of temporary variables - when working on the same global variable from different threads, the result is of course totally unpredictable.
But there can still be really awkward situations like this one:
// once loaded from memory, both variables will be kept in the same cache line
int dataA;
int dataB;
core0:
dataA= 1234; // write value back into cache
core1:
int value= dataB;
// cache line of dataB got invalided because core0 modified
// memory which refers to the same cache line!
// must request core0 to write its' cache line back into memory
// wait until memory is available
// read back whole cache line
// wait until data is available in the cache
-
ahh i see,
and does your code wait on the threads finishing before updating the frame or does it just unlock them and let them run, the way mines is if i remove the threadwaits i get similar behavior too yours, 100% usage with about 280 fps, yours gets 340fps but that would be down too mmx.
sorry for all the questions im new too all this kind of stuff and its great too be able too ask people with as much knowledge as your self questions about it.
-
does your code wait on the threads finishing before updating the frame or does it just unlock them and let them run
I don't really know how to determine that a frame has finished without waiting on the threads to finish.
At the moment I don't really do anything at all - all the multi-threading is handled by the open-mp macro automatically.
It behaves just like running without multiple threads, so the loop finishes as soon as all threads are done.
As open-mp provides a thread-id for every iteration of the parallel loop, I figured it processes continuous blocks with each thread.
That's probably not the best possible solution for all scenarios but good enough to not think about a better way for now.
If each block requires very different processing time, it probably makes sense to work on a smaller granularity to minimize thread sync time.
I haven't checked the processing time for each thread yet, but I guess it should be quite constant as the amount of source data is equal and accessed strictly linear.
-
i think i might be onto something.. ive noticed that the call too ptcupdate( @Buffer(0)) drops my cpu usage by about 50% across all cores. if i comment it out i obviously get no final render but the cpu usage jumps too about 93% with all cores almost maxed out evenly. im going too give setting up a bit of gdi blitting a go rather than ptc, too see if that helps if not ill just ditch freebasic and jump back into my visual studio and use open mp as i cant afford for my cpu to be so heavily underutilized.
-
i've fixed the problem!! :)
my initial hunch was correct there is something in ptc ext++ that doesn't behave properly in multi core systems, it acts a bit like a sleep command so no matter how much or little threads you create cpu usage doesnt go very high.
ive coded two versions in this zip using gdi. the thread stuff works great now. one of the versions is an eight thread as my i7 has 8 effective cores ( 4 hardware 4 virtual ) this version gets 270 280fps with 90%-93% usage. and a 4 core one which gets 200 210fps with 50% - 60%.
if anyone tries these could they please tell me usage and fps please it would come in most handy too know that it works as hoped.
-Removed too keep Forum tidy see below-
-
Since I don't have "virtual cores" it doesn't seem to make much of a difference:
BumpMap4: around 125fps and 75-85% cpu usage.
BumpMap8: around 130fps and 70-90% cpu usage.
I think there's simply no thread running while ptc transfers the framebuffer over to the window (and it's probably not very fast).
How about running them in parralel with a double buffer?
Start threads rendering to buffer0
display buffer1
wait for threads
swap pointers of buffer0 / buffer1
-
thanks very much hellfire.
great little offscreen rendering idea. i tried it with ptc but it doesnt make a difference. i think there is a little more too the ptc issue though as when i start task manager too check usage with the ptc version the fps jumps a lot like 70fps and stays there. so i think maybe one of the windows calls inside ptc are causing me issues. it might even be a problem that only affects my setup.
im fine using gdi. i was just being lazy using ptc. your little double buffer idea gave a nice little step up with it. about 5% usage so im now sitting at roughly 95% which is fine for me i wouldnt like too run 100% all the time anyway.
i tried too multi thread portions of the screen with setdibitstodevice but windows didnt like that at all :).. the principal works fine a can render the full screen with 4 segment calls but the minute threading gets involved the app instantly gives up..
-
for any one that might find it usefull ive coded a little sort of engine for this tonight it dynamically allocates and deallocates cores you can see the number of threads in use in the text box too reduce cores press Z and add cores X key. ive also made the bump maps hold all there own data so they can now easily be loaded and handed all the 3d info for use too texture map objects.. thats my next thing.
this now does 2 bump map images. that can be fliped back and forth with the 1 and 2 keys.
so Z and X allocates deallocates threads And..
1 and 2 flips back and forth through textures..
thanks for all your help hellfire its been a really great little project ive enjoyed it loads!! and im going too keep chipping away at the mmx stuff in my free time.
-
Nice one, Nino.
The pure 2d bump usually just offsets the light-texture coordinate according to the normal (that's how it's done here).
The light-map was made big enough that you didn't have to care about clipping, so you don't have to recalculate it every frame.
Code: [Select]
for y= ...
for x= ...
col= colorMap[y]
normal= normalMap[y]
nx= normal shr 16 and 255
ny= normal shr 8 and 255
light= lightMap[y+ny+lightPosY][x+nx+lightPosX]
dst[y]
If you really want to work in 3d, you can handle diffuse and reflection separately, though.
^ on first page...
i was just doing my end of year back ups and organizing all my things, while doing so i was just randomly running things then while backing up this i remembered your comment hellfire about how simply this can be done.
so after a quick half hour i codded this up and it works and looks pretty neat much better than expected actually. it is so cheap i got away with 4 lights white blue red and green and still blazes along on my machine... well impressed cheers mate ;)
-
That is coolest Bump mapping I have seen :)
-
thanks hotshot, this hole topic was an excellent learning project that i hope others can enjoy as much as i have. :cheers: