Pixel Shader Newbie: How do I include the diffuse color in my greyscale shader?

Started by
6 comments, last by mikiex 12 years, 2 months ago
Hi all,

Working for the FIRST TIME with pixel shaders today.

I have a simple shader to greyscale everything:


sampler DiffuseSampler;
void main(in float2 vTex : TEXCOORD0, out float4 oCol : COLOR0)
{
oCol=tex2D(DiffuseSampler, vTex);
float aGrey=(oCol.r+oCol.g+oCol.b)/3;
oCol.g=aGrey;
oCol.r=aGrey;
oCol.b=aGrey;
}


And it works great-- on textures. However, if I colorize the textures, or darken them, or even draw solid fills, the diffuse lighting is ignored. I'm sure there's something simple like "sampler DiffuseColor" out there somewhere, but I can't seem to find the magic google keyword combo that pulls it up.

So for instance, if I draw a texture with vertex x,y,z,t,u,diffuse, diffuse isn't getting factored into that shader at all.

Any pixel shader jocks who can tell me how to fix that?

[Edit] To clarify, I'm trying to draw *all* the effects normally in the render pipeline, all the blending (like additive, multiply, etc), with just the colored turned grey. Like, even this, which I used to render my stuff pure white:


gDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
gDevice->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_DIFFUSE);


Like I literally want to say "Okay, complete post-process of everything else, make it grey."
Advertisement
Once you start with shaders, you have to leave behind all of the fixed-function stuff. This includes texture stage states. If you want to multiply your diffuse lighting by a vertex color, then you need to pass the vertex color to your pixel shader and multiply your lighting by it.


sampler DiffuseSampler;
void main(in float2 vTex : TEXCOORD0, in float4 vColor : COLOR0, out float4 oCol : COLOR0)
{
oCol=tex2D(DiffuseSampler, vTex);
float aGrey=(oCol.r+oCol.g+oCol.b)/3;
oColor.rgb = vColor.rgb * aGrey;
}
Very nice... I had to tweak it a bit to get it to work for what I want:


sampler DiffuseSampler;
void main(in float2 vTex : TEXCOORD0, in float4 vColor : COLOR0, out float4 oCol : COLOR0)
{
oCol=tex2D(DiffuseSampler, vTex);
float aVGrey=(vColor.r+vColor.g+vColor.b)/3;
float aGrey=(oCol.r+oCol.g+oCol.b)/3;
oCol.rgb = aVGrey * aGrey;
oCol.a=vColor.a*oCol.a;
}


Incidentally, I notice that you just changed the main function's parameters... are there a certain number of in/out things that are supported? Like (and remember, shader newbie here), coming from a C++ perspective, just throwing an extra param into a function and bam it works means it's a predefined method, right? Is there a list anywhere that one can browse of all the possible shader iterations? For instance, would I have to do anything to this to make it work with a ZBuffer, or will it just work automagically?

[Edit] Oh! And more more question if you don't mind... suppose I wanted to specify a level of desaturation to happen-- like say I want to "fade to greyscale" but I want to set that fade amount (something between 0 and 1) in my program. I.E. I'm going to wrap turning the shader on into a function like RenderGreyscale(float theAmount);

Is there any way to do that? How can you pass data from C++ into the shader?
Untested, but you should be able to simplify while also correcting for luminance with something like this:



sampler DiffuseSampler;

void main(in float2 vTex : TEXCOORD0,in float4 vColor : COLOR0,out float4 oCol : COLOR0)
{
float3 luminance = float3(0.30, 0.59, 0.11);

tColor = tex2D(DiffuseSampler, vTex);

tColor = float4(float3(dot3(luminance, tColor)), tColor.a);
vColor = float4(float3(dot3(luminance, vColor)), vColor.a);

oCol = tColor * vColor;
}


My syntax might be off, but the algorithm should be sound. If you don't want gamma correction, you can do "float3 luminance = float3(1, 1, 1) / 3;" instead.

dot3 multiplies component-wise, then sums the components, so you can compute the average of 3 components using (1/3, 1/3, 1/3) in one step, or use the weighted distribution like I did to correct luminance at the same time.

Using the above, you'd probably have to change things a fair bit in order to fade from full color to grayscale. look up the "lerp" function.

I can't speak to performance of this with any authority, but I think the HLSL and driver-level JIT compiler is pretty good at eliminating overhead due to vector-size transforms.

throw table_exception("(? ???)? ? ???");

Well, in my particular case, I'm looking for a deeper, slate grey (I'm doing a moody game, and it goes grey when you die, anyway, so...)

Actually, what I meant about the interpolation was more specific... say I have a variable in the C++ part of my program: float mDesaturate. How do I get that variable into the shader? See, I can just do some math to make it be the amount of grey I want... if I can get that variable into the shader itself so I can start multiplying it around. :)
With shaders you can use variables called "shader constants". Their value can be set by your C++ program, but are constant during the execution of a shader. To use it, you just declare a float in your shader:

sampler DiffuseSampler;

float mSaturation;

void main(in float2 vTex : TEXCOORD0, in float4 vColor : COLOR0, out float4 oCol : COLOR0)
{
oCol = tex2D(DiffuseSampler, vTex) * vColor;
oCol.rgb = lerp(dot(oCol.rgb, 0.333f), oCol.rgb, mSaturation);
}


Now the way this works, is that the device has a set of constant registers that can be set from the app using SetPixelShaderConstantF (or SetVertexShaderConstantF for vertex shaders). Each of these registers is a float4, so when you call SetPixelShaderConstantF you set at least 4 floats at a time. When you declare a non-static global variable in your shader, that variable gets mapped to a register by the compiler. Typically it will just assign variables to sequential registers starting with 0, but you can also manually assign variables to registers with this syntax:


float mSaturation : register(c0);


To set this variable, you would call SetPIxelShaderConstantF and pass 0 as the StartRegister parameter. If you want, you can also query the register for a variable at runtime using ID3DXConstantTable.

Texture sampler also work in a similar way, in that they will be mapped to sampler registers s0 through s15. This register then corresponds to the index you pass to SetTexture.

The way that pixel shader input parameters work, is that they are mapped to the outputs of your vertex shader. For a vertex shader, the input parameters are mapped to elements of vertices in the vertex buffer. This mapping is done using semantics, So take this simple vertex shader:

float4x4 mWorldViewProjection;

void vsmain(in float3 vPosition : POSITION, in float2 vTex : TEXCOORD0, in float4 vColor : COLOR0,
out float4 oPosition : POSITION, out float2 oTex : TEXCOORD0, out float4 oColor : COLOR0)
{
oPosition = mul(float4(vPosition, 1.0f), mWorldViewProjection);
oTex = vTex;
oColor = vColor;
}


The input parameters will be taken from the vertex buffer, and will have the value of the element that was declared with the POSITION usage in the vertex declaration (or it will be based on the FVF code, if you're using those instead of vertex declarations). The vertex shader transforms the vertex position to clip space and outputs it, which ultimately determines where the vertex ends up in screen space and consequently how the triangle gets rasterized. The texture coordinate and diffuse color are simply passed through. Parameters that get passed through are then accessible in the pixel shader, as long as the pixel shader declares an input with a matching semantic. So if we were to use your pixel shader, the vTex and vColor parameters would get mapped to the vertex texture coordinate and diffuse color that got passed through in the vertex shader. The actual value received by the pixel shader will be interpolated based on where the pixel lies on the triangle.
Thank you very much! Extremely helpful!

Well, in my particular case, I'm looking for a deeper, slate grey (I'm doing a moody game, and it goes grey when you die, anyway, so...)



Its ok to use the average of RGB for a spot effect,

But definatly remember the weighted version for future use, If you want it darker you can always multiply the result by less than 1.0.
In the past I wrote a shader for a menu system that needed greyed out different colour boxes, when comparing the colour to the grey average it looks totally wrong in terms of the tone.

This topic is closed to new replies.

Advertisement