Это развитие темы "Рамочное приложение Vulkan на MASM x64" Здесь будем крутить и двигать простой куб, грани которого окрашены в цвета кубика Рубика для наглядности. Вулкана здесь будет меньше, зато больше GLSL, векторов, матриц и кватернионов. В отличие от темы "Рамочное приложение Vulkan на MASM x64" здесь во главе угла будет не механика Вулкана, а геометрия. Но, поскольку Вулкан - масштабируемая платформа, здесь будем включать и выключать различные его функции. Понеслась. Идём в 3D, а для этого добавляем буфер глубины Если до этого Вулкан отрисовывал полигоны в том порядке, в котором он их обрабатывал, то теперь он будет учитывать расстояние, на которое удалены точки. Это необходимое условие работы в 3D, иначе будет вот так: Спойлер: Глубже. Tiefer, tiefer! Sag es! Sag es laut! Код (ASM): createDepthImage proc LOCAL index:DWORD PROLOG 100h ;1. Fill VkImageCreateInfo mov eax,chosenExtent_width mov imageInfo_extent_width,eax mov eax,chosenExtent_height mov imageInfo_extent_height,eax ;2. Create Depth Image LOG_TEXT szVkCreateImage mov rcx,ghVkLogicalDevice lea rdx,imageInfo_sType xor r8,r8 lea r9,ghVkDepthImage call vkCreateImage test rax,rax jnz lbl_VkError cmp ghVkDepthImage,0 je lbl_VkError mov rax,ghVkDepthImage mov depthViewInfo_image,rax LOG_TEXT szOK ;3. Get Image Memory Requirements LOG_TEXT szVkGetImageMemoryRequirements mov rcx,ghVkLogicalDevice mov rdx,ghVkDepthImage lea r8,memReqs_size ;address of VkMemoryRequirements structure call vkGetImageMemoryRequirements cmp memReqs_size,0 je lbl_Error_NoImgMemReqs cmp memReqs_alignment,0 je lbl_Error_NoImgMemReqs LOG_TEXT szOK ;4. Find memory type index LOG_TEXT szVkGetPhysicalDeviceMemoryProperties mov rcx,ghVkPhysicalDevice lea rdx,memProps_memoryTypeCount ;address of VkPhysicalDeviceMemoryProperties call vkGetPhysicalDeviceMemoryProperties cmp memProps_memoryTypeCount,0 je lbl_Error_NoDeviceMemory LOG_TEXT szOK ;5. Loop through memory types xor rcx,rcx ;i = 0 mov index,ecx ;Check if this type supports the required memory type bits search_memory_type: mov rax,1 mov ecx,index shl eax,cl ;1 << index and eax,memReqs_memoryTypeBits jz next_memory_type ;Check if it has VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT (0x01) ;Offset of memoryTypes[i].propertyFlags is i * 8 shl rcx,3 ;i*8 lea rsi,memProps_memoryTypes add rsi,rcx test dword ptr[rsi],1 ;check if VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT is set jnz found_memory_type next_memory_type: inc index mov ecx,index cmp ecx,memProps_memoryTypeCount jl search_memory_type ;If not found, fallback to any type matching bits (without checking properties) xor rcx,rcx mov index,ecx fallback_search: mov rax,1 mov ecx,index shl eax,cl ;1 << index and eax,memReqs_memoryTypeBits jnz found_memory_type inc index mov ecx,index cmp ecx,memProps_memoryTypeCount jl fallback_search ;If still not found -> error LOG_TEXT szNoDeviceMemory jmp lbl_Error_NoDeviceMemory found_memory_type: mov ecx,index mov depthAllocInfo_memoryTypeIndex,ecx LOG_TEXT szDeviceMemoryFound ;6. Align allocation size to the required alignment mov rax,memReqs_size mov rbx,memReqs_alignment cmp rbx,0 je lbl_Error_NoImgMemReqs dec rbx add rax,rbx not rbx and rax,rbx ; Ensure non-zero test rax,rax jz lbl_Error_ZeroSize ;7. Set allocation size from requirements mov depthAllocInfo_allocationSize,rax ;8. Allocate the Device Memory LOG_TEXT szVkAllocateMemory mov rcx,ghVkLogicalDevice lea rdx,depthAllocInfo_sType ;address of VkMemoryAllocateInfo structure xor r8,r8 ;pAllocator lea r9,ghVkDepthDeviceMemory call vkAllocateMemory test rax,rax jnz lbl_VkError cmp ghVkDepthDeviceMemory,0 je lbl_VkError LOG_TEXT szOK ;9. Bind the memory to the image LOG_TEXT szVkBindImageMemory mov rcx,ghVkLogicalDevice mov rdx,ghVkDepthImage mov r8,ghVkDepthDeviceMemory xor r9,r9 ;memoryOffset = 0 call vkBindImageMemory test rax,rax jnz lbl_VkError LOG_TEXT szOK ;10. Create Depth Image View LOG_TEXT szVkCreateImageView mov rcx,ghVkLogicalDevice lea rdx,depthViewInfo_sType ;Reuse the existing VkImageViewCreateInfo structure xor r8,r8 lea r9,ghVkDepthImageView call vkCreateImageView test rax,rax jnz lbl_VkError cmp ghVkDepthImageView,0 je lbl_VkError LOG_TEXT szOK jmp lbl_End lbl_Error_NoImgMemReqs: LOG_TEXT szNoImgMemReqs jmp lbl_End lbl_Error_ZeroSize: LOG_TEXT szErrorZeroSize jmp lbl_End lbl_Error_NoDeviceMemory: LOG_TEXT szNoDeviceMemory jmp lbl_End lbl_VkError: call SpellVkError lbl_End: EPILOG createDepthImage endp Создаём куб Ранее мы создали загрузчик шейдеров, здесь мы им воспользуемся по полной программе. Спойлер: Вершинный шейдер куба Код (C): #version 450 // OBJECT GEOMETRY // 8 unique cube vertices const vec3 cubeVerts[8] = { vec3(-1.0, -1.0, -1.0), // 0 vec3(+1.0, -1.0, -1.0), // 1 vec3(+1.0, +1.0, -1.0), // 2 vec3(-1.0, +1.0, -1.0), // 3 vec3(-1.0, -1.0, +1.0), // 4 vec3(+1.0, -1.0, +1.0), // 5 vec3(+1.0, +1.0, +1.0), // 6 vec3(-1.0, +1.0, +1.0) // 7 }; // Cube vertices (36 vertices, 6 faces, 2 triangles per face) const vec3 cubeVertices[36] = { // Face -Z (Bottom) – White cubeVerts[0], cubeVerts[3], cubeVerts[2], cubeVerts[0], cubeVerts[2], cubeVerts[1], // Face -Y (Front) – Blue cubeVerts[0], cubeVerts[1], cubeVerts[5], cubeVerts[0], cubeVerts[5], cubeVerts[4], // Face +X (Right) – Red cubeVerts[1], cubeVerts[2], cubeVerts[6], cubeVerts[1], cubeVerts[6], cubeVerts[5], // Face +Y (Back) – Green cubeVerts[2], cubeVerts[3], cubeVerts[7], cubeVerts[2], cubeVerts[7], cubeVerts[6], // Face -X (Left) – Orange cubeVerts[0], cubeVerts[4], cubeVerts[7], cubeVerts[0], cubeVerts[7], cubeVerts[3], // Face +Z (Top) – Yellow cubeVerts[4], cubeVerts[5], cubeVerts[6], cubeVerts[4], cubeVerts[6], cubeVerts[7] }; // OBJECT COLOR // Rubik's cube face colours (matching the order above) const vec3 faceColors[6] = { vec3(1.0, 1.0, 1.0), //Face -Z (Bottom) – White vec3(0.0, 0.0, 1.0), //Face -Y (Front) – Blue vec3(1.0, 0.0, 0.0), //Face +X (Right) – Red vec3(0.0, 1.0, 0.0), //Face +Y (Back) – Green vec3(1.0, 0.5, 0.0), //Face -X (Left) – Orange vec3(1.0, 1.0, 0.0) //Face +Z (Top) – Yellow }; // PROJECTION TRANSFORMATIONS // 0. Orthographic Projection float nearPlane = -2.0; float farPlane = 10.0; float o22 = 1.0 / (farPlane - nearPlane); float o23 = -nearPlane / (farPlane - nearPlane); float o32 = 0.0; mat4 mtxOrtho = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, o22, o32), vec4(0.0, 0.0, o23, 1.0) ); // 0. Perspective Projection float aspect = 1.0; // 512/512 float fovY = radians(60.0); float nearZ = 0.1; // Near plane at z_eye = -0.1 float farZ = 10.0; // Far plane at z_eye = -10.0 float p11 = -1.0 / tan(fovY * 0.5); // Mirror about XZ plane float p00 = p11 / aspect; float p22 = -farZ / (farZ - nearZ); float p32 = -1.0; // Makes w_clip = -z_eye float p23 = nearZ * p22; mat4 mtxPersp = mat4( vec4(p00, 0.0, 0.0, 0.0), vec4(0.0, p11, 0.0, 0.0), vec4(0.0, 0.0, p22, p32), vec4(0.0, 0.0, p23, 0.0) ); // AFFINE TRANSFORMATIONS //0. Translate float tx = 0.0; float ty = 0.0; float tz = -4.0; mat4 transMat = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4( tx, ty, tz, 1.0) ); // 1. Overall Scale float S = 0.5; mat4 scaleMat = mat4( vec4( S, 0.0, 0.0 ,0.0), vec4(0.0, S, 0.0, 0.0), vec4(0.0, 0.0, S ,0.0), vec4(0.0, 0.0, 0.0, 1.0) ); // 2. Rotate around X-axis float angleX = radians(300.0); float cX = cos(angleX); float sX = sin(angleX); mat4 rotX = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, cX, sX, 0.0), vec4(0.0, -sX, cX, 0.0), vec4(0.0, 0.0, 0.0, 1.0) ); // 3. Rotate around Z-axis float angleZ = radians(20.0); float cZ = cos(angleZ); float sZ = sin(angleZ); mat4 rotZ = mat4( vec4( cZ, -sZ, 0.0, 0.0), vec4( sZ, cZ, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0) ); // 4. Rotate around Y-axis float angleY = radians(10.0); float cY = cos(angleY); float sY = sin(angleY); mat4 rotY = mat4( vec4( cY, 0.0, sY, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(-sY, 0.0, cY, 0.0), vec4(0.0, 0.0, 0.0, 1.0) ); // Ortho Sequence //mat4 projView = mtxOrtho; //mat4 projView = mtxOrtho * scaleMat; //mat4 projView = mtxOrtho * scaleMat * rotX; //mat4 projView = mtxOrtho * scaleMat * rotX * rotZ; //mat4 projView = mtxOrtho * scaleMat * rotX * rotZ * rotY; //Perspective Sequence //mat4 projView = mtxPersp; //mat4 projView = mtxPersp * transMat; //mat4 projView = mtxPersp * transMat * rotX; //mat4 projView = mtxPersp * transMat * rotX * rotZ; mat4 projView = mtxPersp * transMat * rotX * rotZ * rotY; // OUTPUT layout(location = 0) out vec3 fragColor; void main() { int face = gl_VertexIndex / 6; vec3 pos = cubeVertices[gl_VertexIndex]; fragColor = faceColors[face]; gl_Position = projView * vec4(pos, 1.0); } Спойлер: Фрагментный шейдер Код (C): #version 450 layout(location = 0) in vec3 fragColor; layout(location = 0) out vec4 outColor; void main() { outColor = vec4(fragColor, 1.0); } Напоминаю, что компилятор языка GLSL поставляется с пакетом Vulkan, он встраивается в переменные среды, поэтому его можно вызывать прямо с командной строки. Спойлер: Батник для сборки Код (DOS): @echo off echo Compiling vertex shader... glslc -fshader-stage=vertex vertex.vert -o vertex.spv if %errorlevel% equ 0 ( echo Success: vertex.spv generated. ) else ( echo Error: compilation failed. ) pause
Система координат Вулкан Ориентация осей В Северном полушарии Оси координат Вулкана ориентированы следующим образом: +Z - это направление, в котором фотоны летят из монитора на Вашу сетчатку. +Y смотрит вниз (в отличие от декартовой системы). +X смотрит вправо (как в декартовой системе). Таким образом, чтобы привести Вулкан к декартовой системе, надо применить следующую матрицу: mat4 mtxScale = mat4( vec4( S, 0.0, 0.0 ,0.0), vec4(0.0, -S, 0.0, 0.0), //Right-hand vec4(0.0, 0.0, S ,0.0), vec4(0.0, 0.0, 0.0, 1.0) Если этого не сделать, то всё будет в зеркальном отображении относительно плоскости XZ. Позиционирование объекта Если (как, например, в случае с нашим кубом) точка 0,0,0 находится в центре сцены, то мы не увидим всей сцены. Нужно отодвинуть всю сцену на достаточное расстояние от себя, т.е. в направлении -Z. В примере выше я сделал это так: Код (C): // Step 1. Translate float dx = 0.0; float dy = 0.0; float dz = -2.0; mat4 mtxTrans = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4( dx, dy, dz, 1.0) ); Обращаю внимание, что матрица row-major (по горизонтали). Не помню, как это называется в русской линейной алгебре. Потом переведу, чтобы сейчас не сбиваться с мысли. Направление взгляда Чтобы создать эффект прогулки от первого лица, надо повернуть всю сцену вокруг оси X. На какой угол? - Сейчас определим. Как показано выше, Вулкан смотрит на сцену сверху вниз. Фотоны летят на Вас в направлении +Z, а Вы смотрите на объект в направлении -Z. В реальной жизни (я имею в виду, конечно же, BIM-моделирование) мы считаем, что +Z - это направление от земли к небу, т.е. наш взгляд условно параллелен плоскости XY. Следовательно, чтобы Вулкан рисовал сцену так, как мы видим реальный мир, нам нужно повернуть всю сцену вокруг оси X на угол 90 +/- 30 градусов. Я повернул на 60, чтобы взгляд был чуть сверху. Код (C): // Step 3. Rotate around X-axis float angleX = radians(60.0); float cX = cos(angleX); float sX = sin(angleX); mat4 rotX = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, cX, -sX, 0.0), vec4(0.0, sX, cX, 0.0), vec4(0.0, 0.0, 0.0, 1.0) ); Классическая матрица поворота, тут ничего волшебного. Примечание Какие-то цифры, коэффициенты могут отличаться от примера. Такое может быть, т.к. я выкладываю в процессе разработки, потому что потом уже будет лень что-то форматировать. Даже не то, что лень, а вайб пройдёт и пойдут уже другие идеи. Поэтому фиксирую, как есть. --- Сообщение объединено, 29 июн 2026 --- Надо уточнить! А вот и нет. Сам Khronos пишет, что: "Vulkan requires the right hand NDC space compared to GL that requires the left hand. In other words the (-1, -1) NDC coordinate maps to the top left corner for Vulkan and to the bottom left corner for GL". И добавляет: "In practice, if a Vulkan application ignored this convention the final output will end up flipped in the Y axis and inside out." Короче: у меня куб задан в декартовой системе координат: Код (C): const vec3 cubeVerts[8] = { vec3(-0.5, -0.5, -0.5), // 0 vec3(+0.5, -0.5, -0.5), // 1 vec3(+0.5, +0.5, -0.5), // 2 vec3(-0.5, +0.5, -0.5), // 3 vec3(-0.5, -0.5, +0.5), // 4 vec3(+0.5, -0.5, +0.5), // 5 vec3(+0.5, +0.5, +0.5), // 6 vec3(-0.5, +0.5, +0.5) // 7 }; // Cube faces (36 vertices, 6 faces, 2 triangles per face) const vec3 cubeVertices[36] = { // Face -Z (Bottom) – White cubeVerts[0], cubeVerts[3], cubeVerts[2], cubeVerts[0], cubeVerts[2], cubeVerts[1], // Face -Y (Front) – Blue cubeVerts[0], cubeVerts[1], cubeVerts[5], cubeVerts[0], cubeVerts[5], cubeVerts[4], // Face +X (Right) – Red cubeVerts[1], cubeVerts[2], cubeVerts[6], cubeVerts[1], cubeVerts[6], cubeVerts[5], // Face +Y (Back) – Green cubeVerts[2], cubeVerts[3], cubeVerts[7], cubeVerts[2], cubeVerts[7], cubeVerts[6], // Face -X (Left) – Orange cubeVerts[0], cubeVerts[4], cubeVerts[7], cubeVerts[0], cubeVerts[7], cubeVerts[3], // Face +Z (Top) – Yellow cubeVerts[4], cubeVerts[5], cubeVerts[6], cubeVerts[4], cubeVerts[6], cubeVerts[7] }; Чтобы не запутаться, я держу на столе кубик Рубика. И могу сказать: Вулкан рисовал куб в зеркальном отражении относительно плоскости XZ, пока я не умножил координату Y на -1. Код (C): float S = 1.0; mat4 mtxScale = mat4( vec4( S, 0.0, 0.0 ,0.0), vec4(0.0, -S, 0.0, 0.0), //Right-hand vec4(0.0, 0.0, S ,0.0), vec4(0.0, 0.0, 0.0, 1.0) ); --- Сообщение объединено, 29 июн 2026 --- К вопросу о направлении оси Z Подтверждение этому - преобразование: Код (C): // Step 1. Translate float dx = 0.0; float dy = 0.0; float dz = -2.0; mat4 mtxTrans = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4( dx, dy, dz, 1.0) ); dx = +0.5 двигает вправо, dy = +0.5 двигает вниз, dz = -3.0 двигает назад Всё-таки я прав - с оговоркой: если моя матрица перспективы правильная: Код (C): // Option 2. Perspective Projection float aspect = 1.0; // 512/512 float fovY = radians(60.0); float nearZ = 0.1; // Near plane at z_eye = -0.1 float farZ = 10.0; // Far plane at z_eye = -10.0 float p11 = 1.0 / tan(fovY * 0.5); float p00 = p11 / aspect; float p22 = -farZ / (farZ - nearZ); float p32 = -1.0; // Makes w_clip = -z_eye float p23 = nearZ * p22; mat4 mtxPersp = mat4( vec4(p00, 0.0, 0.0, 0.0), vec4(0.0, p11, 0.0, 0.0), vec4(0.0, 0.0, p22, p32), vec4(0.0, 0.0, p23, 0.0) ); --- Сообщение объединено, 29 июн 2026 --- Да, моя перспектива правильная и понимание координат Вулкан - тоже. Это подтверждается результатами преобразований ортогональной проекции: Код (C): // Step 1. Translate float dx = [COLOR=#00b300][B]0.5[/B][/COLOR]; float dy = [COLOR=#00b300][B]0.5[/B][/COLOR]; float dz = [COLOR=#ff0000][B]-0.5[/B][/COLOR]; mat4 mtxTrans = mat4( vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4( dx, dy, dz, 1.0) );