From b6725a3a0040b56156e6d0e41016e7f890e77425 Mon Sep 17 00:00:00 2001 From: Henri Verbeet Date: Thu, 26 Jun 2025 20:52:13 +0200 Subject: [PATCH] demos/teapot: Add diffuse lighting. --- demos/demo_macos.h | 2 + demos/teapot.c | 86 ++++++++++++++++++++++++++++++++++++------- demos/teapot.hlsl | 91 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 161 insertions(+), 18 deletions(-) diff --git a/demos/demo_macos.h b/demos/demo_macos.h index b69c5dcba..a20ef8803 100644 --- a/demos/demo_macos.h +++ b/demos/demo_macos.h @@ -177,6 +177,7 @@ static demo_key demo_key_from_nsevent(id event) enum vkey { kVK_ANSI_A = 0x00, + kVK_ANSI_W = 0x0d, kVK_ANSI_Equal = 0x18, kVK_ANSI_Minus = 0x1b, kVK_Escape = 0x35, @@ -198,6 +199,7 @@ static demo_key demo_key_from_nsevent(id event) lookup[] = { {kVK_ANSI_A, 'a'}, + {kVK_ANSI_W, 'w'}, {kVK_ANSI_Equal, '='}, {kVK_ANSI_Minus, '-'}, {kVK_Escape, DEMO_KEY_ESCAPE}, diff --git a/demos/teapot.c b/demos/teapot.c index 8e68c74fc..e1cdd911a 100644 --- a/demos/teapot.c +++ b/demos/teapot.c @@ -39,6 +39,7 @@ struct teapot_cb_data { struct demo_matrix mvp_matrix; float level; + unsigned int wireframe; }; struct demo_text_run @@ -90,7 +91,7 @@ struct teapot unsigned int text_scale; float theta, phi; - bool display_help; + bool display_help, wireframe; struct timeval last_text; struct timeval frame_times[16]; size_t frame_count; @@ -107,12 +108,12 @@ struct teapot ID3D12CommandAllocator *command_allocator; ID3D12GraphicsCommandList *command_list; } *swapchain_images; - ID3D12DescriptorHeap *rtv_heap; + ID3D12DescriptorHeap *rtv_heap, *dsv_heap; unsigned int rtv_descriptor_size; ID3D12RootSignature *root_signature; ID3D12PipelineState *pipeline_state; - ID3D12Resource *cb, *vb, *ib; + ID3D12Resource *ds, *cb, *vb, *ib; D3D12_VERTEX_BUFFER_VIEW vbv; D3D12_INDEX_BUFFER_VIEW ibv; @@ -423,8 +424,8 @@ static void demo_text_cleanup(struct demo_text *text) static void teapot_populate_command_list(struct teapot *teapot, ID3D12GraphicsCommandList *command_list, unsigned int rt_idx) { + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle, dsv_handle; size_t rotate_idx_count, flip_idx_count; - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; D3D12_RESOURCE_BARRIER barrier; HRESULT hr; @@ -449,10 +450,13 @@ static void teapot_populate_command_list(struct teapot *teapot, rtv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(teapot->rtv_heap); rtv_handle.ptr += rt_idx * teapot->rtv_descriptor_size; - ID3D12GraphicsCommandList_OMSetRenderTargets(command_list, 1, &rtv_handle, FALSE, NULL); + dsv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(teapot->dsv_heap); + ID3D12GraphicsCommandList_OMSetRenderTargets(command_list, 1, &rtv_handle, FALSE, &dsv_handle); ID3D12GraphicsCommandList_ClearRenderTargetView(command_list, rtv_handle, (float[]){1.00f * 0.1f, 0.69f * 0.1f, 0.00f, 1.0f}, 0, NULL); + ID3D12GraphicsCommandList_ClearDepthStencilView(command_list, + dsv_handle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, NULL); ID3D12GraphicsCommandList_IASetPrimitiveTopology(command_list, D3D_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST); ID3D12GraphicsCommandList_IASetIndexBuffer(command_list, &teapot->ibv); ID3D12GraphicsCommandList_IASetVertexBuffers(command_list, 0, 1, &teapot->vbv); @@ -542,9 +546,10 @@ static void teapot_update_text(struct teapot *teapot, double fps) demo_text_draw(text, &amber, 0, -2 * h, "%.2f fps", fps); if (teapot->display_help) { - demo_text_draw(text, &amber, 0, 2 * h, "ESC: Exit"); - demo_text_draw(text, &amber, 0, 1 * h, " F1: Toggle help"); - demo_text_draw(text, &amber, 0, 0 * h, "-/+: Tessellation level (%u)", teapot->tessellation_level); + demo_text_draw(text, &amber, 0, 3 * h, "ESC: Exit"); + demo_text_draw(text, &amber, 0, 2 * h, " F1: Toggle help"); + demo_text_draw(text, &amber, 0, 1 * h, "-/+: Tessellation level (%u)", teapot->tessellation_level); + demo_text_draw(text, &amber, 0, 0 * h, " W: Toggle wireframe (%s)", teapot->wireframe ? "on" : "off"); } a = text->draw_arguments; @@ -579,6 +584,7 @@ static void teapot_destroy_pipeline(struct teapot *teapot) { unsigned int i; + ID3D12DescriptorHeap_Release(teapot->dsv_heap); ID3D12DescriptorHeap_Release(teapot->rtv_heap); for (i = 0; i < demo_swapchain_get_back_buffer_count(teapot->swapchain); ++i) { @@ -642,6 +648,13 @@ static void teapot_load_pipeline(struct teapot *teapot) &IID_ID3D12CommandAllocator, (void **)&teapot->swapchain_images[i].command_allocator); assert(SUCCEEDED(hr)); } + + heap_desc.NumDescriptors = 1; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + hr = ID3D12Device_CreateDescriptorHeap(teapot->device, &heap_desc, + &IID_ID3D12DescriptorHeap, (void **)&teapot->dsv_heap); + assert(SUCCEEDED(hr)); } static void teapot_fence_destroy(struct teapot_fence *teapot_fence) @@ -659,6 +672,7 @@ static void teapot_destroy_assets(struct teapot *teapot) ID3D12Resource_Release(teapot->vb); ID3D12Resource_Unmap(teapot->cb, 0, NULL); ID3D12Resource_Release(teapot->cb); + ID3D12Resource_Release(teapot->ds); for (i = 0; i < demo_swapchain_get_back_buffer_count(teapot->swapchain); ++i) { ID3D12GraphicsCommandList_Release(teapot->swapchain_images[i].command_list); @@ -715,7 +729,11 @@ static void teapot_load_assets(struct teapot *teapot) D3D12_ROOT_SIGNATURE_DESC root_signature_desc; D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc; D3D12_ROOT_PARAMETER root_parameters[1]; - ID3DBlob *vs, *hs, *ds, *ps; + D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle; + D3D12_RESOURCE_DESC resource_desc; + ID3DBlob *vs, *hs, *ds, *gs, *ps; + D3D12_HEAP_PROPERTIES heap_desc; + D3D12_CLEAR_VALUE clear_value; unsigned int i; HRESULT hr; @@ -734,8 +752,7 @@ static void teapot_load_assets(struct teapot *teapot) root_signature_desc.pParameters = root_parameters; root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS - | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS - | D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS; + | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS; hr = demo_create_root_signature(teapot->device, &root_signature_desc, &teapot->root_signature); assert(SUCCEEDED(hr)); @@ -748,6 +765,9 @@ static void teapot_load_assets(struct teapot *teapot) hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", NULL, NULL, "ds_main", "ds_5_0", 0, 0, &ds, NULL); assert(SUCCEEDED(hr)); + hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", + NULL, NULL, "gs_main", "gs_5_0", 0, 0, &gs, NULL); + assert(SUCCEEDED(hr)); hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", NULL, NULL, "ps_main", "ps_5_0", 0, 0, &ps, NULL); assert(SUCCEEDED(hr)); @@ -762,24 +782,31 @@ static void teapot_load_assets(struct teapot *teapot) pso_desc.HS.BytecodeLength = ID3D10Blob_GetBufferSize(hs); pso_desc.DS.pShaderBytecode = ID3D10Blob_GetBufferPointer(ds); pso_desc.DS.BytecodeLength = ID3D10Blob_GetBufferSize(ds); + pso_desc.GS.pShaderBytecode = ID3D10Blob_GetBufferPointer(gs); + pso_desc.GS.BytecodeLength = ID3D10Blob_GetBufferSize(gs); pso_desc.PS.pShaderBytecode = ID3D10Blob_GetBufferPointer(ps); pso_desc.PS.BytecodeLength = ID3D10Blob_GetBufferSize(ps); demo_rasterizer_desc_init_default(&pso_desc.RasterizerState); - pso_desc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME; pso_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; pso_desc.RasterizerState.FrontCounterClockwise = TRUE; demo_blend_desc_init_default(&pso_desc.BlendState); + pso_desc.DepthStencilState.DepthEnable = TRUE; + pso_desc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + pso_desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS; + pso_desc.DepthStencilState.StencilEnable = FALSE; pso_desc.SampleMask = UINT_MAX; pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; pso_desc.NumRenderTargets = 1; pso_desc.RTVFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; + pso_desc.DSVFormat = DXGI_FORMAT_D32_FLOAT; pso_desc.SampleDesc.Count = 1; hr = ID3D12Device_CreateGraphicsPipelineState(teapot->device, &pso_desc, &IID_ID3D12PipelineState, (void **)&teapot->pipeline_state); assert(SUCCEEDED(hr)); ID3D10Blob_Release(ps); + ID3D10Blob_Release(gs); ID3D10Blob_Release(ds); ID3D10Blob_Release(hs); ID3D10Blob_Release(vs); @@ -794,12 +821,42 @@ static void teapot_load_assets(struct teapot *teapot) assert(SUCCEEDED(hr)); } + heap_desc.Type = D3D12_HEAP_TYPE_DEFAULT; + heap_desc.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heap_desc.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heap_desc.CreationNodeMask = 1; + heap_desc.VisibleNodeMask = 1; + + resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resource_desc.Alignment = 0; + resource_desc.Width = teapot->width; + resource_desc.Height = teapot->height; + resource_desc.DepthOrArraySize = 1; + resource_desc.MipLevels = 1; + resource_desc.Format = DXGI_FORMAT_D32_FLOAT; + resource_desc.SampleDesc.Count = 1; + resource_desc.SampleDesc.Quality = 0; + resource_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resource_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + + clear_value.Format = DXGI_FORMAT_D32_FLOAT; + clear_value.DepthStencil.Depth = 1.0f; + clear_value.DepthStencil.Stencil = 0; + + hr = ID3D12Device_CreateCommittedResource(teapot->device, &heap_desc, D3D12_HEAP_FLAG_NONE, &resource_desc, + D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, &IID_ID3D12Resource, (void **)&teapot->ds); + assert(SUCCEEDED(hr)); + + dsv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(teapot->dsv_heap); + ID3D12Device_CreateDepthStencilView(teapot->device, teapot->ds, NULL, dsv_handle); + teapot->cb = create_buffer(teapot->device, sizeof(*teapot->cb_data)); hr = ID3D12Resource_Map(teapot->cb, 0, &(D3D12_RANGE){0, 0}, (void **)&teapot->cb_data); assert(SUCCEEDED(hr)); teapot_update_mvp(teapot); teapot->cb_data->level = teapot->tessellation_level; + teapot->cb_data->wireframe = teapot->wireframe; demo_text_init(&teapot->text, teapot->device, teapot->width, teapot->height, teapot->text_scale); teapot_load_mesh(teapot); @@ -824,6 +881,9 @@ static void teapot_key_press(struct demo_window *window, demo_key key, void *use if (teapot->tessellation_level < D3D12_TESSELLATOR_MAX_TESSELLATION_FACTOR) teapot->cb_data->level = ++teapot->tessellation_level; break; + case 'w': + teapot->cb_data->wireframe = teapot->wireframe = !teapot->wireframe; + break; case DEMO_KEY_ESCAPE: demo_window_destroy(window); break; @@ -889,7 +949,7 @@ static int teapot_main(void) teapot.width = width; teapot.height = height; - teapot.tessellation_level = 4; + teapot.tessellation_level = 10; teapot.text_scale = (1.25 * dpi_y / 96.0) + 0.5; teapot.theta = M_PI / 2.0f; diff --git a/demos/teapot.hlsl b/demos/teapot.hlsl index 87675fe8f..ee6817ead 100644 --- a/demos/teapot.hlsl +++ b/demos/teapot.hlsl @@ -16,10 +16,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#define M_PI 3.14159265 + cbuffer teapot_cb : register(b0) { float4x4 mvp_matrix; float level; + uint wireframe; }; struct control_point @@ -33,9 +36,19 @@ struct patch_constant_data float inside[2] : SV_InsideTessFactor; }; +struct gs_in +{ + float4 position : SV_POSITION; + float3 pos : POSITION; + float3 normal : NORMAL; +}; + struct ps_in { float4 position : SV_POSITION; + float3 pos : POSITION; + float3 normal : NORMAL; + float2 barycentric : BARYCENTRIC; }; float4 vs_main(float4 position : POSITION, uint id : SV_InstanceID) : SV_POSITION @@ -92,30 +105,98 @@ float3 eval_cubic(float3 p0, float3 p1, float3 p2, float3 p3, float t) eval_quadratic(p1, p2, p3, t), t); } -struct ps_in eval_patch(float2 t, float4 p[16]) +struct gs_in eval_patch(float2 t, float4 p[16]) { - float3 position, q[4]; - struct ps_in o; + float3 position, normal, q[4], u, v; + struct gs_in o; + + q[0] = eval_cubic( p[0].xyz, p[1].xyz, p[2].xyz, p[3].xyz, t.x); + q[1] = eval_cubic( p[4].xyz, p[5].xyz, p[6].xyz, p[7].xyz, t.x); + q[2] = eval_cubic( p[8].xyz, p[9].xyz, p[10].xyz, p[11].xyz, t.x); + q[3] = eval_cubic(p[12].xyz, p[13].xyz, p[14].xyz, p[15].xyz, t.x); + u = eval_quadratic(q[0], q[1], q[2], t.y) - eval_quadratic(q[1], q[2], q[3], t.y); q[0] = eval_cubic(p[0].xyz, p[4].xyz, p[8].xyz, p[12].xyz, t.y); q[1] = eval_cubic(p[1].xyz, p[5].xyz, p[9].xyz, p[13].xyz, t.y); q[2] = eval_cubic(p[2].xyz, p[6].xyz, p[10].xyz, p[14].xyz, t.y); q[3] = eval_cubic(p[3].xyz, p[7].xyz, p[11].xyz, p[15].xyz, t.y); + v = eval_quadratic(q[0], q[1], q[2], t.x) - eval_quadratic(q[1], q[2], q[3], t.x); position = eval_cubic(q[0], q[1], q[2], q[3], t.x); o.position = mul(mvp_matrix, float4(position, 1.0)); + o.pos = position; + + /* The patches for the bottom of the teapot and the top of its lid are + * degenerate. Technically this isn't the right way to deal with that, but + * it's easy and gets the right result for these patches. */ + if (length(v) == 0.0) + normal = cross(p[4].xyz - p[0].xyz, p[7].xyz - p[3].xyz); + else + normal = cross(u, v); + o.normal = normalize(normal); return o; } [domain("quad")] -struct ps_in ds_main(struct patch_constant_data input, +struct gs_in ds_main(struct patch_constant_data input, float2 tess_coord : SV_DomainLocation, const OutputPatch patch) { return eval_patch(tess_coord, patch); } +[maxvertexcount(3)] +void gs_main(triangle struct gs_in i[3], inout TriangleStream stream) +{ + struct ps_in v[3]; + float3 n; + + v[0].position = i[0].position; + v[0].pos = i[0].pos; + v[0].normal = i[0].normal; + v[0].barycentric = float2(1.0, 0.0); + + v[1].position = i[1].position; + v[1].pos = i[1].pos; + v[1].normal = i[1].normal; + v[1].barycentric = float2(0.0, 1.0); + + v[2].position = i[2].position; + v[2].pos = i[2].pos; + v[2].normal = i[2].normal; + v[2].barycentric = float2(0.0, 0.0); + + stream.Append(v[0]); + stream.Append(v[1]); + stream.Append(v[2]); +} + +/* Lambertian diffuse. */ +float3 brdf_lambert(float3 diffuse) +{ + return diffuse / M_PI; +} + float4 ps_main(struct ps_in i) : SV_TARGET { - return float4(1.0, 0.69, 0.0, 1.0); + float3 light_dir = normalize(float3(5.0, 5.0, 10.0)); + float3 light_colour = float3(1.0, 0.95, 0.88); + float3 light_irradiance = 5.0 * light_colour; + float3 barycentric, diffuse_colour, radiance; + float3 base_colour = float3(0.8, 0.8, 0.8); + float3 ambient = 0.3 * light_colour; + float cos_theta_i, wire; + float metallic = 0.3; + + diffuse_colour = base_colour * (1.0 - metallic); + cos_theta_i = saturate(dot(normalize(i.normal), light_dir)); + /* Cook-Torrance. */ + radiance = brdf_lambert(diffuse_colour) * light_irradiance * cos_theta_i; + radiance += ambient * base_colour; + + barycentric = float3(i.barycentric, 1.0 - (i.barycentric.x + i.barycentric.y)); + barycentric /= fwidth(barycentric); + wire = wireframe ? min(min(barycentric.x, barycentric.y), barycentric.z) : 1.0; + + return float4(lerp(float3(1.00, 0.69, 0.0), saturate(radiance), saturate(wire)), 1.0); }