본문 바로가기

Unity/작업방식

Unity: Workflow. 셰이더 그래프에서 단축키 사용하기 (1)

스크립터블 렌더 파이프라인이 도입되면서 유니티에서도 노드 기반 셰이더 에디팅이 가능해졌습니다. 프리뷰 태그는 뗀지 시간이 꽤 지났지만 셰이더랩 문법을 이용해 작성하던 것에 익숙해진 개발자들은 여전히 불편함을 토로하고 있습니다. 제대로 정립되지 않은 규칙들과 느린 개발 속도로 요구되는 기능들이 제때 들어오고 있지 않아 포럼은 말 그대로 불이 난 상태입니다.

 

 

셰이더 그래프를 활용해서 작업을 해보려고 이래저래 만져보고 있는데, 단축키 바인딩도 없고 이건 뭐... 쓰라고 만든건지 의심이 될 정도로 기능이 빈약합니다. 언리얼에서 머티리얼 작업하던 방식이 생각나 키보드를 이용해 단축키를 추가하는 확장을 개발해보려고 검색하던 찰나, 비슷한 문제로 포럼에 올라온 글을 발견합니다.

 

 

대략적으로, 언리얼 머티리얼 에디터에 있는 기능을 요구하는 포럼 글입니다. 로드맵에 얘기하라는 커뮤니티 매니저의 시큰둥한 답변에 아니나다를까, 사용자들의 마음은 모두 비슷하다는 점을 대변하듯 키보드 단축키 기능만큼 중요한 것이 어디있냐는 반응입니다.

 

저 역시 이 기능이 가장 먼저 추가되었어야 할 기능이라고 생각합니다. 궁예질을 하고 싶지는 않지만... 그럼에도 굳이 아직까지 추가되지 않았던 것은 언리얼 엔진에서 사용하는 키보드 바인딩 맵을 의식해서 나중으로 미뤘거나, 관련해서 전혀 불편함을 느끼지 못했거나 하는 이유겠지요. 

 

셰이더 그래프의 코드는 굉장히 폐쇄적으로 작성되어있습니다. 모두 internal, sealed로 작성되어 있으며 외부 패키지에서 전혀 건드릴 수 없도록 설계되어있습니다. 외부 확장기능을 고려하기는 커녕 최선을 다해 방어적으로 나오고 있다는 얘기입니다. 결국 패키지 코드를 수정해서 작업해야한다는 이야기인데 이 경우 업데이트를 받기가 귀찮아집니다. 매번 일부 코드를 파악하고 수정해야하기 때문입니다.

 

작업

위 포럼 글에서 제시된 코드를 가지고 일단 기능 구현을 시도해보겠습니다.

 

패키지 이동

유니티 에디터 프로젝트 뷰에서 Packages 폴더 내 Shader Graph 패키지 폴더를 찾습니다.

마우스 우클릭 - Show In Explorer 메뉴로 패키지 캐시가 위치한 폴더를 열어줍니다.

 

 

셰이더 그래프 패키지의 디렉터리가 열리면, 상위 폴더로 이동해 com.unity.shadergraph@x.x.x 폴더를 복사합니다.

 

 

현재 작업중인 프로젝트의 루트에 있는 Packages 폴더 안에 붙여넣습니다.

 

 

manifest.json을 텍스트 에디터로 열어 com.unity.shadergraph 관련 정의가 있는지 확인합니다.

만약 있다면 해당 라인을 지우고 저장합니다. 없다면 다른 패키지의 종속성으로 인해 추가된 패키지이므로 별다른 작업을 하지 않아도 됩니다.

 

 

이제 에디터로 돌아와 에셋의 임포트를 기다립니다. 끝나면 다시 에디터 프로젝트 뷰의 Packages/Shader Graph 폴더에 마우스 우클릭 - Show In Explorer 메뉴를 통해 프로젝트경로/Packages/com.unity.shadergraph@x.x.x 위치로 옮겨졌는지 확인합니다.

 

코드 수정

ShaderGraph/Editor/Drawing/Views/GraphEditorView.cs 소스 파일을 열어줍니다. 

 

namespace UnityEditor.ShaderGraph.Drawing { 안에 클래스를 넣어줍니다.

 

internal class KeyboardShortcutHelper
    {
        private MaterialGraphView GraphView { get; }
        private GraphData GraphData { get; }
 
        public KeyboardShortcutHelper(MaterialGraphView graphView, GraphData graph)
        {
            GraphView = graphView;
            GraphData = graph;
 
            GraphView.RegisterCallback<KeyDownEvent>(OnKeyDown);
        }
 
        void OnKeyDown(KeyDownEvent evt) {
            if (GraphData == null) return;
 
            Vector2 pos = evt.originalMousePosition;
 
            switch (evt.keyCode) {
                case KeyCode.O:
                    CreateNode(() => new OneMinusNode(), pos);
                    break;
                case KeyCode.M:
                    CreateNode(() => new MultiplyNode(), pos);
                    break;
                case KeyCode.L:
                    CreateNode(() => new LerpNode(), pos);
                    break;
                case KeyCode.D:
                    CreateNode(() => new DivideNode(), pos);
                    break;
                case KeyCode.S:
                    CreateNode(() => new SubtractNode(), pos);
                    break;
                case KeyCode.A:
                    CreateNode(() => new AddNode(), pos);
                    break;
                case KeyCode.F:
                    CreateNode(() => new FractionNode(), pos);
                    break;
                case KeyCode.Alpha1:
                    CreateNode(() => new Vector1Node(), pos);
                    break;
                case KeyCode.Alpha2:
                    CreateNode(() => new Vector2Node(), pos);
                    break;
                case KeyCode.Alpha3:
                    CreateNode(() => new Vector3Node(), pos);
                    break;
                case KeyCode.Alpha4:
                    CreateNode(() => new Vector4Node(), pos);
                    break;
                case KeyCode.Alpha5:
                    CreateNode(() => new ColorNode(), pos);
                    break;
            }
        }
 
        private void CreateNode(Func<AbstractMaterialNode> createNode, Vector2 pos) {
            AbstractMaterialNode multiplyNode = createNode();
 
            var drawState = multiplyNode.drawState;
            Vector2 p = GraphView.contentViewContainer.WorldToLocal(pos);
 
            drawState.position = new Rect(p.x, p.y, drawState.position.width, drawState.position.height);
            multiplyNode.drawState = drawState;
            GraphData.AddNode(multiplyNode);
        }
    }

 

그리고 GraphEditorView 클래스의 생성자 끝 부분에 아래 코드를 넣어줍니다.

 

KeyboardShortcutHelper shortcutHelper = new KeyboardShortcutHelper(graphView, graph);

 

대략 이런 모양이 됩니다.

 

수정된 해당 소스 파일은 여기에서 확인할 수 있습니다. (셰이더그래프 10.3.1 기준)

 

키보드 1, 2, 3, 4, 5, a, d, f, s, l, m, o 를 누르면 마우스 위치에 노드가 생성됩니다.

언리얼은 키보드를 누른채로 마우스 클릭을 해야 하지만, 일단은 노드가 키보드로 생성이 가능하다는 점에 만족해야겠습니다.

 

리플렉션과 후킹을 이용해 패키지 코드 수정 없이도 사용할 수 있도록 구현해 볼 예정입니다.

 

참고 사이트

 

Feature Request - Keyboard shortcuts are an essential and missing feature.

This has been brought up many many times. Could we please have some keyboard shortcuts, like every other big boy shader editor since 2010? 1,2,3,4,5...

forum.unity.com

 

다음 글에서 이어집니다.