understanding clipspace w coordinate division practically

Started by
6 comments, last by JohnnyCode 4 years, 6 months ago

hi,

please could someone explain me when and why do i have to divide my tranformed coordinates in e.g. pixelshader by w ?

here a typical NVIDIA example. the comments and questions are made by myself.

 

struct VS_OUTPUT
{
  float4 ScreenP : SV_POSITION;
  float4 P       : TEXCOORD0;
  float3 N       : NORMAL0;

}; 

// Vertex Shader

VS_OUTPUT main( uint id : SV_VERTEXID )
{
    VS_OUTPUT output;
 
    ... some Code ..
    
    float3 N; // Normal

    N.x = ((face_idx % 3) == 0) ? 1 : 0;
    N.y = ((face_idx % 3) == 1) ? 1 : 0;
    N.z = ((face_idx % 3) == 2) ? 1 : 0;
    N *= ((face_idx / 3) == 0) ? 1 : -1;
    P += N;   // World Coordinate

    output.P = mul(c_mObject, float4(P, 1));      // transform with object world matrix with w = 1 => float4(P, 1)
    output.ScreenP = mul(c_mViewProj, output.P);  // transform further with ViewProj in clip coordinates 
    output.N = mul(c_mObject, float4(N, 0)).xyz;  // transform with object world matrix with w = 0 because only rotations apply
    return output;
}

cbuffer CameraCB : register( b0 )
{
    column_major float4x4 c_mViewProj   : packoffset(c0);
    float3 c_vEyePos                    : packoffset(c4);
    float c_fZNear                      : packoffset(c5);
    float c_fZFar                       : packoffset(c5.y);
};

// pixelShader

float4 main(VS_OUTPUT input) : SV_Target0
{
    float3 P   = input.P.xyz / input.P.w;                  // =>  my Question why do we have the world coordnate by w ???
    float3 N   = normalize(input.N);                       // normalize because normal is interpolated in pixelshader ?
    float3 Kd  = c_vObjectColor;

   
    const float SHADOW_BIAS  = -0.001f;
    float4      shadow_clip  = mul(c_mLightViewProj, float4(P,1));   // => here P is transformed to clipspace coordinate with w = 1
                shadow_clip  = shadow_clip / shadow_clip.w;          // => why division by w again ??
    uint        hemisphereID = (shadow_clip.z > 0) ? 0 : 1;
    
    float2 shadow_tc         = float2(0.5f, -0.5f)*shadow_clip.xy + 0.5f; // => here xy used for texure coordinates
    float receiver_depth     = shadow_clip.z+SHADOW_BIAS;                 // => here z  used as depth

    float total_light = 0;
    const int SHADOW_KERNEL = 2;
    [unroll]
    for (int ox=-SHADOW_KERNEL; ox<=SHADOW_KERNEL; ++ox)
    {
        [unroll]
        for (int oy=-SHADOW_KERNEL; oy<=SHADOW_KERNEL; ++oy)
        {
            total_light += tShadowmap.SampleCmpLevelZero(sampShadowmap, shadow_tc, receiver_depth, int2(ox, oy)).x;
        }
    }
    float shadow_term = total_light / ((2*SHADOW_KERNEL+1) * (2*SHADOW_KERNEL+1));

    float3 output = float3(0,0,0);
    float3 L = -c_vLightDirection;
   
    // Spotlight)
    {
        float light_to_world = length(P - c_vLightPos);     // P (divided by w) used as world Pos but LightPos is not divided by w
        float3 W = (c_vLightPos - P)/light_to_world;        // Light direction Vector is calculated from P

        float distance_attenuation = 1.0f/(c_vLightAttenuationFactors.x + c_vLightAttenuationFactors.y*light_to_world
                                      + c_vLightAttenuationFactors.z*light_to_world*light_to_world) + c_vLightAttenuationFactors.w;

        const float ANGLE_EPSILON = 0.00001f;
        float angle_factor        = saturate((dot(N, L)-c_fLightFalloffCosTheta)/(1-c_fLightFalloffCosTheta));
        float spot_attenuation    = (angle_factor > ANGLE_EPSILON) ? pow(angle_factor, c_fLightFalloffPower) : 0.0f;

        float3 attenuation        = distance_attenuation*spot_attenuation*shadow_term*dot(N, W);
        float3 ambient            = 0.00001f*saturate(0.5f*(dot(N, L)+1.0f));
        output                   += c_vLightColor*max(attenuation, ambient) * exp(-c_vSigmaExtinction*light_to_world);
    }

    return float4(output, 1);
}

 

 

 

Advertisement

Hi,

just to clarify, of course i have read several post in the internet about the subject, but the problem is that they dont help in the practical shader code development.

Yesterday it stumbeld about this rdidiculous problem i cant understand.


struct VertexShaderOutput
{
    float4 Position       : POSITION0;
    float4 ScreenPosition : TEXCOORD0;
    float4 PositionVS     : TEXCOORD1;
};

here my VertexShader


VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
	VertexShaderOutput output;

    output.PositionVS     = mul(input.Position, WorldView);
    output.Position       = mul(input.Position, WorldViewProj);
    output.ScreenPosition = output.Position;
	return output;
}

so Position and ScreenPosition get the same value.

 

But let us see the pixelShader


   
    input.ScreenPosition.xyz /= input.ScreenPosition.w;  => here division by W ??

    //obtain textureCoordinates corresponding to the current pixel
    //the screen coordinates are in [-1,1]*[1,-1]
    //the texture coordinates need to be in [0,1]*[0,1]
    float2 texCoord = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1);

    int3 texCoordInt = int3(input.TexCoord * Resolution, 0);

    float4 normalData = NormalMap.Load(texCoordInt);   => read gbuffer normal

 

After try and error i found out it goes much easier by this code

 


float4 NormalData = NormalMap.Load(int3(input.Position.xy, 0));

 

Both gives the exactly same result, but why ??

 

Hi,

to make the things crystal clear what i mean


struct VertexShaderOutput
{
    float4 Position       : POSITION0;
    float4 ScreenPosition : TEXCOORD0;
    float4 PositionVS     : TEXCOORD1;
};


Position                  => POSITION0
ScreenPosition       => TEXCOORD0

in VertexShader we have


output.Position       = mul(input.Position, WorldViewProj);
    output.ScreenPosition = output.Position;

But when working with theese Values in the PixelShader

output.Position    <=   has not the same value  as  =>  output.ScreenPosition

both get interpolated but in different ways.  ( which way ?)

When using output.Position with Tag POSITION0 i DONT have to divide by W although i would assume i have to
but using     output.ScreenPosition with Tag TEXCOORD0 i have to divide by W

pleeeaase someone could help me to understand

1 hour ago, evelyn4you said:
 
 
 
 
1 hour ago, evelyn4you said:

float2 texCoord = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1);

int3 texCoordInt = int3(input.TexCoord * Resolution, 0);

Your texCoord is unused. You are actually using input.TexCoord. Besides, your texture coordinate calculation seems not so correct.

1 hour ago, evelyn4you said:

of course i have read several post in the internet about the subject, but the problem is that they dont help in the practical shader code development

If you really read some articles carefully you should understand Perspective Division is used to represent point from Homogeneous coordinates to Cartesian coordinates. Or in a more mathematical point of view, transform an element in projective space to Euclidean space. Because after projection matrix all points in your 3D Euclidean space going to a 4D projective space, you need to transform them back to get your texture coordinates later.

The Perspective Division has been done to the SV_POSITION if you are trying to use it in the pixel shader.

Quote

In Direct3D 10 and later, the SV_Position semantic (when used in the context of a pixel shader) specifies screen space coordinates (offset by 0.5).

Quote

Note to Direct3D 9 developers: For Direct3D 9 targets, shader semantics must map to valid Direct3D 9 semantics. For backwards compatibility POSITION0 (and its variant names) is treated as SV_Position, COLOR is treated as SV_TARGET.

 (https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics)

Quote
4 hours ago, zhangdoa said:

Your texCoord is unused. You are actually using input.TexCoord. Besides, your texture coordinate calculation seems not so correct.

 

this is just a writing/typing error because i wrote the code here and did not copy/paste. In my code certainly  texCoord is used an NOT input.TexCoord

On the one hand, thank you for your reply, but on the other hand, sorry to say it doesnt answer my questions at all.

It is simply the repetition of what i have read many times. Please read my concrete questions.
 

The w is simply because as a mathematics form, if you derive a projection of a point to a camera center position, you can come up with some 3d projection math to do that. To simply into a matrix form and simplify other parts, everyone moved the division/projection to later on. I believe this also is done to make clipping much much easier, but I think it also might simplify the math a little.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

If you specify a vertex function output as POSITION, it performs division by fourth component after all of the trasnformation run, this way

Transformation*4dvector= unnormalized vector

then

unnormalized vector (x,y,z,w)=normalized vector(x/w,y/w,z/w,w)

Thus fourth component of this final normalized vector is the multiplicator to achieve unnormalized vector and de-transform further to view or world space or work with it as you please. If you do not specify vertex output as POSITION, and want to project the vector, you have to do the division yourself.

 

This topic is closed to new replies.

Advertisement