Vulkan default coord system for vertex positions - vulkan

I'm studing Vulkan coordinate system stuff by working on a toy renderer.
I'm confused about coordinates of vertex positions.
Online Vulkan info, such as this:
https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/
...mention that +X is right, +Y is down, and +Z is back.
In my renderer, +Z is pointing forward and I can't figure out why.
I have a triangle defined like this:
// CCW is facing forward
std::vector<PosColorVertex> vertexBuffer = {
{{ 0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}},
{{ 1.0f, 1.0f, -5.0f}, {0.0f, 0.0f, 1.0f}},
};
That -5(Z) moves the vertex back, into the screen. It should be +5 that does that.
The coordinate system seems to be like this:
If I place the camera at the origin, it looks like this:
Another shot, with the camera away from the triangle (view translated by -4 on Z).
Some relevant code. Both model & view matrix are identity.
VS:
outColor = inColor;
gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0);
FS:
outFragColor = vec4(inColor, 1.0);
Projection is:
glm::perspective(glm::radians(60.0f), w/h, 0.1, 256.0);

Clip, and normalized device coordinates
Clip coordinates are those we get from the vertex shader. Normalized device coordinates (NDC) are the same, but divided by w. There are two common user options (left-handed, and right-handed):
What "up" means is actually up to you. But if you want it to be compatible with virtually all presentation engines, you want "up" to mean -y after the viewport transform (so in NDH your "up" should be -y in case of vanilla viewport transform, or it should be +y in case viewport transform later flips it).
The choice of "up" being always -y in framebuffer\image coordinates is because surface coordinates on virtually all presentation engines assume upper-left origin:
Many image file formats also assume the same.
World coordinates
World coordinates are the ones that are perfectly up to you. And via the vertex shader you transform the world coordinates into the clip coordinates Vulkan can process.
You did this via your glm::perspective.
Let's first actually see what we have:
std::vector<PosColorVertex> vertexBuffer = {
{{ 0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}},
{{ 1.0f, 1.0f, -5.0f}, {0.0f, 0.0f, 1.0f}},
};
Now, this is again highly up to interpretation what this actually means. We need another "up" reference direction.
To stay sane, we would perhaps like "up" to be in the increasing direction of y. So that means we got some red corner at the bottom. We got one green corner at upper left. And we got one blue corner at upper right. Or so I assume was the author's intent.
Additionally to stay sane we would prefer right-handed coordinate system. So, if we have chosen that +y means "up" and +x means "right", then -z gotta be "front" (and +z gotta be "back"):
(This is something that matches e.g. how Blender have world coordinates.) Now we got ourselves in bit of a pickle though. Our z is negative instead of positive as required by NDH. And our y points the other way than in NDH compared to "up". Whatever transform we do, we need to make these match.
What does glm::perspective() do
glm::perspective() primarily does make it look, well perspectivy. But for that it needs to do couple of assumptions.
The easy one is depth. In Vulkan in NDC it is zero to one. Conveniently there is GLM_FORCE_DEPTH_ZERO_TO_ONE. That instucts perspective() that it should map your near and far to 0 and 1, respectively. (The defualt is -1 to 1, which would not work in Vulkan unless manually corrected.) The x and y are still always -1 to 1.
Second choice is handedness. Right-handed is default. Left-handed needs GLM_FORCE_LEFT_HANDED. Or it can be used explicitly for the single function, e.g. perspectiveRH(). It is slightly misleading. What this actually means here is that "right-handed" implies "front" being -z. And "left-handed" means the projection assumes "front" is into positive direction of z:
Third choice is what is "up". glm::perspective() does not do anything with this actually, and the polarity of y stays the same througout the transform. If we want +y to mean "up", we need to do this manually. Either we can make use of the viewport flip feature, or we can bake it into the view-projection matrix: proj[1][1] = -proj[1][1].
How to test this stuff
It is actually pretty straightforwar to test this. This code can be used for the purpose:
#include <cmath>
#include <iostream>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
//#define GLM_FORCE_LEFT_HANDED
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
int main(){
#ifdef GLM_FORCE_LEFT_HANDED
const float near = 1.0f;
const float far = 2.0f;
#else //right-handed
const float near = -1.0f;
const float far = -2.0f;
#endif
glm::vec3 right { 1.0f, 0.0f, near};
glm::vec3 left {-1.0f, 0.0f, near};
glm::vec3 up { 0.0f, 1.0f, near};
glm::vec3 down { 0.0f, -1.0f, near};
glm::vec3 front { 0.0f, 0.0f, (far-near) + near };
glm::vec3 back { 0.0f, 0.0f, (near-far) + near };
const auto xform = glm::perspective(glm::radians(60.0f), 1.0f, std::abs(near), std::abs(far));
auto r = xform * glm::vec4(right , 1.0f);
auto l = xform * glm::vec4(left , 1.0f);
auto u = xform * glm::vec4(up , 1.0f);
auto d = xform * glm::vec4(down , 1.0f);
auto f = xform * glm::vec4(front , 1.0f);
auto b = xform * glm::vec4(back , 1.0f);
std::cout << "Right to clip: (" << r.x << ", " << r.y << ", " << r.z << ", " << r.w << ")\n";
std::cout << "Left to clip: (" << l.x << ", " << l.y << ", " << l.z << ", " << l.w << ")\n";
std::cout << "Up to clip: (" << u.x << ", " << u.y << ", " << u.z << ", " << u.w << ")\n";
std::cout << "Down to clip: (" << d.x << ", " << d.y << ", " << d.z << ", " << d.w << ")\n";
std::cout << "Front to clip: (" << f.x << ", " << f.y << ", " << f.z << ", " << f.w << ")\n";
std::cout << "Back to clip: (" << b.x << ", " << b.y << ", " << b.z << ", " << b.w << ")\n";
}
For both handedness settings, we get:
Right to clip: (1.73205, 0, 0, 1)
Left to clip: (-1.73205, 0, 0, 1)
Up to clip: (0, 1.73205, 0, 1)
Down to clip: (0, -1.73205, 0, 1)
Front to clip: (0, 0, 2, 2)
Back to clip: (0, 0, -2, 0)
Ups, it all gets clipped away. But anyway "right" and "up" is positive number. So yea, we might want to flip y some way to be compatible with presentation engine coordinates. "front" is (0,0,1) direction, and "back" stops existing.
Note what changes in the code is the direction of z as used in the world coordinates.

Related

Assimp does not correctly load multiple meshes from one obj file

The obj files I am trying to load have multiple -o flags, so there are multiple meshes. I am trying to load them into only 1 VAO, and I will draw them by first recording each mesh's offset and size. I have noted that the offset and size are in terms of number of vertices instead of faces, so they are multiplied by 3. For example, the first mesh starts at offset 0, and its size is mesh1's mNumberFaces * 3, and the second mesh starts at offset mesh1's mNumberFaces * 3, and its size is mesh2's mNumberFaces * 3. However, it seems only the first mesh is drawn correctly, and the rest of the meshes are all distorted somehow.
This is my loading logic:
Object* obj = new Object(objName);
// Initialize the meshes in the obj file one by one
std::vector<glm::vec3> vert, norm;
std::vector<glm::vec2> text;
std::vector<glm::ivec3> indices;
int vertexOffset = 0;
std::cout << objName << " numMeshes: " << pScene->mNumMeshes << std::endl;
for (unsigned int i = 0; i < pScene->mNumMeshes; i++) {
std::cout << objName << ": vOffset " << vertexOffset << " numV " << pScene->mMeshes[i]->mNumFaces * 3 << std::endl;
aiMesh* pMesh = pScene->mMeshes[i];
aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
for (unsigned int j = 0; j < pMesh->mNumVertices; j++) {
vert.push_back(glm::vec3(pMesh->mVertices[j].x, pMesh->mVertices[j].y, pMesh->mVertices[j].z));
norm.push_back(glm::vec3(pMesh->mNormals[j].x, pMesh->mNormals[j].y, pMesh->mNormals[j].z));
aiVector3D textCoord = pMesh->HasTextureCoords(0) ? pMesh->mTextureCoords[0][j] : Zero3D;
text.push_back(glm::vec2(textCoord.x, textCoord.y));
}
for (unsigned int j = 0; j < pMesh->mNumFaces; j++) {
aiFace face = pMesh->mFaces[j];
indices.push_back(glm::ivec3(face.mIndices[0], face.mIndices[1], face.mIndices[2]));
}
aiMaterial* mtl = pScene->mMaterials[pMesh->mMaterialIndex];
std::string meshName = std::string(pMesh->mName.C_Str());
Mesh* mesh = new Mesh(meshName, loadMaterial(mtl), vertexOffset, pMesh->mNumFaces * 3);
obj->meshList.push_back(mesh);
vertexOffset = vertexOffset + 3 * pMesh->mNumFaces;
}
//create the obj's node structure
//obj->root = processNode(pScene->mRootNode, obj->meshList);
//send the data to the gpu
GLuint vao;
GLuint vbo[3];
GLuint ebo;
glcheck(glGenVertexArrays(1, &vao));
glcheck(glBindVertexArray(vao));
glcheck(glGenBuffers(3, vbo));
glcheck(glBindBuffer(GL_ARRAY_BUFFER, vbo[0]));
glcheck(glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * vert.size(), vert.data(), GL_STATIC_DRAW));
glcheck(glEnableVertexAttribArray(0));
glcheck(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0));
glcheck(glBindBuffer(GL_ARRAY_BUFFER, vbo[1]));
glcheck(glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * norm.size(), norm.data(), GL_STATIC_DRAW));
glcheck(glEnableVertexAttribArray(1));
glcheck(glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0));
glcheck(glBindBuffer(GL_ARRAY_BUFFER, vbo[2]));
glcheck(glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * text.size(), text.data(), GL_STATIC_DRAW));
glcheck(glEnableVertexAttribArray(2));
glcheck(glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0));
glcheck(glGenBuffers(1, &ebo));
glcheck(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo));
glcheck(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(glm::ivec3) * indices.size(), indices.data(), GL_STATIC_DRAW));
// Unbind the VBO/VAO
glcheck(glBindVertexArray(0));
//glcheck(glBindBuffer(GL_ARRAY_BUFFER, 0));
//glcheck(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
obj->vao = vao; //shared vao variable
objMap[objName] = obj;
objList.push_back(obj);
return obj;
This is my drawing logic:
for (int i = 0; i < instObj->meshList.size(); i++) {
Mesh* mesh = instObj->meshList[i];
glcheck(glDrawElements(GL_TRIANGLES, mesh->size, GL_UNSIGNED_INT, (GLvoid*)(sizeof(GLuint) * mesh->vertexOffset)));
}
This is the first mesh, which is drawn correctly first mesh
The second mesh and onward are all messed up however, second mesh
The complete mesh enter image description here

CGAL Cartesian grid

In my code, I organize objects into a regular Cartesian grid (such as 10x10). Often given a point, I need to test whether the point intersects grid and if so, which bins contain the point. I already have my own implementation but I don't like to hassle with precision issues.
So, does CGAL has a 2D regular Cartesian grid?
You can use CGAL::points_on_square_grid_2 to generate the grid points. CGAL kernels provide Kernel::CompareXY_2 functors, which you can use to figure out the exact location of your query point on the grid. For example you can sort your grid points and then use std::lower_bound followed by CGAL::orientation or CGAL::collinear on the appropriate elements of your range. You could also build an arrangement, but this would be an overkill.
Here is a sample code.
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/point_generators_2.h>
#include <CGAL/random_selection.h>
#include <CGAL/Polygon_2_algorithms.h>
using namespace CGAL;
using K= Exact_predicates_exact_constructions_kernel;
using Point =K::Point_2;
using Creator = Creator_uniform_2<double, Point>;
using Grid = std::vector<Point>;
const int gridSide = 3;
void locate_point (Point p, Grid grid);
int main ()
{
Grid points;
points_on_square_grid_2(gridSide * gridSide, gridSide * gridSide, std::back_inserter(points), Creator());
std::sort(points.begin(), points.end(), K::Less_xy_2());
std::cout << "Grid points:\n";
for (auto& p:points)
std::cout << p << '\n';
std::cout << "\ncorner points:\n";
Grid cornerPoints{points[0], points[gridSide - 1], points[gridSide * gridSide - 1],
points[gridSide * (gridSide - 1)]};
for (auto& p:cornerPoints)
std::cout << p << '\n';
std::cout << '\n';
Point p1{-8, -8};
Point p2{-10, 3};
Point p3{-9, -8};
Point p4{0, 4};
Point p5{1, 5};
locate_point(p1, points);
locate_point(p2, points);
locate_point(p3, points);
locate_point(p4, points);
locate_point(p5, points);
}
void locate_point (Point p, Grid grid)
{
if (grid.empty())
{
std::cout << "Point " << p << " not in grid";
return;
}
// check if point is in grid
Grid cornerPoints{grid[0], grid[gridSide - 1], grid[gridSide * gridSide - 1], grid[gridSide * (gridSide - 1)]};
auto point_is = CGAL::bounded_side_2(cornerPoints.begin(), cornerPoints.end(), p);
switch (point_is)
{
case CGAL::ON_UNBOUNDED_SIDE:
std::cout << "Point " << p << " not in grid\n";
return;
case CGAL::ON_BOUNDARY:
std::cout << "Point " << p << " on grid boundary\n";
return;
case CGAL::ON_BOUNDED_SIDE:
std::cout << "Point " << p << " is in grid\n";
}
auto f = std::lower_bound(grid.begin(), grid.end(), p, K::Less_xy_2());
auto g = std::find_if(f, grid.end(), [&p] (const Point& gridpoint)
{ return K::Less_y_2()(p, gridpoint); });
if (CGAL::collinear(p, *g, *(g - 1)))
{
std::cout << "Point " << p << " on grid side between points " << *(g - 1) << " and " << *g << '\n';
return;
}
std::cout << "Point " << p << " in bin whose upper right point is " << *g << '\n';
return;
}
Output:
Grid points:
-9 -9
-9 0
-9 9
0 -9
0 0
0 9
9 -9
9 0
9 9
corner points:
-9 -9
-9 9
9 9
9 -9
Point -8 -8 is in grid
Point -8 -8 in bin whose upper right point is 0 0
Point -10 3 not in grid
Point -9 -8 on grid boundary
Point 0 4 is in grid
Point 0 4 on grid side between points 0 0 and 0 9
Point 1 5 is in grid
Point 1 5 in bin whose upper right point is 9 9

Vulkan compute shader invisible pixel writes

I am playing around with a compute shader and have a weird behaviour I really cannot explain. I was wondering if you could help me figure it out.
This is a suboptimal use of a compute shader, but I want to understand what is happening...
I have a 64x64 texture and I want to write 4 horizontal pixels from a single invocation of my shader. Therefore I call vkCmdDispatch( 64 / 4, 1, 1 ); with the following shader
#version 450
layout (local_size_x = 1, local_size_y = 1) in;
layout (binding = 0, rgba8) uniform writeonly image2D resultImage;
void main()
{
const uint texPosX = gl_GlobalInvocationID.x * 4;
const uint texPosY = gl_GlobalInvocationID.y;
imageStore( resultImage, ivec2( texPosX + 0, texPosY ), vec4( 1.0f, 0.0f, 0.0f, 1.0f) );
imageStore( resultImage, ivec2( texPosX + 1, texPosY ), vec4( 0.0f, 1.0f, 0.0f, 1.0f) );
imageStore( resultImage, ivec2( texPosX + 2, texPosY ), vec4( 0.0f, 0.0f, 1.0f, 1.0f) );
imageStore( resultImage, ivec2( texPosX + 3, texPosY ), vec4( 1.0f, 1.0f, 1.0f, 1.0f) );
}
I would expect this to write 4 pixels (red green blue and white) in a single row. I would expect the final image to look like vertical lines with a pattern like { red, green, blue, white, red, green, blue, white, ...}
What I get is instead vertical lines with a pattern of { red, green, blue, white, green, blue, white, red, blue, etc... } so it looks like there is a shift by one in the values.
If I instead change the way I compute the texPosX to const uint texPosX = gl_GlobalInvocationID.x * 5; (writing 5 pixels instead of 4) I get the expected result. If I add a fifth write like so
imageStore( resultImage, ivec2( texPosX + 4, texPosY ), vec4( 1.0f, 1.0f, 1.0f, 1.0f) ); // purple color at fifth pixel
the result is exactly the same as previously, meaning the last write is "invisible". My image is really 64x64, but it seems the number of pixels each invocation is working on is 5, with the last one not being invisible... And as far as I know, 64 / 4 = 16 workgroups and 64 / 16 = 4 pixels per workgroup (since my local group size is 1).
I am surely missing something that is obvious, but I have no clue to what it is. I thought I understood the global/local workgroup size, but it does not seem to be the case...
Thanks a lot for your help !

Comparison between 2D and 3D Affine transforms

Is it expected that the following test should fail?
The test compares results of a 2D and a 3D AffineTransformation. Both are constructed to have unit scaling and zero offsets in the y and z direction, but to have non-zero and non-unity scaling and offset in the x direction. All other off-diagonal elements are zero. It is my belief that these transformations are identical in the x and y directions, and hence should produce identical results.
Furthermore I have found that the test passes if I use this Kernel:
using K = CGAL::Exact_predicates_exact_constructions_kernel;
Is it to be expected that the test passes if I use this Kernel? Should the test fail with either kernel or pass with either kernel?
TEST(TransformerTest, testCGALAffine) {
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Float = typename K::FT;
using Transformation_2 = K::Aff_transformation_2;
using Transformation_3 = K::Aff_transformation_3;
using Point_2 = typename K::Point_2;
using Point_3 = typename K::Point_3;
double lowerCorner(17.005142946538115);
double upperCorner(91.940521484752139);
int resolution = 48;
double tmpScaleX((upperCorner - lowerCorner) / resolution);
Float scaleX(tmpScaleX);
Float zero(0);
Float unit(1);
// create a 2D voxel to world transform
Transformation_2 transformV2W_2(scaleX, zero, Float(lowerCorner),
zero, unit, zero,
unit);
// create it's inverse: a 2D world to voxel transform
auto transformW2V_2 = transformV2W_2.inverse();
// create a 3D voxel to world transform
Transformation_3 transformV2W_3(scaleX, zero, zero, Float(lowerCorner),
zero, unit, zero, zero,
zero, zero, unit, zero,
unit);
// create it's inverse: a 3D world to voxel transform
auto transformW2V_3 = transformV2W_3.inverse();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 2; ++j) {
EXPECT_EQ(transformV2W_2.cartesian(i, j), transformV2W_3.cartesian(i, j)) << i << ", " << j;
EXPECT_EQ(transformW2V_2.cartesian(i, j), transformW2V_3.cartesian(i, j)) << i << ", " << j;
}
}
std::mt19937_64 rng(0);
std::uniform_real_distribution<double> randReal(0, resolution);
// compare the results of 2D and 3D transformations of random locations
for (int i = 0; i < static_cast<int>(1e4); ++i) {
Float x(randReal(rng));
Float y(randReal(rng));
auto world_2 = transformV2W_2(Point_2(x, y));
auto world_3 = transformV2W_3(Point_3(x, y, 0));
EXPECT_EQ(world_2.x(), world_3.x()) << world_2 << ", " << world_3;
auto voxel_2 = transformW2V_2(world_2);
auto voxel_3 = transformW2V_3(world_3);
EXPECT_EQ(voxel_2.x(), voxel_3.x()) << voxel_2 << ", " << voxel_3;
}
}

Custom CIKernel move pixels

I'm trying to create custom cifilter (like adobe's warp filter). How to move only few pixels (that are in ROI) to some other location in kernel language? Maybe someone could suggest me some info about it? I have read all apple docs about creating custom cifilter, but haven't found any similar example of kernel portion of that type of filter. There are some CIFilters that does something similar (like CITwirlDistortion, CIBumpDistortion). Maybe there is some place where I could find their kernels?
You have to do this in reverse. Instead of saying I want to put those input pixels at this position in the output you have to answer the question where are the pixels in the input for this output pixel.
Take a look at this kernel:
kernel vec4 coreImageKernel(sampler image, float minX, float maxX, float shift)
{
vec2 coord = samplerCoord( image );
float x = coord.x;
float inRange = compare( minX - x, compare( x - maxX, 1., 0. ), 0. );
coord.x = coord.x + inRange * shift;
return sample( image, coord );
}
It replaces a vertical stripe between minX and maxX with the contents of the image that is shift pixels to the right. Using this kernel with minX = 100, maxX = 300 and shift = 500 gives the image in the lower left corner. Original is in upper right.
So the effect is that you move the pixels in the range (minX + shift, maxX + shift) to (minX, maxX)