I'm trying to replicate the Photoshop filter multiply with Direct3D. I've been reading and googling about the different render states and I've got the effect almost working. The problem is that it's ignoring the alpha value of the textures.
Here's an image that explains the sitution:
http://www.kloonigames.com/petri/stackoverflow_doesnt_allow_.jpg
I found one solution to this, which was to save the images with no transparency and white background. But I'm not satisfied with this solution. The problem is that I really need to use the alpha value. I want to fade out the images gradually. And I cannot do this if the blending mode is ignoring the alpha value.
So the question is how to render the images with alpha?
Here's the blending mode code:
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
Edit added the SetTextureStageState
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
You can achieve this effect in one step by premultipling alpha in your pixel shader, or by using textures with pre-multiplied alpha.
For example if you have 3 possible blend operations for a shader, and you want each one to take alpha into account.
Blend = ( src.rgb * src.a ) + ( dest.rgb * (1-src.a) )
Add = ( src.rgb * src.a ) + ( dest.rgb )
Multiply = (src.rgb * dest.rgb * src.a) + (dest.rgb * (1-src.a) )
You'll notice that Multiply is impossible with a single pass because there are two operations on the source color. But if you premultiply alpha in your shader you can extract the alpha component from the blending operation and it becomes possible to blend all three operations in the same shader.
In your pixel shader you can pre-multiply alpha manually. Or use a tool like DirectXTex texconv to modify your textures.
return float4(color.rgb*color.a, color.a);
The operations become:
Blend = ( src.rgb ) + ( dest.rgb * (1-src.a) )
Add = ( src.rgb ) + ( dest.rgb )
Multiply = ( src.rgb * dest.rgb ) + (dest.rgb * (1-src.a) )
It sounds like you want:
dst.rgb = (src.a * src.rgb) * ((1 - src.a) * dst.rgb)
You would use D3DRS_BLENDOP to do that, but unfortunately there isn't a D3DBLENDOP_MULTIPLY. I don't think this operation is possible without a fragment shader.
OK this is not as simple as you would think. I would use an Effect & two renderTargets for this...
I'm amusing your using one render pass to try to do this, which will not work.
Photoshop has layers & each layers have an alpha channel. BTW it would be nice to know what kind of app your making.
So first in D3D I would create 2 RGBA_32bit renderTargets of the same size as your window & clear them to color white. Make it an array like so (new RenderTarget[2];) for swapping.
Now set the blending state to (AlphaFunc=Add, Src=SrcAlpha, Dst=InvSrcAlpha). For the first circle you draw it into renderTarget[0] using renderTarget[1] as a texture/sampler input source. You will render the circle with an Effect that will take the circles color & multiply it with renderTarget[1]'s sampler color. After you draw circle one you swap the renderTarget[0] with renderTarget[1] by simple indexing, so now renderTarget[1] is the one you draw to & renderTarget[0] is the one you sample from. Then you repeat the drawing process for circle 2 & so on.
After you draw ever circle you copy the last drawn renderTarget to the backBuffer & present the scene.
Here is an example of logically how you would do it. If you need reference for coding http://www.codesampler.com/ is a good place.
void TestLayering()
{
bool rtIndex = false;
RenderTarget* renderTarget = new RenderTarget[2];
Effect effect = new Effect("multiplyEffect.fx");
effect.Enable();
BlendingFunc = Add;
BlendingSource = SrcAlpha;
BlendingDest = InvSrcAlpha;
for(int i = 0; i != circleCount; ++i)
{
renderTarget[rtIndex].EnableAsRenderTarget();
renderTarget[!rtIndex].EnableAsSampler();
circle[i].Draw();
rtIndex = !rtIndex;
}
//Use D3D9's StretchRect for this...
backBuffer.CopyFromSurface(renderTarget[rtIndex]);
}
//Here is the effects pixel shader
float4 PS_Main(InStruct In) : COLOR
{
float4 backGround = tex2D(someSampler, In.UV);
return circleColor * backGround;
}
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
Will do the trick. You cannot use the 'alpha' from the diffuse vertex color anymore though. Setting a low alpha on the vertex colors will actually brighten your overlaying pixels.
Related
I currently have 3 textures being blended using a slope amount, in my terrain project. I do this by sampling each texture, determining the slope amount and setting the texture colour based on a lerp between two textures. This is the snippet of this from my pixel shader:
static const float TEX_LOW_BOUND = 0.4f;
static const float TEX_HIGH_BOUND = 0.7f;
...
float4 texColour;
float4 lowColour = lowerTex.Sample(SWrap, pin.Tex);
float4 midColour = middleTex.Sample(SWrap, pin.Tex);
float4 hiColour = upperTex.Sample(SWrap, pin.Tex);
float slope = 1.0f - pin.Normal.y;
if (slope < TEX_LOW_BOUND)
{
texColour = lerp(lowColour, midColour, slope / TEX_LOW_BOUND);
}
else if (slope >= TEX_LOW_BOUND && slope < TEX_HIGH_BOUND)
{
texColour = lerp(midColour, hiColour, (slope - TEX_LOW_BOUND) * (1.0f / (TEX_HIGH_BOUND - TEX_LOW_BOUND)));
}
else if (slope >= TEX_HIGH_BOUND)
{
texColour = hiColour;
}
I want to add a final snow texture, to apply above a certain height. I get the height value in my vertex shader by using:
vout.WHeight = mul(vin.Pos, worldMatrix).y;
I can then just set the texture colour to the snow above a certain height using this in my pixel shader:
if (pin.WHeight > 35.0f)
{
texColour = snowTex.Sample(SWrap, pin.Tex);
}
Which produces the following:
How can I blend the edge of the snow with the other textures so that the edge isn't so harsh. Bearing in mind the other textures may have already been lerped, and i'd like to maintain the texture colour.
Thank you for your time
You can do basically the same thing you just did when adding in the color for the snow caps, but what you would need here is a ranged input to determine if it is close to the edge. There are several approaches to doing this. One method could be to blend the pixel values with color addition or subtraction then normalize between the range of color value. The other would be to apply multiple texture blending. As you stated in your condition above if (pin.WHeight > 35.0f)
we know that 35.0f is the maximum height value before you start to apply the snow texture. Depending on your desired results your ranged based input might be something like: if ( height > 34.8f && height < 35.2f ) { apply texture blending or color blending; }.
The other method would be to use an alpha value with transparency fading layer over top of the original layer using the same ranged input to produce the desired output.
The only thing with this type of approach or algorithm is that it may not appear to look as realistic as you would like. This is because all the snow caps will have exactly the same height value creating an unrealistic perimeter.
A suggestion which would be close to your original approach may work out better. When applying the texture or color to your snow caps you could have an nondeterministic algorithm that would randomly select specific heights within a min range to apply the texture - texture blending, then anything over a specific height above that would then smooth out to being pure white. This way each mountain top would have a white cap, but not all of the heights would be the same at the lower bounds.
(Edit) I made working geometry picking with framebuffer. My goal is draw huge scene in one draw call, but I need to draw to multisample color texture attachment (GL_COLOR_ATTACHMENT0) and draw to (eddited) non-multisample picking texture attachment (GL_COLOR_ATTACHMENT1). The problem is if I use multisample texture to pick, picking is corrupted because of multi-sampling.
I write geometry ID to fragment shader like this:
//...
// Given geometry id
uniform int in_object_id;
// Drawed to screen (GL_COLOR_ATTACHMENT0)
out vec4 out_frag_color0;
// Drawed to pick texture (GL_COLOR_ATTACHMENT1)
out vec4 out_frag_color1;
// ...
void main() {
out_frag_color0 = ...; // Calculating lighting and other stuff
//...
const int max_byte1 = 256;
const int max_byte2 = 65536;
const float fmax_byte = 255.0;
int a1 = in_object_id % max_byte1;
int a2 = (in_object_id / max_byte1) % max_byte1;
int a3 = (in_object_id / max_byte2) % max_byte1;
//out_frag_color0 = vec4(a3 / fmax_byte, a2 / fmax_byte, a1 / fmax_byte, 1);
out_frag_color1 = vec4(a3 / fmax_byte, a2 / fmax_byte, a1 / fmax_byte, 1);
}
(Point of that code is use RGB space for store geometry ID which is then read back a using for changing color of cube)
This happens when I move cursor by one pixel to left:
Because of alpha value of cube pixel:
Without multisample is works well. But multisampling multiplies my output color and geometry id is then corrupted, so it selects random cube with multiplied value.
(Edit) I can't attach one multisample texture target to color0 and non-multisample texture target to color1, it's not supported. How can I do this in one draw call?
Multisampling is not my friend I am not sure If I understand it well (whole framebuffering). Anyway, this way to pick geometries looks horrible for me (I meant calculating ID to color). Am I doing it well? How can I solve multisample problem? Is there better way?
PS: Sorry for low english. :)
Thanks.
You can't do multisampled and non-multisampled rendering in a single draw call.
As you already found, using two color targets in an FBO, with only one of them being multisampled, is not supported. From the "Framebuffer Completeness" section in the spec:
The value of RENDERBUFFER_SAMPLES is the same for all attached renderbuffers; the value of TEXTURE_SAMPLES is the same for all attached textures; and, if the attached images are a mix of renderbuffers and textures, the value of RENDERBUFFER_SAMPLES matches the value of TEXTURE_SAMPLES.
You also can't render to multiple framebuffers at the same time. There is always one single current framebuffer.
The only reasonable option I can think of is to do picking in a separate pass. Then you can easily switch the framebuffer/attachment to a non-multisampled renderbuffer, and avoid all these issues.
Using a separate pass for picking seems cleaner to me anyway. This also allows you to use a specialized shader for each case, instead of always producing two outputs even if one of them is mostly unused.
I think it is posible...
You have to set the picking texture to multisampled and after rendering the scene, you can render 2 triangles over the screen and inside another fragmentshader you can readout each sample... to do that you have to use the GLSL command:
texelFetch(sampler, pixelposition/*[0-texturesize]*/, /*important*/layernumber);
Then you can render it into a single-sampled texture and read the color via glReadPixel.
I haven't tested it now, but I think it works
I'm trying to set up a two-stage render of objects in a 3D engine I'm working on written in C++ with DirectX9 to facilitate transparency (and other things). I thought it was all working nicely until I noticed some dodgyness on the edge of objects rendered before objects using this two stage method.
The two stage method is simple:
Draw model to off-screen ("side") texture of same size using same zbuffer (no MSAA is used anywhere)
Draw off-screen ("side") texture over the top of the main render target with a suitable blend and no alpha test or write
In the image below the left view is with the two stage render of the gray object (a lamppost) with the body in-front of it rendered directly to the target texture. The right view is with the two-stage render disabled, so both are rendered directly onto the target surface.
On close inspection it is as if the side texture is offset by exactly 1 pixel "down" and 1 pixel "right" when rendered over the target surface (but is rendered correctly in-place). This can be seen in an overlay of the off screen texture (which I get my program to write out to a bitmap file via D3DXSaveTextureToFile) over a screen shot below.
One last image so you can see where the edge in the side texture is coming from (it's because rendering to the side texture does use z test). Left is screen short, right is side texture (as overlaid above).
All this leads me to believe that my "overlaying" isn't very effective. The code that renders the side texture over the main render target is shown below (note that the same viewport is used for all scene rendering (on and off screen)). The "effect" object is an instance of a thin wrapper over LPD3DXEFFECT, with the "effect" field (sorry about shoddy naming) being a LPD3DXEFFECT itself.
void drawSideOver(LPDIRECT3DDEVICE9 dxDevice, drawData* ddat)
{ // "ddat" drawdata contains lots of render state information, but all we need here is the handles for the targetSurface and sideSurface
D3DXMATRIX idMat;
D3DXMatrixIdentity(&idMat); // create identity matrix
dxDevice->SetRenderTarget(0, ddat->targetSurface); // switch to targetSurface
dxDevice->SetRenderState(D3DRS_ZENABLE, false); // disable z test and z write
dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);
vertexOver overVerts[4]; // create square
overVerts[0] = vertexOver(-1, -1, 0, 0, 1);
overVerts[1] = vertexOver(-1, 1, 0, 0, 0);
overVerts[2] = vertexOver(1, -1, 0, 1, 1);
overVerts[3] = vertexOver(1, 1, 0, 1, 0);
effect.setTexture(ddat->sideTex); // use side texture as shader texture ("tex")
effect.effect->SetTechnique("over"); // change to "over" technique
effect.setViewProj(&idMat); // set viewProj to identity matrix so 1/-1 map directly
effect.effect->CommitChanges();
setAlpha(dxDevice); // this sets up the alpha blending which works fine
UINT numPasses, pass;
effect.effect->Begin(&numPasses, 0);
effect.effect->BeginPass(0);
dxDevice->SetVertexDeclaration(vertexDecOver);
dxDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, overVerts, sizeof(vertexOver));
effect.effect->EndPass();
effect.effect->End();
dxDevice->SetRenderState(D3DRS_ZENABLE, true); // revert these so we don't mess everything up drawn after this
dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
}
The C++ side definition for the VertexOver struct and constructor (HLSL side shown below somewhere):
struct vertexOver
{
public:
float x;
float y;
float z;
float w;
float tu;
float tv;
vertexOver() { }
vertexOver(float xN, float yN, float zN, float tuN, float tvN)
{
x = xN;
y = yN;
z = zN;
w = 1.0;
tu = tuN;
tv = tvN;
}
};
Inefficiency in re-creating and passing the vertices down to the GPU each draw aside, what I really want to know is why this method doesn't quite work, and if there are any better methods for overlaying textures like this with an alpha blend that won't exhibit this issue
I figured that the texture sampling may matter somewhat in this matter, but messing about with options didn't seem to help much (for example, using a LINEAR filter just makes it fuzzy as you might expect implying that the offset isn't as clear-cut as a 1 pixel discrepancy). Shader code:
struct VS_Input_Over
{
float4 pos : POSITION0;
float2 txc : TEXCOORD0;
};
struct VS_Output_Over
{
float4 pos : POSITION0;
float2 txc : TEXCOORD0;
float4 altPos : TEXCOORD1;
};
struct PS_Output
{
float4 col : COLOR0;
};
Texture tex;
sampler texSampler = sampler_state { texture = <tex>;magfilter = NONE; minfilter = NONE; mipfilter = NONE; AddressU = mirror; AddressV = mirror;};
// side/over shaders (these make up the "over" technique (pixel shader version 2.0)
VS_Output_Over VShade_Over(VS_Input_Over inp)
{
VS_Output_Over outp = (VS_Output_Over)0;
outp.pos = mul(inp.pos, viewProj);
outp.altPos = outp.pos;
outp.txc = inp.txc;
return outp;
}
PS_Output PShade_Over(VS_Output_Over inp)
{
PS_Output outp = (PS_Output)0;
outp.col = tex2D(texSampler, inp.txc);
return outp;
}
I've looked about for a "Blended Blit" or something but I can't find anything, and other related searches have only brought up forums implying that rendering a quad with an orthographic projection is the way to go about doing this.
Sorry if I've given far too much detail for this issue but it's both interesting and infuriating and any feedback would be greatly appreciated.
It looks for me that you problem is the mapping of texels to pixels. You must offset a screen-aligned quad with a half pixel to match the texels direct to the screenpixels. This issue is explaines here: Directly Mapping Texels to Pixels (MSDN)
For anyone else hitting a similar wall, my specific problem solved by adjusting the U and V values of the verticies sent to the GPU for the overlaid texture triangles thus:
for (int i = 0; i < 4; i++)
{
overVerts[i].tu += 0.5 / (float)ddat->targetVp->Width; // ddat->targetVp is the viewport in use, and the viewport is the same size as the texture
overVerts[i].tv += 0.5 / (float)ddat->targetVp->Height;
}
See Directly Mapping Texels to Pixels as provided by Gnietschow's answer for an explanation as to why this makes sense.
All the anti-aliased line drawing algorithms I've come across simply say that the "intensity" of the pixels needs to be a function of how much of the line passes through it. This works fine on constant backgrounds (ie white), but I want to be able to draw on a background of arbitrary complexity, which means replacing intensity with transparency and alpha blending the line with the background.
Doing this necessarily changes the color of the line depending on what the background is, since for a 1px line it rarely passes exactly through a single pixel, giving it full opacity. I'm curious if there's a technique for drawing these blended lines while maintaining the appearance of the original color.
Here's an example of my rendering attempt on a colorful background. You'll note the vertical/horizontal lines are drawn as a special case with the real color, and the anti-aliased diagonal lines have a blue tint to them.
Is there a proper way to blend anti-aliased lines into the background while maintaining the appearance of the proper line color?
Edit: and code for actually plotting points:
// Plot pixel at (x,y) with color at transparency alpha [0,1]
static inline void plot(pixel_t *pixels, uint16_t stride, const pixel_t &color, uint16_t x, uint16_t y, uint8_t alpha) {
pixel_t pix = pixels[y*stride+x];
pixels[y*stride+x].r = (uint16_t)color.r * alpha/255 + pix.r * (255 - alpha) / 255;
pixels[y*stride+x].g = (uint16_t)color.g * alpha/255 + pix.g * (255 - alpha) / 255;
pixels[y*stride+x].b = (uint16_t)color.b * alpha/255 + pix.g * (255 - alpha) / 255;
}
Edit: For future generations, blending green and blue can give your lines a blue-ish tint.
I'm glad you spotted the bug in your code.
Another problem to watch out for is gamma correction. Anti-aliasing must be applied in a linear color space to look correct, but most of the time to save some processing steps it is applied in a gamma-corrected color space instead. The effects are much more subtle than your example.
I am using Nvidia CG and Direct3D9 and have the question about the following code.
It compiles, but doesn't "loads" (using cgLoadProgram wrapper) and the resulting failure is described simplyas D3D failure happened.
It's a part of the pixel shader compiled with shader model set to 3.0
What may be interesting is that this shader loads fine in the following cases:
1) Manually unrolling the while statement (to many if { } statements).
2) Removing the line with the tex2D function in the loop.
3) Switching to shader model 2_X and manually unrolling the loop.
Problem part of the shader code:
float2 tex = float2(1, 1);
float2 dtex = float2(0.01, 0.01);
float h = 1.0 - tex2D(height_texture1, tex);
float height = 1.00;
while ( h < height )
{
height -= 0.1;
tex += dtex;
// Remove the next line and it works (not as expected,
// of course)
h = tex2D( height_texture1, tex );
}
If someone knows why this can happen or could test the similiar code in non-CG environment or could help me in some other way, I'm waiting for you ;)
Thanks.
I think you need to determine the gradients before the loop using ddx/ddy on the texture coordinates and then use tex2D(sampler2D samp, float2 s, float2 dx, float2 dy)
The GPU always renders quads not pixels (even on pixel borders - superfluous pixels are discarded by the render backend). This is done because it allows it to always calculate the screen space texture derivates even when you use calculated texture coordinates. It just needs to take the difference between the values at the pixel centers.
But this doesn't work when using dynamic branching like in the code in the question, because the shader processors at the individual pixels could diverge in control flow. So you need to calculate the derivates manually via ddx/ddy before the program flow can diverge.