Good morning, I am currently running into a UV interpolation issue with D3D12 that looks like this:
Is there something that needs to be set when initialising the pipeline so that the GPU does the correct interpolation or do I have to do it myself? I have seen several examples of textured 3D objects that looked fine and the associated code did not seem to do anything specific to compute the correct UVs manually, so I guess I just forgot to set a flag or something?
EDIT: here is some additional information (shaders are trimmed to the core):
// Input data layout:
D3D12_INPUT_ELEMENT_DESC mesh_layout[NUM_ELEMENTS_IN_MESH_LAYOUT] = {
{"Vertex_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"Normal", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"U_Axis", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"V_Axis", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"Uv_Coords", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Scale", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Orientation", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Colour", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Shadow_Map_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Shadow_Map_X_Axis", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Shadow_Map_Y_Axis", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Shadow_Map_Z_Axis", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Shadow_Map_Uvs", 0, DXGI_FORMAT_R32G32_FLOAT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
{"Material_Index", 0, DXGI_FORMAT_R32_UINT, 1, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 0},
};
/*
The vertex buffer looks like this (only positions and UVs are represented here as this is what interests us):
{ // First triangle.
{ 1, -1, 0}, {1, 0},
{-1, 1, 0}, {0, 1},
{-1, -1, 0}, {0, 0},
},
{ // Second triangle.
{ 1, -1, 0}, {1, 0},
{ 1, 1, 0}, {1, 1},
{-1, 1, 0}, {0, 1},
}
*/
// Vertex shader:
ConstantBuffer<Renderer_Info> renderer_info : register(b0, space2);
StructuredBuffer<Material> materials : register(t0, space2);
struct To_Pixel_Shader
{
v4 position : SV_Position;
v4 colour : Colour;
v3 origin : Origin;
v3 position_3d : Position_3d;
v3 normal : Normal;
v2 uvs : Uv_Coords;
v3 camera_view_direction : Camera_View_Direction;
u32 material_index : Material_Index;
f32 scale : Uniform_Scale;
};
To_Pixel_Shader main(
v3 vertex_position : Vertex_Position,
v3 normal : Normal,
v3 u_axis : U_Axis,
v3 v_axis : V_Axis,
v2 uvs : Uv_Coords,
v3 position : Position,
v3 scale : Scale,
v4 orientation : Orientation,
v4 colour : Colour,
v3 shadow_map_position : Shadow_Map_Position,
v3 shadow_map_x_axis : Shadow_Map_X_Axis,
v3 shadow_map_y_axis : Shadow_Map_Y_Axis,
v3 shadow_map_z_axis : Shadow_Map_Z_Axis,
v2 shadow_map_uvs : Shadow_Map_Uvs,
u32 material_index : Material_Index
)
{
To_Pixel_Shader result;
v2 rt_dimensions = {renderer_info.rt_width, renderer_info.rt_height};
f32 rt_ratio = rt_dimensions.y / rt_dimensions.x;
// Calculate the vertex world position. START
v4 orientation_inverse = quaternion_inverse_no_norm(orientation); // @ This could be given in the mesh instance along with the orientation.
vertex_position *= scale;
vertex_position = rotate_point(orientation, vertex_position, orientation_inverse);
vertex_position += position;
result.position_3d = vertex_position;
// Calculate the vertex world position. END
// Calculate the vertex screen position. START
v3 eye_to_vertex = vertex_position - renderer_info.camera_eye_position;
result.camera_view_direction = eye_to_vertex; // This is normalised in the pixel shader.
f32 vertex_dot_camera_z = -dot(eye_to_vertex, renderer_info.camera_z_direction);
f32 distance_to_vertex = distance(renderer_info.camera_eye_position, vertex_position);
f32 triangle_ratio = renderer_info.camera_eye_offset / vertex_dot_camera_z;
f32 MAX_DEPTH_DISTANCE = 1000.0f;
f32 NEAR_CLIP_PLANE = 1;
v3 vertex_position_on_screen = {
dot(eye_to_vertex, renderer_info.camera_x_direction),
dot(eye_to_vertex, renderer_info.camera_y_direction),
(distance_to_vertex - NEAR_CLIP_PLANE) / MAX_DEPTH_DISTANCE
};
vertex_position_on_screen.xy *= triangle_ratio;
vertex_position_on_screen.x *= rt_ratio;
///////////////////////////////////////////////////////////////////////////////////////////
// NOTE: @ for now we do not take vertices lying behind the near clip plane into account!!!
///////////////////////////////////////////////////////////////////////////////////////////
result.screen_depth = vertex_position_on_screen.z;
// Calculate the vertex screen position. END
result.origin = position;
result.position = v4(vertex_position_on_screen.xyz, 1);
result.normal = rotate_point(orientation, normal, orientation_inverse);
result.uvs = uvs;
result.material_index = material_index;
result.colour = colour;
result.scale = scale.x;
return result;
}
// Pixel shader:
Texture2D<v4> colour_atlas : register(t0, space3);
StructuredBuffer<Atlas_Texture> colour_textures : register(t1, space3);
ConstantBuffer<Renderer_Info> renderer_info : register(b0, space3);
StructuredBuffer<Material> materials : register(t2, space3);
Texture2D<f32> shadow_map_atlas : register(t3, space3);
sampler bilinear_sampler : register(s0);
sampler point_sampler : register(s1);
v4 main(
v4 position : SV_Position,
v4 colour : Colour,
v3 origin : Origin,
v3 position_3d : Position_3d,
v3 normal : Normal,
v2 uvs : Uv_Coords,
v3 camera_view_direction : Camera_View_Direction,
u32 material_index : Material_Index,
f32 scale : Uniform_Scale
) : SV_Target
{
u32 real_material_index = material_index & 0x00ffFFff; // The top 8 bits are reserved for flags.
Material material = materials[real_material_index];
colour *= material.colour;
if(material.flags & TEXTURED)
{
Atlas_Texture t = colour_textures[material.texture_index];
v2 sample_uvs = uvs * t.uv_ratio + t.uv_offset_in_atlas;
colour *= colour_atlas.Sample(bilinear_sampler, sample_uvs);
}
return linear_to_srgb(colour);
}