You should definitely try to calculate your normals etc in the domain shader. This is the 3rd and final stage of the optional tessellation stages and is specifically used to calculate the final vertex position and data of the subdivided point. Because you are using the tessellation pipline the domain shader is going to be called no matter what, whereas the geometry shader stage is still optional and will incur additional cost (even for an empty shader).
Depending on the domain (tri or quad ) you may need to use barycentric, bilinear or bicubic interpolation to determine the correct values.
You will want to implement backface culling and/or dynamic LoD within the hull shader.
Below is an example domain shader taken from Chapter 5: Applying Hardware Tessellation of my book Direct3D Rendering Cookbook. It uses bilinear interpolation and a combination of patch and constant data for the inputs:
// This domain shader applies control point weighting with bilinear interpolation using the SV_DomainLocation
[domain("quad")]
PixelShaderInput DS_Quads( HS_QuadPatchConstant constantData, const OutputPatch<DS_ControlPointInput, 4> patch, float2 uv : SV_DomainLocation )
{
PixelShaderInput result = (PixelShaderInput)0;
// Interpolate using bilerp
float4 c[4];
float3 p[4];
[unroll]
for(uint i=0;i<4;i++) {
p[i] = patch[i].Position;
c[i] = patch[i].Diffuse;
}
float3 position = Bilerp(p, uv);
float2 UV = Bilerp(constantData.TextureUV, uv);
float4 diffuse = Bilerp(c, uv);
float3 normal = Bilerp(constantData.NormalW, uv);
// Prepare pixel shader input:
// Transform world position to view-projection
result.PositionV = mul( float4(position,1), ViewProjection );
result.Diffuse = diffuse;
result.UV = UV;
result.NormalW = normal;
result.PositionW = position;
return result;
}
And bilinear interpolation on float2, float3 and float4 properties for the simple quad domain:
//*********************************************************
// QUAD bilinear interpolation
float2 Bilerp(float2 v[4], float2 uv)
{
// bilerp the float2 values
float2 side1 = lerp( v[0], v[1], uv.x );
float2 side2 = lerp( v[3], v[2], uv.x );
float2 result = lerp( side1, side2, uv.y );
return result;
}
float3 Bilerp(float3 v[4], float2 uv)
{
// bilerp the float3 values
float3 side1 = lerp( v[0], v[1], uv.x );
float3 side2 = lerp( v[3], v[2], uv.x );
float3 result = lerp( side1, side2, uv.y );
return result;
}
float4 Bilerp(float4 v[4], float2 uv)
{
// bilerp the float4 values
float4 side1 = lerp( v[0], v[1], uv.x );
float4 side2 = lerp( v[3], v[2], uv.x );
float4 result = lerp( side1, side2, uv.y );
return result;
}
.
For tri domains you would use barycentric interpolation - something like the following:
//*********************************************************
// TRIANGLE interpolation (using Barycentric coordinates)
/*
barycentric.xyz == uvw
w=1-u-v
P=w*A+u*B+v*C
C ______________ B
\. w . /
\ . . /
\ P /
\u . v/
\ . /
\ ./
\/
A
*/
float2 BarycentricInterpolate(float2 v0, float2 v1, float2 v2, float3 barycentric)
{
return barycentric.z * v0 + barycentric.x * v1 + barycentric.y * v2;
}
float3 BarycentricInterpolate(float3 v0, float3 v1, float3 v2, float3 barycentric)
{
return barycentric.z * v0 + barycentric.x * v1 + barycentric.y * v2;
}
float4 BarycentricInterpolate(float4 v0, float4 v1, float4 v2, float3 barycentric)
{
return barycentric.z * v0 + barycentric.x * v1 + barycentric.y * v2;
}
Good luck.