colors.rin
src/2.lighting/1.colors/colors.rin · 375 lines
// 1. Colors
//
// The colour we see is the light's colour times the colour the surface
// reflects. A coral object under white light stays coral; under green light it
// turns dark green, because coral reflects little green.
//
// The scene is the one the lighting chapter keeps: an object, and a small cube
// standing in for the light. The lamp has a shader of its own, so nothing done
// to the lighting ever darkens it.
import "../../../includes/win32.rin"
import "../../../includes/math.rin"
import "../../../includes/learn/shader.rin"
import "../../../includes/learn/camera.rin"
import "std/print.rin"
// settings
scr_width: i32 = 1280;
scr_height: i32 = 720;
// a unit cube: position, normal, texture coordinate; counter-clockwise from outside
cube_vertices: [288]f32 = {
// right (+x)
0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 1.0,
0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 1.0,
0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 1.0,
0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0,
// left (-x)
-0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0,
-0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 1.0,
-0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0,
-0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0,
-0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0,
-0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 0.0,
// top (+y)
-0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0,
0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0,
0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0,
0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 0.0,
// bottom (-y)
-0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0,
0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 1.0,
0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0,
-0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0,
0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0,
-0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.0, 0.0,
// front (+z)
-0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0,
0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0,
0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0,
0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0,
// back (-z)
0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0,
-0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0,
};
vertex_layout: [3]D3D11_INPUT_ELEMENT_DESC = {
{ .SemanticName = "POSITION", .Format = DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT, .AlignedByteOffset = 0 },
{ .SemanticName = "NORMAL", .Format = DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT, .AlignedByteOffset = 12 },
{ .SemanticName = "TEXCOORD", .Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT, .AlignedByteOffset = 24 },
};
colors_constants: struct = {
object_color: vec3;
pad0: f32;
light_color: vec3;
pad1: f32;
}
// Mirrors `cbuffer transforms` in the shaders, register for register.
transforms: struct = {
model: mat4;
normal_matrix: mat4; // inverse transpose of model, for normals
view: mat4;
projection: mat4;
view_position: vec3; // the eye, in world space
pad: f32;
}
// Draws the 36 vertices of the bound cube with `model`.
draw_cube: proc(context: *ID3D11DeviceContext, buffer: *ID3D11Buffer, t: *transforms, model: mat4) -> void = {
t[0].model = model;
t[0].normal_matrix = m4_normal_matrix(model);
update_buffer(context, buffer, t, sizeof(transforms));
ID3D11DeviceContext_Draw(context, 36, 0);
}
// camera
cam: camera = {};
last_cursor: POINT = {};
scroll: f32 = 0.0;
// timing
delta_time: f32 = 0.0;
last_frame: f32 = 0.0;
perf_frequency: i64 = 0;
perf_start: i64 = 0;
// Seconds since the first call: what glfwGetTime is to LearnOpenGL.
get_time: proc() -> f32 = {
now: LARGE_INTEGER = {};
if (perf_frequency == 0) {
QueryPerformanceFrequency(now&);
perf_frequency = now.QuadPart;
QueryPerformanceCounter(now&);
perf_start = now.QuadPart;
}
QueryPerformanceCounter(now&);
return cast(now.QuadPart - perf_start, f32) / cast(perf_frequency, f32);
}
// win32: every event for the window (keys, mouse, closing) arrives here
// ---------------------------------------------------------------------
window_proc: proc[callconv(CALLBACK)](hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT = {
if (msg == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
if (msg == WM_MOUSEWHEEL) {
// The high word of wparam is the distance turned, 120 per notch.
scroll += cast(cast((wparam shr 16) & 0xffff, i16), f32) / 120.0;
return 0;
}
return DefWindowProcA(hwnd, msg, wparam, lparam);
}
// process all input: ask Windows whether relevant keys are down this frame and react accordingly
// ----------------------------------------------------------------------------------------------
// The top bit of GetAsyncKeyState is set while a key is held, which makes the SHORT negative.
key_down: proc(key: i32) -> b32 = {
return GetAsyncKeyState(key) < 0;
}
process_input: proc(window: HWND) -> void = {
cursor: POINT = {};
GetCursorPos(cursor&);
// GetAsyncKeyState reads the whole keyboard, so ignore it while another
// window has the focus; glfwGetKey only ever sees its own window's keys.
if (GetForegroundWindow() != window) {
last_cursor = cursor;
scroll = 0.0;
return;
}
if (key_down(VK_ESCAPE)) { DestroyWindow(window); }
if (key_down('W')) { camera_process_keyboard(cam&, camera_movement.forward, delta_time); }
if (key_down('S')) { camera_process_keyboard(cam&, camera_movement.backward, delta_time); }
if (key_down('A')) { camera_process_keyboard(cam&, camera_movement.left, delta_time); }
if (key_down('D')) { camera_process_keyboard(cam&, camera_movement.right, delta_time); }
if (key_down(VK_SPACE)) { camera_process_keyboard(cam&, camera_movement.up, delta_time); }
if (key_down(VK_SHIFT)) { camera_process_keyboard(cam&, camera_movement.down, delta_time); }
// Hold the right mouse button to look around.
if (key_down(VK_RBUTTON)) {
// Screen y grows downwards, so moving the mouse up is a negative dy: pitch up.
camera_process_mouse(cam&, cast(cursor.x - last_cursor.x, f32), cast(last_cursor.y - cursor.y, f32));
}
last_cursor = cursor;
camera_process_scroll(cam&, scroll);
scroll = 0.0;
}
// An immutable buffer: filled once at creation, never written by the CPU again.
create_buffer: proc(device: *ID3D11Device, bind: UINT, data: *const void, size: u64) -> *ID3D11Buffer = {
desc: D3D11_BUFFER_DESC = {
.ByteWidth = cast(size, UINT),
.Usage = D3D11_USAGE.D3D11_USAGE_IMMUTABLE,
.BindFlags = bind,
};
init: D3D11_SUBRESOURCE_DATA = { .pSysMem = data };
buffer: *ID3D11Buffer = null;
ID3D11Device_CreateBuffer(device, desc&, init&, buffer&);
return buffer;
}
// A constant buffer the CPU rewrites, usually every frame. Its size must be a
// multiple of 16 bytes, the size of one HLSL register.
create_constant_buffer: proc(device: *ID3D11Device, size: u64) -> *ID3D11Buffer = {
desc: D3D11_BUFFER_DESC = {
.ByteWidth = cast(size, UINT),
.Usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC,
.BindFlags = D3D11_BIND_CONSTANT_BUFFER,
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
};
buffer: *ID3D11Buffer = null;
ID3D11Device_CreateBuffer(device, desc&, null, buffer&);
return buffer;
}
// WRITE_DISCARD hands back fresh memory, so the GPU never waits on the CPU.
update_buffer: proc(context: *ID3D11DeviceContext, buffer: *ID3D11Buffer, data: *const void, size: u64) -> void = {
mapped: D3D11_MAPPED_SUBRESOURCE = {};
ID3D11DeviceContext_Map(context, cast(buffer, *ID3D11Resource), 0, D3D11_MAP.D3D11_MAP_WRITE_DISCARD, 0, mapped&);
memcpy(mapped.pData, data, size);
ID3D11DeviceContext_Unmap(context, cast(buffer, *ID3D11Resource), 0);
}
// The same buffer in the same register for the vertex, geometry and pixel stages.
bind_constants: proc(context: *ID3D11DeviceContext, slot: UINT, buffer: *ID3D11Buffer) -> void = {
buffers: [1]*ID3D11Buffer = { buffer };
ID3D11DeviceContext_VSSetConstantBuffers(context, slot, 1, buffers[0]&);
ID3D11DeviceContext_GSSetConstantBuffers(context, slot, 1, buffers[0]&);
ID3D11DeviceContext_PSSetConstantBuffers(context, slot, 1, buffers[0]&);
}
main: proc() -> i32 = {
// win32: register a window class and create the window
// ----------------------------------------------------
SetProcessDPIAware();
instance: HMODULE = GetModuleHandleA(null);
wc: WNDCLASSA = {
.lpfnWndProc = window_proc,
.hInstance = instance,
.hCursor = LoadCursorA(null, IDC_ARROW),
.lpszClassName = "LearnD3D11",
};
RegisterClassA(wc&);
// A fixed size: the swap chain and every render target keep the size they
// were created with, so nothing needs rebuilding on resize.
style: DWORD = WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
rect: RECT = { .right = scr_width, .bottom = scr_height };
AdjustWindowRect(rect&, style, 0);
window: HWND = CreateWindowExA(0, wc.lpszClassName, "LearnD3D11", style | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top, null, null, instance, null);
if (window == null) {
printf("Failed to create the window\n");
return -1;
}
// d3d11: create the device, its immediate context and a swap chain
// ----------------------------------------------------------------
swap_desc: DXGI_SWAP_CHAIN_DESC = {
.BufferDesc = {
.Width = cast(scr_width, UINT),
.Height = cast(scr_height, UINT),
.Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
},
.SampleDesc = { .Count = 1 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.OutputWindow = window,
.Windowed = 1,
.SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_DISCARD,
};
feature_level: D3D_FEATURE_LEVEL = D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0;
swap_chain: *IDXGISwapChain = null;
device: *ID3D11Device = null;
context: *ID3D11DeviceContext = null;
if (D3D11CreateDeviceAndSwapChain(null, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, null, 0, feature_level&, 1, D3D11_SDK_VERSION,
swap_desc&, swap_chain&, device&, null, context&) < 0) {
printf("Failed to create the D3D11 device\n");
return -1;
}
backbuffer: *ID3D11Texture2D = null;
IDXGISwapChain_GetBuffer(swap_chain, 0, IID_ID3D11Texture2D&, cast(backbuffer&, **void));
backbuffer_rtv: *ID3D11RenderTargetView = null;
ID3D11Device_CreateRenderTargetView(device, cast(backbuffer, *ID3D11Resource), null, backbuffer_rtv&);
// A depth buffer the size of the window: 24 bits of depth, 8 of stencil.
depth_desc: D3D11_TEXTURE2D_DESC = {
.Width = cast(scr_width, UINT),
.Height = cast(scr_height, UINT),
.MipLevels = 1,
.ArraySize = 1,
.Format = DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT,
.SampleDesc = { .Count = 1 },
.BindFlags = D3D11_BIND_DEPTH_STENCIL,
};
depth_texture: *ID3D11Texture2D = null;
depth_dsv: *ID3D11DepthStencilView = null;
ID3D11Device_CreateTexture2D(device, depth_desc&, null, depth_texture&);
ID3D11Device_CreateDepthStencilView(device, cast(depth_texture, *ID3D11Resource), null, depth_dsv&);
// configure global d3d11 state
// ----------------------------
// GL's conventions rather than D3D's: counter-clockwise triangles face the
// viewer, and nothing is culled.
raster_desc: D3D11_RASTERIZER_DESC = {
.FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID,
.CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE,
.FrontCounterClockwise = 1,
.DepthClipEnable = 1,
};
raster_state: *ID3D11RasterizerState = null;
ID3D11Device_CreateRasterizerState(device, raster_desc&, raster_state&);
ID3D11DeviceContext_RSSetState(context, raster_state);
viewport: D3D11_VIEWPORT = {
.Width = cast(scr_width, f32),
.Height = cast(scr_height, f32),
.MaxDepth = 1.0,
};
// build and compile our shader programs
// -------------------------------------
lighting_shader: shader = shader_create(device, filesystem_path("src/2.lighting/1.colors/colors.hlsl"), vertex_layout);
lamp_shader: shader = shader_create(device, filesystem_path("src/2.lighting/1.colors/light_cube.hlsl"), vertex_layout);
lamp_buffer: *ID3D11Buffer = create_constant_buffer(device, 16);
// set up vertex data
// ------------------
cube_vb: *ID3D11Buffer = create_buffer(device, D3D11_BIND_VERTEX_BUFFER, cube_vertices[0]&, sizeof(cube_vertices));
stride: UINT = 32;
offset: UINT = 0;
transforms_buffer: *ID3D11Buffer = create_constant_buffer(device, sizeof(transforms));
colors_buffer: *ID3D11Buffer = create_constant_buffer(device, sizeof(colors_constants));
cam = camera_create(v3(-1.0, 1.0, 4.0), -75.0, -10.0);
light_position: vec3 = v3(1.2, 1.0, 2.0);
// render loop
// -----------
running: b32 = 1;
while (running) {
msg: MSG = {};
while (PeekMessageA(msg&, null, 0, 0, PM_REMOVE) != 0) {
if (msg.message == WM_QUIT) { running = 0; }
TranslateMessage(msg&);
DispatchMessageA(msg&);
}
// per-frame time logic
// --------------------
current_frame: f32 = get_time();
delta_time = current_frame - last_frame;
last_frame = current_frame;
// input
// -----
process_input(window);
// render
// ------
clear_color: [4]f32 = { 0.1, 0.1, 0.1, 1.0 };
ID3D11DeviceContext_OMSetRenderTargets(context, 1, backbuffer_rtv&, depth_dsv);
ID3D11DeviceContext_RSSetViewports(context, 1, viewport&);
ID3D11DeviceContext_ClearRenderTargetView(context, backbuffer_rtv, clear_color[0]&);
ID3D11DeviceContext_ClearDepthStencilView(context, depth_dsv, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0, 0);
ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ID3D11DeviceContext_IASetVertexBuffers(context, 0, 1, cube_vb&, stride&, offset&);
bind_constants(context, 0, transforms_buffer);
t: transforms = {
.view = camera_view(cam&),
.projection = m4_perspective(radians(cam.zoom), cast(scr_width, f32) / cast(scr_height, f32), 0.1, 100.0),
.view_position = cam.position,
};
colors: colors_constants = {
.object_color = v3(1.0, 0.5, 0.31),
.light_color = v3(1.0, 1.0, 1.0),
};
update_buffer(context, colors_buffer, colors&, sizeof(colors_constants));
shader_use(context, lighting_shader&);
bind_constants(context, 1, colors_buffer);
draw_cube(context, transforms_buffer, t&, m4_identity());
lamp_color: vec4 = v4(1.0, 1.0, 1.0, 1.0);
update_buffer(context, lamp_buffer, lamp_color&, 16);
shader_use(context, lamp_shader&);
bind_constants(context, 1, lamp_buffer);
draw_cube(context, transforms_buffer, t&, m4_mul(m4_translate(light_position), m4_scale(v3(0.2, 0.2, 0.2))));
// dxgi: show the image just drawn, waiting for the next vertical blank
// --------------------------------------------------------------------
IDXGISwapChain_Present(swap_chain, 1, 0);
}
return 0;
}