Content of tables

  • Selection buffer
  • Draw outline
  • Translation when dragging

Pipeline

  1. Get selected object ID from application thread.
  2. If it is not a transform gizmo, set up transforms for transform gizmo, such us three rotations, scale and position. Add three arrows to the rendering map.
  3. Draw outline for selected object.
  4. Generate selection buffer (Transform gizmos are also inside the rendering map is a object is selected.) Get the latest selected object ID.
  5. Render transform gizmos(3 arrows point to 3 perpendicular direction).
  6. Return data(selected object ID) to application thread.

Selection buffer

The idea of selection in my implementation is straightforward. The idea is to give any object that wants to be selected an uniquie ID. And use a framebuffer to store the selection info. Like in this image:

This image is post-processed by photoshop because the original one is too dark.

This is a RGB8 format texture. Theoretically, it could store256^3 different selectable obejcts.

uint32_t modelID = it->first.SelectableID;
int r = (modelID & 0x000000FF) >> 0;
int g = (modelID & 0x0000FF00) >> 8;
int b = (modelID & 0x00FF0000) >> 16;
g_currentEffect->SetVec3("selectionColor", glm::vec3(r / 255.f, g / 255.f, b / 255.f));

After generating this buffer, use the following line to get the pixel data, which is the selection id.

glReadPixels(mouseX, mouseY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, data);
uint32_t pickedID = data[0] + data[1] * 256 + data[2] * 256 * 256;

Outline effect

The idea of rendering a outline is drawing a up-scaled object with pure color and at the same time use stencil buffer to exclude the original object. The following image is a highlighted teapot stencil buffer.

When I draw the up-scaled object, all pixel inside the pink area will be ignroed.

The up-scale method should be moving the vertex along the smoothed vertex normal direction to prevent artifacts. The width of the outline is changing according to the vertex distance to the camera. Here is the vertex shader code for up-scaled object:

vec3 ViewPosition = vec3(InvView[3][0], InvView[3][1], InvView[3][2]);
vec3 WorldPos = (modelMatrix * vec4(pos, 1.0)).xyz;
float dist = distance(ViewPosition, WorldPos);
float width = clamp(outlineWidth * abs(dist) / 200.0, 0.05, 0.2);

vec3 scaledPos = pos + normalize(normal) * width;
gl_Position = PVMatrix * modelMatrix * vec4(scaledPos, 1.0);

I use another framebuffer to capture the stencil buffer and copy the stencil data to the default framebuffer:

// Copy stencil buffer data to the default frame buffer
glBlitNamedFramebuffer(g_outlineBuffer.fbo(), 0, 0, 0, g_outlineBuffer.GetWidth(), g_outlineBuffer.GetHeight(), 0, 0, g_outlineBuffer.GetWidth(), g_outlineBuffer.GetHeight(), GL_STENCIL_BUFFER_BIT, GL_NEAREST);

Additionally, I disable the depth-test such that the outline is always drawn on top of every scene object.

Translation when dragging

When I click the LMB, an object will be selected. When the LMB is down and the down point is within a transform gizmo(), and the dragging velocity is larger than zero, the obejct will move according to the dragging arrow direction. When the cursor is hoverring the gizmo or it is translating, the color will be orange.

The transform gizmo will scale according to the distance to the view direction.

I use dot product of the world space drag velocity and the arrow direction to determine the sign of the moving direction.

glm::vec4 dragDelta(glm::vec2(_windowInput->DX(), _windowInput->DY()), 0, 0);
// multiply by the inverse view matrix of the editor camera
glm::vec3 dragInWorldSpace = m_editorCamera->GetInvViewMatrix() * dragDelta;
float dragVelocity = glm::length(dragInWorldSpace);
glm::vec3 dragInWorldSpace_norm = dragInWorldSpace / dragVelocity;
int directionSign = glm::dot(direction, dragInWorldSpace_norm) > 0 ? 1 : -1;
owner->Transform.Translate(direction * directionSign * 100.f * dragVelocity * second_since_lastFrame);