Skip to main content
gamedev.net
🔒 Locked

Bump mapping

Started by codymanix Mar 13, 2005 at 6:25 PM 11 replies 2.3k views
Original Post
codymanix
codymanix
I do not understand how bumpmapping really works. I tried the following and nothing happended, except that my model was completely black&white as if it would just show me my heightmap texture. Did I miss something? Does the bumpmapping automatically know where I positioned my Lights with SetLight? Does the textures must have a certain format? /* Layer 0 */ lpDev7->SetTexture(0, lpDDSBumpMapTexture); lpDev7->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3); lpDev7->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); lpDev7->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); /* Layer 1 */ lpDev7->SetTexture(1, lpDDSBaseTexture); lpDev7->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); lpDev7->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE); lpDev7->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
Evil Steve
Evil Steve
Yes, you need a particular format. Look into the D3DXComputeNormalMap function to load the bump map from an RGB image.

In a bump map, each of the texels represents a vector. This vector is used for the lighting calculations, so you get lighting on a per-texel basis.
An image is just a load of RGB intensities, which has very little to do with the vectors. So you need to convert your RGB image to a normal map with D3DXComputeNormalMap().
paic
paic
Hmm

I would try to do this :

/* Layer 0 */

lpDev7->SetTexture(0, lpDDSBaseTexture);
lpDev7->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
lpDev7->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_CURRENT);
lpDev7->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);

/* Layer 1 */

lpDev7->SetTexture(1, lpDDSBumpMapTexture);
lpDev7->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3);
lpDev7->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
lpDev7->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE);


The first layer is (I think) almost always the diffuse texture. In this one, we set the first color arg to current and the second one to the texture, and use a modulate operation (to use vertex colors if any)
In the second layer, we set the color args to current (result of the previous layer) and texture, and set the color op to dot product 3 (bump mapping with greyscale height textures, and NOT normal maps)

That should work. I didn't test it, but that's the way I would do it ^^

Tell us if it works or not.


.:: Paic Citron ::.
Trenton05
Trenton05
I just started working with directx, and i looked at a lot of sample code.

The way you do it is proper set up for the pixel shader. However, you have to set up your own vertex shader to be able to get the light vectors to dot product with.

ie: for each texture do D3DXComputeNormalMap() which stores the normals in the texture relative to texture space
then for the mesh do D3DXComputeNormals() and D3DXComputeTangent() (I think these are the functions)

Then you have to create a vertex shader:
The basic inputs are: position, normal, texture coordinate and tangent
Constants would be: the 3 transformation matrices and the light vector
Outputs are: position, texture coordinate and diffuse
The diffuse output is actually the light vector transformed to texture space, which will be interpolated and dot producted with the vector for the current pixel to get the final diffuse color to multiply with the texture color in the second stage.

In the shader you take the cross product of the tangent and normal to get the third normal needed to define the texture space. Then using either a point light or directional light, find the direction of the light to the point and then transform the light into texture space and transform the position. Set those and the texture coordinates in the output.
codymanix
codymanix
So I need a *particular* (vertex, texture?)format. And which one exactly?

To Trenton05:
Uuh vertex shaders. I wanted to start with something easy, not with HLSL :)
Namethatnobodyelsetook
Namethatnobodyelsetook
No, the D3DLIGHTs have nothing to do with normal mapping. The DOT3 (or dp3) instruction needs you to tell it the light direction. It doesn't take it from the standard lighting. Since you're not using shaders, the normal map must be in object space, not tangent space. However you generate your normal map, you must take this into account.

Next you need a light vector to dp3 with. Typically you'll program one, adjusted for your mesh's rotation into TFACTOR. You'll need to convert from -1..1 to 0..255, and pack each element into the correct bits of the color DWORD. ((int)(x*127 + 128))<<16 + ((int)(y*127 + 128)) << 8 + ((int)(z*127 + 128))

"dot3, texture, tfactor" will then produce a grey "amount of light". You can multiply (modulate) this by another constant color to act like a coloured light.

If you're doing multiple lights, you'll need to do this once per light, with a new vector in tfactor, and additive blending, to sum the amount of light from all sources.

After that's all done you can:
(single light) just modulate by the diffuse texture
(multipass multilight), setup blending to modulate and render just the diffuse texture.

Normal mapping is much more useful when you have shaders and can use tangent space (ie: you can reuse a normal map across many objects). Shaders also give you more direct control over data processing. Using ps.1.1, you can squeeze 3 diffuse only lights into one pass. Adding in specular will reduce this to 1 light (maybe even requiring a pass per light with specular, on top of the diffuse calcuation). Using ps.2 + you put do all your lighting in one pass.
codymanix
codymanix
I created my texture using format Q8W8V8U8. The result looks a bit bumpy now but the texture is gone away so I just see the bumps:

/* Layer 0 */

lpDev7->SetTexture(0, lpDDSBaseTexture);
lpDev7->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SelectArg1);
lpDev7->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

/* Layer 1 */

lpDev7->SetTexture(1, lpDDSBumpMapTexture);
lpDev7->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DOT3);
lpDev7->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
lpDev7->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR);

In case case my translation to unmanaged directx got wrong here is my original:

device.SetTexture(0, planet.Texture); device.TextureState[0].ColorOperation = TextureOperation.SelectArg1; device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;

device.SetTexture(1, planet.BumpTexture);
device.TextureState[1].ColorArgument1 = TextureArgument.Current;
device.TextureState[1].ColorOperation = TextureOperation.DotProduct3;
device.TextureState[1].ColorArgument2 = TextureArgument.TFactor;

This is how I create my textures now:

Texture marsTexture = TextureLoader.FromFile(device, resPath + "mars.png");

Texture marsBumpTexture = TextureLoader.FromFile(device, resPath + "mars_bump.jpg");

Texture normalTexture = new Texture(device, 1024, 512, 0, (Usage)0, Format.Q8W8V8U8, Pool.Default);

TextureLoader.ComputeNormalMap(normalTexture, marsBumpTexture, NormalMap.InvertSign, Channel.Luminance, 1f);
Namethatnobodyelsetook
Namethatnobodyelsetook
The V8U8 format (and it's 32 bit counterpart) are for signed value textures. DOT3 says in it's documentation that it scales and biases the inputs from 0..1 to -1..1, so using a regular A8R8G8B8 or X8R8G8B8 texture is what it's expecting. If you write to the normal map texture yourself, scale and bias the vectors before putting them in the texture, just like for TFactor. -1 maps to 1. 0 maps to 128. +1 maps to 255.

Using, ComputeNormalMap may expect an unsigned texture, a signed texture, or may detect which format the texture is and act accordingly. If it expect a signed texture, you may have to lock and offset each value by 128. It probably just expects unsigned formats. I can't say for sure though, as I've never used this function, only tangent space normal maps with nVidia's photoshop plugin.

The reason you don't see your texture is because you load it on stage 0 and overwrite the values on stage 1. Think of texture stages like a small program. You currently say:

color = texture0
color = texture1 dot tfactor
end

what you'll want is probably to do the dot3 on stage 0, and the modulate by your texture on stage 1. I'll also set things up for alpha to be vertex or material alpha multiplied by the texture alpha.

color = texture0 dot tfactor
color = color * texture1
alpha = vertexalpha
alpha = alpha * texture1
end

SetTSS(0, colorop, dot3)
SetTSS(0, colorarg1, texture)
SetTSS(0, colorarg2, tfactor)
SetTSS(1, colorop, modulate)
SetTSS(1, colorarg1, texture)
SetTSS(1, colorarg2, current)
SetTSS(2, colorop, disable)
SetTSS(0, alphaop, selectarg1)
SetTSS(0, alphaarg1, diffuse)
SetTSS(1, alphaop, modulate)
SetTSS(1, alphaarg1, diffuse)
SetTSS(1, alphaarg2, texture)
SetTSS(2, alphaop, disable)

Note everything I've said applies to DX8 and 9. I see a lpDev7. I have no idea how DX7 works, and what differences it may have. From everything I've heard, everything before DX8 was overly complicated and difficult to use.
Namethatnobodyelsetook
Namethatnobodyelsetook
MultiplyAdd is a three argument operation (COLORARG0 being the third argument). You want modulate, or something in arg0.

Stage 1 alpha op is never set (you set stage 0 again by cut-n-paste mistake). Any output from the card with this setup will be undefined.

Stage 0 alpha op of disable is invalid (you meant to set stage 2 to disable). Any output from the card with this setup will by undefined.
codymanix
codymanix
I don't believe it!!! Putting

device.TextureState[1].TextureCoordinateIndex = 0;

in solved the problem! I don't know why but after experimenting for hours that did it.

But I have now another problem: The sun rotates around the mars (from viewer). If you see sun coming from the side mars gets brighter (as it should), but if the sun reaches the "highest point" or zenith (you know what I mean) it starts to get darker again which is wrong. What could be the reason?
Iam calculating the vecot like this:

int VectorToRgba(Vector3 v, float height)
{
int r = (int)(127.0f * v.X + 128.0f);
int g = (int)(127.0f * v.Y + 128.0f);
int b = (int)(127.0f * v.Z + 128.0f);
int a = (int)(255.0f * height);
return((a<<24) + (r<<16) + (g<<8) + (b<<0));
}

int factor = VectorToRgba(Vector3.Normalize(Vector3.TransformCoordinate(lightVector, planet.Matrix)), 0.0f);
device.RenderState.TextureFactor = factor;


However I have one additional question: What is in your example the disabling of device.TextureState[2] good for since it is never set shouldn't it be disabled by default?
Namethatnobodyelsetook
Namethatnobodyelsetook
Never rely on the defaults, as not all drivers default the values correctly.

Even if you could rely on the defaults, imagine having more than one simple test effect. You setup an effect that uses stage 0 only, and never sets 1 to disable. Next, setup an effect that sets stage 0 and 1, and possibly stage 2 to disable. After rendering with the second effect, you try to switch back to the first effect. It doesn't work as expected. It's doing what you expect in stage 0, but stage 1 is still active doing whatever the second effect wanted.

It's better to make each effect self reliant, setting all values, even if they're defaults, as drivers or another effect may have changed the setting from expected default.
codymanix
codymanix
> It's better to make each effect self reliant, setting all values, even if
> they're defaults, as drivers or another effect may have changed the setting from > expected default.

Yes that is true. I once tried to capture the renderstates and reappling the original ones after each effect, but notices the applyign a renderstates object to the device is a serious performance bottleneck.

However, do you have an Idea what could be wrong with my lightvector?
The sun is at coord (0,0,0). The planet circles around the sun, the viewer look staight at the planet. if I transform the coord(0,0,0) with the matrix the planet is using shouldn't I get the correct lightvector?

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.