shadow_mapping.asm

src/5.advanced_lighting/3.1.shadow_mapping/shadow_mapping.asm · 282 lines

;---------------------------------------------------------------------------------------------------
; 3.1 Shadow mapping
;---------------------------------------------------------------------------------------------------
; A point is in shadow if something nearer to the light covers it. So: draw the scene once from the
; light, keeping only depth. That depth texture, the shadow map, records the nearest surface the
; light sees in every direction. Then draw normally, and for each pixel ask: transformed into the
; light's view, is this point farther than what the map recorded? Then it is behind something, and
; only ambient light reaches it.
;
; The sun is directional, so its view uses an orthographic projection: a box around the scene
; rather than a frustum.
;
; What D3D does for us here:
;
;   * the shadow map is one typeless texture seen two ways: as a depth buffer while it is drawn,
;     as a float texture while it is read (includes/learn/shadow.asm)
;   * shadow acne, a surface shadowing itself because the map's texels are coarser than the
;     screen's, is fixed by the rasterizer's slope-scaled depth bias during the shadow pass, not by
;     a constant in the shader
;   * the comparison sampler does the depth test and blends four texels of results inside
;     SampleCmp; 3x3 of those give soft edges (PCF)
;
;   Tab   the scene / the shadow map itself
include rt.inc
include d3d11.inc
include macros.asm
include math.asm
include learn\window.asm
include learn\shader.asm
include learn\gpu.asm
include learn\cube.asm
include learn\camera.asm
include learn\transforms.asm
include learn\shadow.asm
include learn\srgb.asm

;---------------------------------------------------------------------------------------------------
.data
;---------------------------------------------------------------------------------------------------
align 4
plane_vertices label byte
    real4 -12.5, -0.5,  12.5,   0.0, 1.0, 0.0,    0.0, 12.0
    real4  12.5, -0.5,  12.5,   0.0, 1.0, 0.0,   12.0, 12.0
    real4  12.5, -0.5, -12.5,   0.0, 1.0, 0.0,   12.0,  0.0
    real4 -12.5, -0.5,  12.5,   0.0, 1.0, 0.0,    0.0, 12.0
    real4  12.5, -0.5, -12.5,   0.0, 1.0, 0.0,   12.0,  0.0
    real4 -12.5, -0.5, -12.5,   0.0, 1.0, 0.0,    0.0,  0.0
plane_vertices_size equ 6 * 32

; Mirrors `cbuffer shadow` in shadow_mapping.hlsl.
shadow_constants struct
    light_space mat4 <>
    light_direction vec3 <>     ; where the sunlight travels
    pad real4 ?
shadow_constants ends

shadow_size equ 2048

align 4
camera_start vec3 <-2.5, 3.0, 6.5>
origin vec3 <0.0, 0.0, 0.0>
up_axis vec3 <0.0, 1.0, 0.0>
tilt_axis vec3 <1.0, 0.0, 1.0>
cube_place_a vec3 <0.0, 1.5, 0.0>
cube_place_b vec3 <2.0, 0.0, 1.0>
cube_place_c vec3 <-1.0, 0.0, 2.0>
half_size vec3 <0.5, 0.5, 0.5>
light_position vec3 <>

align 16
t transforms <>
sh shadow_constants <>
cubes mat4 3 dup(<>)
light_view mat4 <>
light_projection mat4 <>
model mat4 <>
scratch mat4 <>
s shader <>
depth_shader shader <>
debug_shader shader <>
cam camera <>
depth_map shadow_map <>
align 8
cube_vb qword 0
plane_vb qword 0
transforms_buffer qword 0
shadow_buffer qword 0
wood qword 0
sampler qword 0
compare_sampler qword 0
bias_state qword 0

; timing
current_frame real4 0.0
delta_time real4 0.0
last_frame real4 0.0

;---------------------------------------------------------------------------------------------------
.code
;---------------------------------------------------------------------------------------------------
; The floor and the three cubes, with whatever shader and view are bound.
align 16
draw_scene proc
    USING rbx, rsi
    SaveRegs
    invoke m4_identity, ADDR model
    invoke draw_vertices, transforms_buffer, ADDR t, plane_vb, 6, ADDR model
    lea rsi, cubes
    xor ebx, ebx
    each_cube:
        invoke draw_vertices, transforms_buffer, ADDR t, cube_vb, 36, rsi
        add rsi, sizeof mat4
        inc ebx
        cmp ebx, 3
        jb each_cube
    RestoreRegs
    ret
draw_scene endp

align 16
main proc
    invoke window_create, "LearnD3D11  (Tab: scene / shadow map)"
    ; The back buffer is viewed as sRGB: what the shaders write is linear, and the view
    ; gamma-encodes it on the way in (5.2).
    call window_use_srgb
    call window_create_depth

    invoke create_sampler, D3D11_FILTER_ANISOTROPIC, D3D11_TEXTURE_ADDRESS_WRAP
    mov sampler, rax
    vcall context, ID3D11DeviceContext_PSSetSamplers, 0, 1, ADDR sampler

    ; The shadow pass's rasterizer: push depth away from the light, more on slopes seen at a
    ; grazing angle.
    call shadow_bias_state
    mov bias_state, rax

    ; build and compile shaders
    ; -------------------------
    invoke shader_create, ADDR s, "src/5.advanced_lighting/3.1.shadow_mapping/shadow_mapping.hlsl", ADDR cube_layout, cube_layout_count
    invoke shader_create, ADDR depth_shader, "src/5.advanced_lighting/3.1.shadow_mapping/shadow_mapping_depth.hlsl", ADDR cube_layout, cube_layout_count
    invoke shader_create, ADDR debug_shader, "src/5.advanced_lighting/3.1.shadow_mapping/debug_quad.hlsl", 0, 0

    ; set up vertex data and textures
    ; -------------------------------
    invoke create_buffer, D3D11_BIND_VERTEX_BUFFER, ADDR cube_vertices, cube_vertices_size
    mov cube_vb, rax
    invoke create_buffer, D3D11_BIND_VERTEX_BUFFER, ADDR plane_vertices, plane_vertices_size
    mov plane_vb, rax
    invoke create_constant_buffer, sizeof transforms
    mov transforms_buffer, rax
    invoke create_constant_buffer, sizeof shadow_constants
    mov shadow_buffer, rax
    invoke filesystem_path, "resources/textures/wood.png"
    invoke load_texture, rax, 1
    mov wood, rax
    invoke create_shadow_map, ADDR depth_map, shadow_size
    call create_shadow_sampler
    mov compare_sampler, rax

    lea rcx, cam
    lea rdx, camera_start
    movf xmm2, -65.0
    movf xmm3, -25.0
    call camera_create

    ; three cubes: one floating, one on the floor, one small and tilted
    invoke m4_translate, ADDR cubes[0], ADDR cube_place_a
    invoke m4_translate, ADDR cubes[sizeof mat4], ADDR cube_place_b
    invoke m4_scale, ADDR scratch, ADDR half_size
    movf xmm0, 60.0
    call radians
    movaps xmm1, xmm0
    lea rcx, model
    lea r8, tilt_axis
    call m4_rotate
    invoke m4_mul, ADDR scratch, ADDR model, ADDR scratch
    invoke m4_translate, ADDR model, ADDR cube_place_c
    invoke m4_mul, ADDR cubes[2 * sizeof mat4], ADDR model, ADDR scratch

    ; The sun's view never changes shape: a 20 x 20 box, 1 to 20 units from it.
    movf xmm0, 10.0
    movss real4 ptr [rsp + 32], xmm0        ; top
    movf xmm0, 1.0
    movss real4 ptr [rsp + 40], xmm0        ; z_near
    movf xmm0, 20.0
    movss real4 ptr [rsp + 48], xmm0        ; z_far
    lea rcx, light_projection
    movf xmm1, -10.0                        ; left
    movf xmm2, 10.0                         ; right
    movf xmm3, -10.0                        ; bottom
    call m4_ortho

    render_loop:
        call window_poll
        test eax, eax
        jz render_loop_done

        ; per-frame time logic
        call get_time
        movss current_frame, xmm0
        movss xmm1, xmm0
        subss xmm0, last_frame
        movss delta_time, xmm0
        movss last_frame, xmm1

        invoke camera_process_input, ADDR cam, ADDR delta_time
        invoke window_mode, 2
        mov ebx, eax                            ; 1: show the shadow map

        ; the sun circles slowly, so the shadows sweep across the floor:
        ; (-4 cos(0.3 t), 8, -2 - 2 sin(0.3 t))
        movss xmm0, current_frame
        movf xmm1, 0.3
        mulss xmm0, xmm1
        call cosf
        movf xmm1, -4.0
        mulss xmm0, xmm1
        movss light_position.x, xmm0
        storef light_position.y, 8.0
        movss xmm0, current_frame
        movf xmm1, 0.3
        mulss xmm0, xmm1
        call sinf
        movf xmm1, -2.0
        mulss xmm0, xmm1
        addss xmm0, xmm1
        movss light_position.z, xmm0
        invoke m4_look_at, ADDR light_view, ADDR light_position, ADDR origin, ADDR up_axis
        invoke m4_mul, ADDR sh.light_space, ADDR light_projection, ADDR light_view
        ; light_direction = normalize(-light_position)
        lea rcx, sh.light_direction
        lea rdx, light_position
        movf xmm2, -1.0
        call v3_scale
        lea rcx, sh.light_direction
        mov rdx, rcx
        call v3_normalize
        invoke update_buffer, shadow_buffer, ADDR sh, sizeof shadow_constants
        vcall context, ID3D11DeviceContext_IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
        invoke bind_constants, 0, transforms_buffer

        ; 1. render depth of scene to texture (from light's perspective)
        ; --------------------------------------------------------------
        call unbind_textures
        vcall context, ID3D11DeviceContext_OMSetRenderTargets, 0, 0, depth_map.dsv
        movf xmm3, 1.0                          ; the depth, a float: xmm3
        vcall context, ID3D11DeviceContext_ClearDepthStencilView, depth_map.dsv, D3D11_CLEAR_DEPTH, 0, 0
        invoke set_viewport, shadow_size, shadow_size
        vcall context, ID3D11DeviceContext_RSSetState, bias_state
        invoke mem_copy, ADDR t.view, ADDR light_view, sizeof mat4
        invoke mem_copy, ADDR t.projection, ADDR light_projection, sizeof mat4
        invoke shader_use, ADDR depth_shader
        call draw_scene
        vcall context, ID3D11DeviceContext_RSSetState, raster_state

        ; 2. render scene as normal using the generated depth/shadow map
        ; --------------------------------------------------------------
        invoke window_begin, floats(0.1, 0.1, 0.1, 1.0)
        test ebx, ebx
        jz scene
            invoke shader_use, ADDR debug_shader
            invoke bind_texture, 0, depth_map.srv
            vcall context, ID3D11DeviceContext_Draw, 3, 0
            jmp drawn
        scene:
            invoke transforms_from_camera, ADDR t, ADDR cam
            invoke shader_use, ADDR s
            invoke bind_constants, 1, shadow_buffer
            invoke bind_texture, 0, wood
            invoke bind_texture, 1, depth_map.srv
            vcall context, ID3D11DeviceContext_PSSetSamplers, 1, 1, ADDR compare_sampler
            call draw_scene
        drawn:

        call window_present
        jmp render_loop
    render_loop_done:

    invoke ExitProcess, 0
main endp

end
Part of masm64-dx11, LearnOpenGL's lessons rewritten for Direct3D 11 in x64 assembly. Licensed CC BY-NC 4.0, after LearnOpenGL by Joey de Vries.