Sign in to follow this  
wong_bo

[MDX2] How to fill a 3D polygon with texture brush

Recommended Posts

wong_bo    128
Hi, I am new to DirectX. My problem is that I need to fill a polygon defined in a 3D space with small 2D bitmap patterns. The patterns are tiled within the polygon and get clipped off at the polygon edges. This fill effect can be achieved using simple 2D drawing methods like
Graphics.FillPolygon (TextureBrush, Point[]) 
in C# and
CBrush brush; brush.CreateDIBPatternBrush(...);
pDC->SelectObject(&brush);
pDC->Polygon(...);
in MFC/C++. I tried to project all the 3D vertices to screen and use these 2D drawing methods. But the result is that the filling patterns are always on top of my 3D models. Is there a native DirectX method to do this and it is depth aware? I am using MDX2.

Share this post


Link to post
Share on other sites
remigius    1172
You can apply textures directly onto Direct3D polygons, as explained in this tutorial, so there should be no need to do the postprocessing you're doing. The way it basically works is that you specify the texture by calling device.SetTexture(0, someTexture) and DirectX will handle the rest, provided the vertices for your polygon have texture coordinates defined. I believe there's also a small article on that in the MDX docs.

On a related note, the 2.0 versions of MDX are still in beta and will not be released in their current form, so it's probably best to stick with the 1.x assemblies for now.

Share this post


Link to post
Share on other sites
wong_bo    128
remigius, thanks for the quick reply.
The tutorial you point out is for normal texture mapping. What I really need is a texture brush to fill a polygon in TILED format. Below is an image showing the desired effect using the post-rendering method I mentioned at the start of this thread. Please note that no matter how I rotate the rectangle, the tiled bitmaps should always facing the user (2D drawing effect).


What I really want is to use native DirectX method to achieve this.

Image and video hosting by TinyPic

Share this post


Link to post
Share on other sites
u235    359
Couldn't you just use a bitmap that's tiled as your texture and then do normal texture mapping? Hope this helps.

-AJ

Share this post


Link to post
Share on other sites
Mace    151
One way to do it could be that you set the texture state to wrap/loop and then use the x,y coordinates of the vertices as texture coordinates, or alternatively generate your texture coordinates based on where the vertex x,y are on the screen in 2d coordinates.

Share this post


Link to post
Share on other sites
remigius    1172
Quote:
the tiled bitmaps should always facing the user (2D drawing effect).


Sorry, I misunderstood your question there. Mace's solution sounds like the way to go for this indeed. I've fixed up a little demo project that shows both approaches, updating the texture coordinates manually and just reusing the vertex xy coordinates in a shader. You can find the project over here in this zip. It does require a card that supports linear texture filtering (I think nearly all non-ancient cards can do this though), since with point filtering you get massive amounts of 'texture twitching'.

Hope this does help :)

Share this post


Link to post
Share on other sites
wong_bo    128
Thank you very much remigius (and mace).

I tested your code and it does almost exactly what I want. I still got some texture 'twitching' or 'shaking' in my current project code when I rotate the polygons. I put

device.SamplerState[0].MinFilter = TextureFilter.Linear;
device.SamplerState[0].MagFilter = TextureFilter.Linear;


right before device.SetTexture(0, mFillTexture). I even tried to create texture with Filter.Linear parameter. Nothing seems to work. But in your code, there is no twitching at all. Do you know what could be the problem?


By the way, is there a way to display the bitmap exactly the same size as they are? In your sample code, test.jpg is a 256x256 image. I want it to be 256x256 when it is used as tiled texture, no matter how I scale, rotate or move the triangle.

Share this post


Link to post
Share on other sites
remigius    1172
If the twitching occurs when you're rotating the polygon's verices towards and away from the camera, it's probably an issue with the MIP filtering still being a Point filter. If that's indeed the case, adding the following line should solve the problem:

device.SamplerState[0].MipFilter = TextureFilter.Linear;


D3D essentially is built to scale your world according to the size of the presentation, so keeping a texture fixed in position and size on the screen is actually exceptional behavior. This means you'll have to do some math to get this to work and the result may not match 'pixel-perfect'. Anyway, the following should do the trick:

[source lang=c#]
// First we'll apply the world transform to our
// triangle's vertices. If you're not using a
// world transform, you can skip this step and
// just use triangle[i].Position
Vector3 transformedPos = Vector3.TransformCoordinate(triangle[i].Position, world);

// Get texture description, for the sizes
SurfaceDescription sd = t.GetLevelDescription(0);
float projectionWidth = 5f; // from OrthoLH projection
float projectionHeight = 5f; // from OrthoLH projection

// Scale x and y coordinates to the current 'world size'.
// This maps the x, y coordinates to the [0,1] ranges, so
// it looks like the texture is covering the entire screen.
float xScaled = (transformedPos.X - (projectionWidth/2)) / projectionWidth;
float yScaled = (-transformedPos.Y - (projectionHeight/2)) / projectionHeight;

// Since we don't want to have the texture covering the
// entire screen, we'll multiply the texture coordinates
// by 'screenSize/textureSize'. The code below does this,
// but the calculation is grouped a bit unintuitively to
// avoid the int/int fraction you'd get otherwise.
xScaled = (xScaled * sampleFramework.BackBufferSurfaceDescription.Width) / sd.Width;
yScaled = (yScaled * sampleFramework.BackBufferSurfaceDescription.Height) / sd.Height;

// apply the mapped texture coordinates
triangle[i].Tu = xScaled;
triangle[i].Tv = yScaled;

Share this post


Link to post
Share on other sites
wong_bo    128
Thanks remigius. The code works fine on your samples.

I am still battling with the twitching problem. I tried to set MipFilter to TextureFilter.Linear but then I got a solid filled grey polygon and no texture anymore.

I suspect the twitching is because I am using a vertex buffer and Device.DrawPrimitives() for rendering which requires constant updating of vertex buffer. Your code use Device.DrawUserPrimitives() and it doesn't need vertex buffer at all. I can use DrawUserPrimitives() but as I am using MDX2, the method signature is:

public void DrawUserPrimitives(
PrimitiveType primitiveType,
Int32 primitiveCount,
GraphicsBuffer vertexStreamZeroData

The last parameter is GraphcsBuffer not GraphcsBuffer<T>. How am I going to put the triangle[] data into a non-generic GraphcsBuffer? I searched the forum and found people talking about DrawUserPrimitives in MDX2 but haven't found the solution I want.

Share this post


Link to post
Share on other sites
remigius    1172
I wouldn't know how the GraphicsBuffer works exactly, since I'm still using MDX1.1. From what I've picked up you can create one with GraphicsBuffer<VertexClass> and then use an indexed (myBuffer[index]) to set the data. As a side note, using MDX2 isn't particularly recommended since it's still in beta and will not be released in it's current form.

Be that as it may, if you can prevent it you shouldn't use DrawUserPrimitives either. I used it to quickly fix up the sample, but essentially it sticks your data into a new VertexBuffer every frame and uploads that to the videocard, which is of course not too efficient. Your vertexbuffer approach should actually work better than my code. Anyway I've updated the sample project so the HLSL shader now also uses the tiling code. By using the shader path, you don't have to bother to lock/transform/unlock your vertices manually.

I doubt the transformation is the cause of your twitching problem though. You could try moving the SamplerState changes to the code right after your device reset, so the states don't need to be updated each frame. I put them into the rendering method for convenience, but it should be more efficient to just set them once when the device resets.

Hope this helps :)

Share this post


Link to post
Share on other sites
wong_bo    128
Thank you remigius for your last post. Sorry for the late reply as I was sidetracked recently.

I managed to get it work with MDX2 and pretty happy with your solution. I've got some final questions:

1. I only use 2 colors for our bitmap: black background and white foreground. Any black color in the bitmap should be treated as transparent when rendering. In the following image, part of the triangle at the back should be able to see through the front triangle.

Image and video hosting by TinyPic

2. I want to be able to replace the foreground white color of the bitmap to any specified color. For example, I want yellow cross and green circle pattern in the 2 triangles respectively.

Is there a solution for this?

Share this post


Link to post
Share on other sites
wong_bo    128
I solved the 2nd problem by using PositionColoredTextured structure for m_triangle array and specify the appropriate color for the triangle.

I am still facing the 1st problem of having a transparent (alpha blended?) texture. It seems I have to use something like the following code, but I don't know the exact parameter I should use. Could anyone help me?

m_device.SetRenderState(RenderStates.AlphaBlendEnable, true);
m_device.SetRenderState(RenderStates.SourceBlend, (int)Blend.???);
m_device.SetRenderState(RenderStates.DestinationBlend, (int)Blend.???);
m_device.SetTextureState(0, TextureStates.AlphaOperation, (int)TextureOperation.???);

and more SetTextureState()??? I hope I can find an article to clearly explain all these operations and parameters.

Share this post


Link to post
Share on other sites
remigius    1172
I just updated the sample project again to include this functionality.

You can specify a color when you load the texture which should be transparant, like this:


// note the last colorkey argument, which specifies which color to treat as transparent
t = TextureLoader.FromFile(e.Device, @"..\..\media\t1.png",
16, 16, 1, Usage.None, Format.A8R8G8B8, Pool.Default,
Filter.Linear, Filter.Linear, unchecked((int)0xff000000));


Then you can use the following arguments to set up alpha blending:


// Set up alpha blending
device.RenderState.AlphaBlendEnable = true;
device.RenderState.SourceBlend = Blend.SourceAlpha;
device.RenderState.DestinationBlend = Blend.InvSourceAlpha;


Since this will leave only the non-black pixels visible in our example, we can just use a single color for the rendering. This can be done through the vertex colors as you used, or through the TextureFactor constant, like this:


// Set up color operations
device.TextureState[0].ColorArgument1 = TextureArgument.TFactor;
device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;
device.RenderState.TextureFactor = c1;


I've also adjusted the shader to provide the same functionality. There seems to be a very minor filtering issue with this though, so the textures look a bit 'blurred' (the alpha isn't as clearly defined as through the FFP). It looks nice enough, but if anyone could check out the sample, I'd like to know what causes this difference from the FFP (the reference device also shows this result).

Share this post


Link to post
Share on other sites
wong_bo    128
remigius, you are the man! Many thanks for the sample code.

Now the only problem I am facing is that only the 2nd triangle appears to be transparent to the first. Not the otherway around.

To test this, I changed your code of drawing 2nd triangle. Instead of rotating it, I did a translation:

//world = Matrix.RotationZ((float)appTime) * world;
world = Matrix.Translation(0, 0.5f, 0.5f) * world;

Now the first triangle (red) is still opaque to the 2nd triangle (green).

But if I change the code to:
world = Matrix.Translation(0, 0.5f, -0.5f) * world;

The 2nd triangle(green) looks transparent to the 1st triangle(red).

Image and video hosting by TinyPic

What I need is whenever one triangle is on top of the other triangle, its fill pattern should cover the bottom triangle, but the bottom triangle's fill pattern should still be able to see through the top triangle's transparent area that is not filled.

Share this post


Link to post
Share on other sites
remigius    1172
I'm not completely awake yet over here, but I think this is due to the typical limitation of alpha blending. With the alpha blending the sample uses now, you're responsible for sorting your objects from back-to-front in respect to the camera, so underlying objects are rendered first.

So you'll either will have to do this sort yourself every frame (which shouldn't be too hard or inefficient) or you could switch to alpha testing instead of alpha blending. With alpha testing you can essentially only define transparent regions, while alpha blending supports nifty stuff like additive blending. To use alpha testing, change the alpha blend setup lines to this:


device.RenderState.AlphaTestEnable = true;
device.RenderState.ReferenceAlpha = 0x08; // or any other appropriate value
device.RenderState.AlphaFunction = CompareFunction.GreaterEqual;

Share this post


Link to post
Share on other sites
wong_bo    128
I think maybe I asked too many questions. But I feel I am getting so close to the solution with the guidance.

Alpha testing sorted out the depth problem without having to write any extra code (which could be difficult to maintain). Now the problem is that it fills the triangles with pattern that is thicker than the original image (the original images are shown at the bottom-left corner of the attached picture). The good thing is that now the anti-aliasing blur is gone.

Image and video hosting by TinyPic

I suspect that anti-aliasing is still being used underneath and the result image is then projected as silhouette. That's why the patterns looks like they were drawn using a thicker pen. If I switch to TextureFilter.Point instead of TextureFilter.Linear, the pattern becomes normal size but then I got this twitching effect when rotating.

The best effect would be:
1. Using the original image drawing width.
2. No anti-aliasing effect.
3. The top triangle should be able to cover the bottom triangle.
4. No twitching when rotate.

By the way, what does
device.RenderState.ReferenceAlpha = 0x08;

mean? Should I provide an ARGB value?

Share this post


Link to post
Share on other sites
abnormal    223
yap, use ReferenceAlpha (8bit value), use a very high value like 250 (or 0xFA) to make sure only very opaque parts are rendered. alternatively you could use just alpha blending, but then you might have a z-buffer problem again (you have to render back to front if you use soft alpha blending).

Share this post


Link to post
Share on other sites
wong_bo    128
It worked!

I set ReferenceAlpha = 100 and my pattern rendered perfectly. I tested and the ReferenceAlpha range from 70 to 120 works alright with my patterns. Now I can enjoy my patterns being rendered clearly and occlude each other.

Thanks again everyone.

Share this post


Link to post
Share on other sites
remco    122
I was reading this thread and was wondering how you fixed the twitching of the texture. Thanks in advance!

Share this post


Link to post
Share on other sites
wong_bo    128
There was a time that I had the twitching problem even though I followed remigius' instruction to set

device.SamplerState[0].MinFilter = TextureFilter.Linear;
device.SamplerState[0].MagFilter = TextureFilter.Linear;
device.SamplerState[0].MipFilter = TextureFilter.Linear;

Then after few changes in my code, the problem just gone. I am not sure what I did in my code.

Setting all those 3 filters to linear is the only advice I can give. Are you still having the twitching problem after setting all the filters? If so, maybe you can download remigius' code and compare the device.SamplerState[0] (maybe other properties like device.RenderState and device.TextureState[0]) with your code in debug mode.

Good luck.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this