GPU 및 Metal을 사용하여 스타일화된 사각형을 그리는 방법

이것은 금속 도형 착색기를 사용하여 양식화된 직사각형을 그리는 강좌입니다.사각형을 그리는 방법, 경계선, 원각을 추가하는 방법, 선형 그래디언트로 사각형을 채우는 방법을 배웁니다.

GPU에 렌더링하는 이유


빠른 렌더링이 필요한 응용 프로그램, 예를 들어 비디오 처리 응용 프로그램이나 3D 게임 응용 프로그램은 일반적으로 GPU를 사용하여 렌더링해야 한다.GPU는 더 많은 핵이 있기 때문에 데이터 병렬 계산을 실행할 수 있다. 예를 들어 픽셀 위치와 색을 매우 신속하게 계산할 수 있다.
GPU 사용의 균형은 우리가 반드시 착색기를 실현해야 한다는 것이다.그러나 현대적인 UI 프로그램에서는 글꼴, 아이콘, 직사각형만 실현할 수 있다.예를 들어 Warp의 UI는 세 개의 원어로 구성되어 있습니다.

이 강좌는 직사각형의 착색기를 중점적으로 소개할 것이다.분식집은 창문 꼭대기 중앙에 위치하고 테두리와 둥근 사각형이 있다.
우리는 점차적으로 직사각형을 그려서 테두리, 원각을 추가하고, 선형 점차적인 변화로 직사각형을 채울 것이다.우리는 거리 이탈, 벡터 투영, 톱니 저항 등 재미있는 도형 개념을 소개할 것이다.
이 자습서는 GPU 렌더링을 처음 접한 초보자를 대상으로 합니다.코드 예제Metal에서 애플의 공식 착색기 API입니다.
다음은 디렉터리입니다. 당신은 어떤 부분으로든 이동할 수 있습니다.
  • Why render on the GPU
  • How to draw a basic rectangle using shaders
  • How to draw borders on a rectangle using shaders
  • How to draw rounded rectangles using shaders
  • How to fill rectangles with gradients using shaders
  • Putting it all together
  • 셰이더를 사용하여 기본 사각형을 그리는 방법


    우리는 한 쌍의 함수를 통해 GPU에 지령: 정점 착색기와 세션 착색기를 제공합니다.정점 착색기의 작업은 그려야 할 위치를 만드는 것이다.세그먼트 셰이더는 이러한 위치를 사용하고 위치 경계 내의 각 픽셀의 색상을 결정합니다.삼각형의 경우 정점 착색기는 세 개의 정점을 생성하고 부분 착색기는 픽셀별로 삼각형을 채운다.

    착색기로 직사각형을 그립시다.
    교점 셰이더의 경우 교점의 좌표를 뷰포트에 독립적으로 규격화합니다.구체적으로 말하면 우리는 좌표를 뷰포트의 반으로 나누어 이 점을 실현한다(위 그림Apple Metal docs 참조).
    세션 착색기에 대해 픽셀의 삽입 색상을 되돌릴 수 있습니다.
    이것은 우리가 사용하고 있는 코드다.정점이 있는 직사각형의 원점, 크기, 색 정보를 포함하는 구조PerRectUniforms도 전달되었습니다.이 정보들은 모든 정점의 정보를 추측하고 픽셀의 색을 계산하는 데 도움을 준다.
    rect_vertex_shader(
       uint vertex_id [[vertex_id]],
       constant vector_float2 *vertices [[buffer(0)]],
       constant PerRectUniforms *rect_uniforms [[buffer(1)]],
       constant Uniforms *uniforms [[buffer(2)]]
    ) {
       float2 pixel_space_position = vertices[vertex_id] * rect_uniforms->size + rect_uniforms->origin;
       vector_float2 viewport_size = uniforms->viewport_size;
    
       RectFragmentData out;
       out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
    // Normalize coordinates of the vertex
       out.position.xy = pixel_space_position / (viewport_size / 2.0);
       out.position.z = rect_uniforms->z_index / uniforms->max_z_index;
       out.color = rect_uniforms->background_color;
    
       return out;
    }
    
    fragment float4 rect_fragment_shader(RectFragmentData in [[stage_in]])
    {
       return in.color;
    }
    
    간결하게 보기 위해서, 우리는 렌더링 파이프를 위한 착색기만 주목할 것입니다.권장 사항:
  • 애플‘Using Metal to Draw a View’s Content’은 메탈키트로 보기를 만들고 렌더링 명령을 보내는 법을 배웠다
  • 애플‘Using a Render Pipeline to Render Primitives’은 렌더링 명령에서 셰이더를 사용하여 뷰에 모양을 그리는 방법을 설명합니다.
  • 우리는 위의 코드를 사용하여 메탈키트와 우리의 맞춤형 레이아웃 엔진과Warp에서 첫 번째 교체된 옵션을 그립니다.

    셰이더를 사용하여 사각형에 테두리를 그리는 방법


    테두리를 그릴 때 사각형의 크기와 모양은 변하지 않습니다.따라서 우리는 정점 착색기를 바꿀 필요가 없다.반대로, 우리는 세션 착색기만 편집할 수 있다.
    세그먼트 착색기는 픽셀을 한 번에 처리합니다.모든 픽셀에 대해 우리는 그것이 경계 안인지 경계 밖인지 분명히 해야 한다.우선, 우리는 픽셀 상한에 대응하는 경계 폭을 줄여서 경계를 계산한다.픽셀이 사각형 중심의 위쪽과 오른쪽에 있으면, 우리는 테두리의 위쪽과 테두리의 아래쪽을 사각형 각도로 줄여야 한다.

    다음과 같습니다.
    fragment float4 rect_fragment_shader(
           RectFragmentData in [[stage_in]],
    constant Uniforms *uniforms [[buffer(0)]])
    {
       vector_float2 border_corner = in.rect_corner;
       if (in.position.y >= in.rect_center.y) {
           border_corner.y -= in.border_bottom;
       } else {   
           border_corner.y -= in.border_top;
       }
       if (in.position.x >= in.rect_center.x) {
           border_corner.x -= in.border_right;
       } else {
           border_corner.x -= in.border_left;
       }
       ...
    
    }
    
    경계 구석을 획득하면 경계 구석 밖의 픽셀을 경계 색상으로 지정하고 경계 구석 안의 픽셀을 배경 색상으로 지정할 수 있습니다.
    ...
    return in.position.xy > border_corner ? border_color : background_color;
    
    Warp의 탭 표시줄에 테두리가 있는 사각형을 만드는 방법입니다.

    셰이더를 사용하여 원형 사각형을 그리는 방법


    직사각형의 각을 둥글게 하기 위해서, 우리는 세그먼트 착색기에 픽셀이 둥근 가장자리의 내부에 있는지 외부에 있는지 알려주는 프레임이 필요하다.
    거리장은 우리가 비사각형 변을 정의하는 데 도움을 주는 함수다.픽셀을 지정하여 가장 가까운 모양의 가장자리까지의 거리를 출력합니다.이것은 세그먼트 셰이더의 유용한 API로 한 번에 하나의 픽셀만 액세스할 수 있습니다.

    거리 필드를 사용하여 필렛 표시하기


    다음 그림은 직사각형(선)의 네 개의 거리 필드를 그렸습니다.각 선은 직사각형 가장자리와 같은 거리의 픽셀을 대표하며 지리상의 등고선도와 유사하다.각 거리 필드는 둥근 사각형의 컨투어와 일치합니다.

    필렛 사각형은 축소 사각형과 거리 도메인 크기corner_radius 내의 영역을 합친 것입니다.

    우리는 원시 직사각형 각에서 각 반경을 빼서 수축 직사각형의 새로운 직사각형 각을 유도한다.우리는 모양 밖의 픽셀이 투명하기를 희망한다. 즉, 색의 알파 채널이 0이 되기를 바란다.내부 픽셀은 불투명합니다. 즉, 알파=1입니다.

    원형 모양의 거리장은 직사각형을 축소하는 거리장에서 각반경을 줄이는 것이다.
    사각형 거리의 방정식은 다음과 같습니다.

    그중 p은 픽셀의 벡터이고, R는 직사각형 각의 벡터이다.Inigo Quilez의 오프셋 함수에 대한 추측을 보십시오.
    수축 사각형의 실제 R은 rect_corner-corner_radius입니다.그것을 대입하면 우리는 수축된 직사각형을 대체하는 거리 함수를 얻을 수 있다.그리고 우리는 각 반경을 줄여 원각 사각형의 거리 함수를 얻는다.
    float distance_from_rect(vector_float2 pixel_pos, vector_float2 rect_center, vector_float2 rect_corner, float corner_radius) {
       vector_float2 p = pixel_pos - rect_center;
       vector_float2 q = abs(p) - (rect_corner - corner_radius);
       return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - corner_radius;
    }
    

    톱니 저항


    위의 코드는 원각 사각형을 나타낼 것이다.하지만 원형 가장자리가 고르지 않은 것처럼 보입니다.
    출처 learnopengl.com
    이런 상황은 모양 내부와 외부의 픽셀 사이에 뾰족한 마감점이 있기 때문이다.우리는 지금 원각에 대해 스크린의 픽셀 격자에 적합하지 않기 때문에 이 문제만 만날 수 있다.해결 방안은 경계에서 픽셀 색을 점차적으로 바꾸는 것이다.편리한 함수 smoothstep(edge0, edge1, x) 는 0 Hermite interpolation 실행하는 것이다.
    if (corner_radius > 0) {
       color.a *= 1.0 - smoothstep(-0.75, -0.1, shape_distance);
    }
    
    테두리가 있는 원형 사각형에 대해 배경과 테두리 사이에서 같은 톱날 저항 처리를 실행합니다.이 코드에서 background_distance는 거리 사각형의 비경계 내용의 거리 필드를 가리킨다.
    background_distance = distance_from_rect(in.position.xy, in.rect_center, border_corner, corner_radius);
    if (border_color.a != 0) {
       color = mix(background_color, border_color, smoothstep(-0.5, 0.5, background_distance));
    }
    
    이제 Warp에서처럼 원형 경계 UI를 렌더링할 수 있습니다.

    셰이더를 사용하여 사각형을 그라데이션으로 채우는 방법


    선형 그래디언트는 시작 및 끝 색상과 시작 및 끝 좌표를 사용하여 지정할 수 있습니다.예를 들어, (0,0) 및 (0,1)을 좌표로 사용하여 수평 그래디언트를 지정할 수 있습니다.
    우리는 금속의 혼합 기능mix(start_color, end_color, h)을 사용하여 색깔을 혼합할 수 있다.그것은 start_color + (end_color – start_color) * h의 선형 혼합을 되돌려준다.
    우리는 변수h가 픽셀이 사다리 방향을 따라 행진하는 것을 대표하기를 바란다.이를 위해서는 픽셀의 위치를 그래디언트 방향으로 투영하는 것이 관건이다.그래디언트 끝에 더 가까운 픽셀은 더 큰 폭을, 그래디언트 시작점에 더 가까운 픽셀은 더 작은 폭을 가질 수 있습니다.

    float4 derive_color(float2 pixel_pos, float2 start, float2 end, float4 start_color, float4 end_color) {
       float2 adjusted_end = end - start;
       float h = dot(pixel_pos - start, adjusted_end) / dot(adjusted_end, adjusted_end);
       return mix(start_color, end_color, h);
    }
    
    이제 Warp의 제목과 같은 그라데이션 UI 요소를 생성할 수 있습니다.

    이 모든 것을 함께 놓아라


    당신은 완전한 코드 예시 here 를 읽을 수 있습니다.
    글꼴과 이미지를 제외하고 우리는 이 착색기에서 생성된 직사각형으로Warp의 UI 표면을 구성했다.GPU를 사용하여 렌더링하면 터미널 텍스트 및 UI를 최대 60fps 이상으로 4K 화면에서 렌더링할 수 있습니다.
    새롭고 복잡한 UI 구성 요소는 이러한 빌드 블록의 구성 요소입니다.이를 통해 견고하고 유지 보수가 가능한 UI 프레임워크를 만들 수 있습니다.우리의 모든 원어의 코드는 300줄밖에 없다.
    현대 사용자 인터페이스를 갖춘 고성능 단말기를 원한다면 저희 사이트here의 대기 명단에 가입하세요.

    좋은 웹페이지 즐겨찾기