[DevLog] 안녕하세요 떵-개 오늘의 먹방은... 픽셀컴포져 짭 먹어볼께요!!😇🪽
네 안녕하세요 떵-개에요 오늘의 먹빵은....

픽셀 컴포져2 (SigmaComposer)(가제)
개요:
아 취업!!! 해서 로블록스하면서 담배 ㅈㄴ펴야지!!!! 그러면서 잼민이들 괴롭히기 ㅎㅎ!!! (갑자기 생긴 꿈)

-> 그럼 포폴이 있어야 되네? ㅠㅠㅠㅠㅠ 도스레바이이 ㅠㅠ
-> 어 잠시만 잠시만 좆도 마때.... 출시경험이 중요하다고 했지!!! 그리고 나는 모바일 출시경험 X
-> 아 근데 나는 게임 만들기 싫은데... (게임 개발잔데 1년동안 게임만들다 다 접고 게임도 접고 망한사람)
-> 진심 게임 만들때 마다 망함... 혼자 만들기 힘드노 ㅠㅠ

-> 그럼 내가 좋아하고 사람들도 마이너하게 관심 가질걸 해보자!!!
-> Shader Graph를 폰으로 할 수 있다면 사람들이 무언가를 만들지 않을까?? 하하하하하 난 천재여!!!
-> PixelComposer x Unity ShaderGraph x AfterEffect Let's GO!!!!!!

개꿀잼 플젝, 바로 무야지 ㅎㅎ

하ㅏ하하하하하 우리가 오늘 볼 결론을 스포해주마!!!!!

여기까지 구현됩니다 ㅎㅎ.
약간 연결선 위치가 이상하게 벗어나긴 하는데
그럼 이제 구현한거 보러 레쓰 고우!!! 개발 과정까진 장담 못하고
그리고 AI를 많이 사용했기 때문에 어 설명을 아주 풀어쓸꺼임 나도 좀 읽어야겠어 음.
머리 말
중간중간 긴코드는 안 읽어도 됨. 근데 내부가 궁금하면 보세용.
제 0장 존나 기획 바탕 기반 배경 등등 아키텍쳐

아 형님들 이거 고려해야하는게 있어요!!! 걍 ui밖에 없습니다!!!
카메라 없애고
UI Toolkit으로 구현할께요!! 사유: 자유로움 + Fast!!!!

그래서 툴킷으로 UI Window부터 가져올께요!!! 생성해야겠죠!!
얘가 해줍니다:

이게 최후의 게임 오브젝트임 참고로

자 일단 카메라는 뭐 없으면 그 에디터 화면에서 막 카메라없다고 쌩 난리쳐서 걍 둠.

자 이런식으로 작동합니다. Claude가 그려줬습니다. 내가 그리기 싫어!!
┌─────────────────────────────────────────────────────────────┐
│ MAIN LOOP │
└─────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [1] 사용자 입력 대기 │
│ - 노드 추가/삭제 │
│ - 연결 생성/제거 │
│ - 파라미터 조정 │
│ - Execute 버튼 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [2] Command 실행 │
│ - AddNodeCommand │
│ - ConnectSlotsCommand │
│ - DeleteNodeCommand │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [3] NodeGraph 업데이트 │
│ - nodes[] 변경 │
│ - connections[] 변경 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [4] 그래프 실행 │
│ - Topological Sort │
│ - 순서대로 Execute() │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [5] GPU 연산 │
│ - Shader 실행 │
│ - Texture 생성 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [6] 결과 캐싱 │
│ - cachedOutputs 저장 │
└───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ [7] UI 업데이트 │
│ - Preview 갱신 │
│ - Connection 다시 그리기 │
└───────────────────────────────────┘
│
▼
(다시 1번으로)
제 1장 배경 구현하기!!
Graph 특유의 배경을 구현해 볼께요 ㅎㅎ!!!

자 이제 걍 격자 도배하면 됩니다. 근데 이거 막 임계점 넘어갈때마다 촘촘해져야해요.
무한 느낌으로 다가 줌을 엄청하면 그 안에 격자들이 나타나고 이런식으로 계속 그려야합니다!!!
일단 위에있는 ComposerWindow.cs 에서 이거 얘 ui 만들어줄께요!!
uiDocument = gameObject.AddComponent<UIDocument>();
if (panelSettings != null)
{
uiDocument.panelSettings = panelSettings;
}
var rootVisualElement = uiDocument.rootVisualElement;
rootVisualElement.style.width = Length.Percent(100);
rootVisualElement.style.height = Length.Percent(100);
if (styleSheet != null)
{
rootVisualElement.styleSheets.Add(styleSheet);
}
if (inspectorStyleSheet != null)
{
rootVisualElement.styleSheets.Add(inspectorStyleSheet);
}
var horizontalContainer = new VisualElement();
horizontalContainer.style.flexDirection = FlexDirection.Row;
horizontalContainer.style.flexGrow = 1;
rootVisualElement.Add(horizontalContainer);
graphView = new NodeGraphView();
graphView.style.flexGrow = 1;
horizontalContainer.Add(graphView);
var inspector = new NodeInspector(graphView);
horizontalContainer.Add(inspector);
graphView.SetInspector(inspector);
걍 뭐 스크린 크기의 판때기를 만들고 USS들을 불러옵니다. uss는 걍 css랑 뭐 비슷합니다. 따라 만든 듯 ㅎㅎ
참고로 저는 디자인의 감각이 없어서 uss는 90% AI가 작성하였습니다. 후후 AI야 고마워!!
그다음에 horizontalContainer 가져옵니다. 이건 그래프 뷰(격자 뷰)랑 인스펙터 분리하려한건데 나중에 인스펙터를 팝업 윈도우 처럼 바꾸면 뭐 없어질 것임.
아무튼 GraphView와 Inspector을 Horizontal Container에 넣어줍니다. 앙 ㄱㅣ무띠!!
NodeGraphView라는게 있는데 이게 그 유명한 배경이오.
NodeGraphView 생성자에서 Grid그려주는 청년을 불러옵시다.
gridBackground = new GridBackground();
Add(gridBackground);
Add는 VisualElement의 함수인데 자식으로 다른 VisualElement를 추가해 줌.
public void SetOffset(Vector2 newOffset)
{
offset = newOffset;
MarkDirtyRepaint();
}
public void SetZoom(float newZoom)
{
zoom = newZoom;
UpdateGridSpacing();
MarkDirtyRepaint();
}
private void UpdateGridSpacing()
{
if (zoom >= 1f)
{
currentGridSpacing = baseGridSpacing * zoom;
currentThickSpacing = baseThickLineSpacing * zoom;
}
... (대충 중간 수치들)
else
{
currentGridSpacing = baseThickLineSpacing * 5f * zoom;
currentThickSpacing = baseThickLineSpacing * 10f * zoom;
}
currentGridSpacing = Mathf.Max(currentGridSpacing, 5f);
currentThickSpacing = Mathf.Max(currentThickSpacing, 20f);
}
그리드 업뎃해줍니다. 근데 하드코딩임. 줌되면 더 촘촘한 그리드 나오고 그거 걍 대충 구현해서 그럼. 뭐 그래프 내부에서 이동하거나 (Offset) 줌하면 다시 그려야해요. Dirty하면 그려집니다.
void OnGenerateVisualContent(MeshGenerationContext ctx)
{
var painter = ctx.painter2D;
float width = contentRect.width;
float height = contentRect.height;
if (currentGridSpacing < 10f)
{
return;
}
painter.lineWidth = 1.0f;
painter.strokeColor = lineColor;
float startX = offset.x % currentGridSpacing;
float startY = offset.y % currentGridSpacing;
for (float x = startX; x < width; x += currentGridSpacing)
{
painter.BeginPath();
painter.MoveTo(new Vector2(x, 0));
painter.LineTo(new Vector2(x, height));
painter.Stroke();
}
for (float y = startY; y < height; y += currentGridSpacing)
{
painter.BeginPath();
painter.MoveTo(new Vector2(0, y));
painter.LineTo(new Vector2(width, y));
painter.Stroke();
}
if (currentThickSpacing >= currentGridSpacing * 2f)
{
float thickStartX = offset.x % currentThickSpacing;
float thickStartY = offset.y % currentThickSpacing;
painter.strokeColor = thickLineColor;
painter.lineWidth = 2.0f;
for (float x = thickStartX; x < width; x += currentThickSpacing)
{
painter.BeginPath();
painter.MoveTo(new Vector2(x, 0));
painter.LineTo(new Vector2(x, height));
painter.Stroke();
}
for (float y = thickStartY; y < height; y += currentThickSpacing)
{
painter.BeginPath();
painter.MoveTo(new Vector2(0, y));
painter.LineTo(new Vector2(width, y));
painter.Stroke();
}
}
}
걍 포문으로 두꺼운 줄과 얇은 줄로 그려줍니다.

이렇게 그려집니다. 바우와우
격자 배경은 끝났습니다.
제 2장 노드 그래프 렌더링!!

노드들이나 뭐 커넥션 등등 그래프위에 많은게 그려집니다.
NodeGraphView는 노드 에디터 걍 총괄합니다. 그래프 위를 다 관리함(비주얼적으로).
GameManager급 형님입니다. 직업:
- 모든 노드들의 시각적 표현 관리
- 사용자 입력 처리 (클릭, 드래그, 줌, 팬)
- 노드 간 연결선 그리기
- Undo/Redo 시스템 관리
- Command 패턴으로 그래프 수정
ComposerWindow 바로 아래있는 고위 간부임.
NodeGraphView (VisualElement)
│
├─ [Layer 1] GridBackground ────────── 그리드 배경
│
├─ [Layer 2] ConnectionLayer ─────── 연결선 (중간)
│
├─ [Layer 3] NodeContainer ──────── 노드들 (최상위)
│ │
│ └─ NodeView[]
│ ├─ NodeView (Noise)
│ ├─ NodeView (Multiply)
│ └─ NodeView (Output)
│
├─ [Overlay] NodeCreationMenu ────── 노드 생성 메뉴
│
└─ [References]
├─ NodeGraph graph ────────── Core 데이터 모델
├─ Dictionary<Node, NodeView> ─ Node ↔ UI 매핑
├─ ConnectionDragHandler ───── 연결 드래그 관리
├─ NodeInspector ──────────── 속성 패널
└─ CommandHistory ─────────── Undo/Redo 스택
코드는 안까볼께요 너무 큼.
앞으로 요소들 렌더할 때 의문점이 생길 수 있기에 먼저 설명합니다.
얘가 그래프 위는 거의 다 총괄합니다.
제 3장 노드 구현하기!!

이거 걍 뭐 노드입니다. ㄹㅆㄱ
설명할거:
- 노드 생성 삭제
- 슬롯이 뭐고
노드 구현 갑시다
각 노드들은 Node를 상속받아요 아니면 뭐 망하고...
추상클래스 노드를 구현해야함!!!
public abstract class Node
{
public string id; // 고유 식별자 (GUID)
public string nodeName; // 노드 이름
public Vector2 position; // UI 상 위치
public List<NodeSlot> inputSlots; // 입력 슬롯 리스트
public List<NodeSlot> outputSlots; // 출력 슬롯 리스트
public bool isExecuted; // 실행 완료 플래그
public Dictionary<string, object> cachedOutputs; // 출력 캐시
}
이건 노드 생성자여
public Node()
{
id = System.Guid.NewGuid().ToString(); // 고유 ID 생성
nodeName = GetType().Name; // 클래스명을 노드명으로
InitializeSlots(); // 슬롯 초기화 (하위 클래스 구현)
}
노드 생성입니다.
public class AddNodeCommand : ICommand
{
public void Execute()
{
graph.AddNode(node);
onAdd?.Invoke(node); // UI 업데이트 콜백
}
public void Undo()
{
graph.RemoveNode(node);
onRemove?.Invoke(node);
}
}
Command 패턴인 이유는 Undo Redo를 구현할 계획입니다 하하...
자 이제 슬롯으로 가봅시다.
public class NodeSlot
{
public string id; // 슬롯 ID
public string displayName; // UI 표시명
public SlotType slotType; // Input/Output
public DataType dataType; // Texture/Float/Vector2/Vector3/Color
public Node owner; // 소유 노드
public NodeSlot connectedSlot; // 연결된 슬롯 (Input 슬롯만 사용)
}
슬롯 간 연결 시 타입 안정성 검사를 해야합니다.
public bool CanConnectTo(NodeSlot other)
{
if (other.owner == this.owner) return false; // 자기 자신 연결 방지
if (other.slotType == this.slotType) return false; // 같은 타입끼리 연결 불가
if (other.dataType != this.dataType) return false; // 데이터 타입 불일치
return true;
}
이런식으로 슬롯을 연동할 수 있습니다잉 헤헤...
Slot 언제 생성함?
Slot들은 Node들 마다 필요한게 달라요... 그래서 각자 생성시에 초기화 단계에서 필요한 슬롯들을 생성합니다.
근데 이제 비주얼은 어디서 생성되는지
궁금할 수 있는데 Node추가 할 때!!
NodeGraphView라고 보이는 것을 총괄하는 형님이 같이 생성해줍니다!!
public NodeView(Node node)
{
this.node = node;
AddToClassList("node");
headerLabel = new Label(node.nodeName);
headerLabel.AddToClassList("node__header");
Add(headerLabel);
var previewImage = new Image();
previewImage.AddToClassList("node__preview");
previewImage.scaleMode = ScaleMode.ScaleToFit;
Add(previewImage);
slotsContainer = new VisualElement();
slotsContainer.AddToClassList("node__slots");
Add(slotsContainer);
BuildSlots();
RegisterCallback<MouseDownEvent>(OnMouseDown);
RegisterCallback<MouseMoveEvent>(OnMouseMove);
RegisterCallback<MouseUpEvent>(OnMouseUp);
RegisterCallback<MouseLeaveEvent>(OnMouseLeave);
schedule.Execute(() => UpdatePreview(previewImage)).Every(100);
}
uss
.node {
position: absolute;
background-color: rgb(50, 50, 50);
border-color: rgb(80, 80, 80);
border-width: 2px;
...
Node가 표시되는 과정입니다.
Node 생성 (Core Layer)
↓
NodeView 생성 (UI Layer)
↓
BuildSlots() 호출
↓
각 Slot을 VisualElement로 변환
↓
화면에 표시
노드 작동 로직 궁금하노 ㅋㅋ
노드 작동 원리 top 1:
[그래프 실행 요청]
↓
[순환 참조 검사 (Cycle Detection)]
↓
[토폴로지컬 정렬 (Topological Sort)]
↓
[의존성 순서대로 노드 실행]
↓
[결과 캐싱 & 출력]
순환 참조는 뭐 노드끼리 충분히 일어날 수 있기때문에 막아야합니다.
근데 갑자기 토폴로지컬 정렬을 사용합니다.
한국어로는 위상정렬임 ㅋㅋ;;
얘가 노드 실행 순서 정해줌.
토폴로지 정렬이라고도 합니다 음 맛있노.
참고로 얘가 순환참조까지 알아 냅니다??? 나이스~
설명 ㄷㄱㅈ
https://m.blog.naver.com/ndb796/221236874984
이 글을 추천할께요. 설명을 잘 함!!
일단 내 설명도함. 저거 보고오면 안 봐도 됩니다. 근데 내 설명도 괜찮음.
트폴로지컬 정렬


이런 그래프가 있다고 합시다. 실행순서를 구해야하죠?
저희가 만드는 쉐이더 그래프? 도 저런식으로 생길 수 있습니다. 뭐 아웃풋은 하나긴 한데..
아무튼 당연하지만 처리 순서가 잘못되면 크게 결과가 바뀔 수 있습니다.
저희는 순서를 알기 쉽지만 그 컴퓨터는 그래프만 보고 순서 파악해야합니다...
이건 좀만 생각해봐도 그냥 막 실행하면 안됨.. 나 설명 안 해
그래서 정렬 과정을 설명을 하자면

자 이건데.. 별로 안 어려워요.
이거 그림이 좀더 어지러운데 그냥 큐 무시하고 저 그림이랑 위상 순서만 보세요. 그림으로 설명합니다.
Input이 없는 노드, 다른말로 진입차수가 0인 노드들이 있죠? 처음엔 0과 1이죠.
진입 차수가 0인 노드들을 큐에 넣어줍니다. 스택으로 해도 별로 상관 없을거에요 참고로. 큐 내부에서 순서는 상관 없어요.
그 다음 이 큐에 있는 애들을 위상 순서에 넣어줍니다. 큐는 비우세요.
그리고 이 0과 1이랑 이어져있던 노드들의 진입 차수를 -1하세요.
쉽게 그냥 0과 1을 뺏다고 생각합니다. 쟤네에서 뻗은 간선도 없애버려요.
그럼 다음으로 진입 차수가 없는 녀석들, 2와 4죠? 이친구들을 또 같은 방식으로 위상 순서에 넣어요. 반복함.
이렇게 점점 줄여서 위상순서를 만들면 됩니다. 이게 위상 정렬입니다.
위상순서로 노드를 실행시키면 됩니다.
DFS로 구현 되용
/// <summary>
/// Topological Sort를 사용한 실행 순서 계산
/// </summary>
private List<Node> GetExecutionOrder()
{
var executionOrder = new List<Node>();
var visited = new HashSet<Node>();
var tempMarked = new HashSet<Node>();
// 모든 노드에 대해 DFS 수행
foreach (var node in graph.nodes)
{
if (!visited.Contains(node))
{
if (!TopologicalSortVisit(node, visited, tempMarked, executionOrder))
{
return new List<Node>(); // 순환 참조 발견
}
}
}
// 역순으로 반환 (의존성이 먼저 실행되도록)
executionOrder.Reverse();
return executionOrder;
}
여기서 DFS 출발 시킵니다.
/// <summary>
/// DFS를 사용한 Topological Sort 방문
/// </summary>
private bool TopologicalSortVisit(Node node, HashSet<Node> visited, HashSet<Node> tempMarked, List<Node> executionOrder)
{
if (tempMarked.Contains(node))
{
return false; // 순환 참조 발견
}
if (visited.Contains(node))
{
return true;
}
tempMarked.Add(node);
// 이 노드의 입력에 연결된 노드들을 먼저 방문
foreach (var inputSlot in node.inputSlots)
{
if (inputSlot.connectedSlot != null)
{
var sourceNode = inputSlot.connectedSlot.owner;
if (!TopologicalSortVisit(sourceNode, visited, tempMarked, executionOrder))
{
return false;
}
}
}
tempMarked.Remove(node);
visited.Add(node);
executionOrder.Add(node);
return true;
}
자 이렇게 구현합니다.

알고리즘도 알려주는 나의 블로그 즉시 구독!!
중복으로 노드가 들어가면 (이미 있으면) 순환 참조임. 그래서 검사 쉬움 하하하ㅏ하핳!!
HashSet도 모른다면 그냥 c++에서 unordered_set과 동일하다.
참고로 노드와 슬롯에 관한 인풋은 VisualElements 내부 이벤트들을 이용하여 구현하였습니다.
노드를 선택하거나 삭제하는 것은 ISelectable과 IDeletable 인터페이스로 구현하였습니다. 이후에 커넥션(간선)도 추가 및 삭제가 되야하기에 인터페이스로 분리하였습니다.
후후ㅜ훟
일단 여기까지는 간선을 잇고 노드를 드래그 하고 드롭하는 것을 구현하였습니다. 야호!!
제 3장 인스펙터??
하하하하 인스펙터는 뭐야!!!!!
사실 이런 그래프에서는 BlackBoard라고 한다.

근데 나는 걍 인스펙터로 이름 지음 ㅠ
사용자: 노드 선택
↓
NodeView 클릭
↓
NodeGraphView.SelectNode(nodeView)
↓
inspector.ShowNodeProperties(nodeView.node)
↓
NodeInspector.ShowNodeProperties(node)
↓
[1] propertiesContainer.Clear() ← 기존 UI 제거
↓
[2] titleLabel.text = node.nodeName
↓
[3] previewImage 생성
↓
[4] 노드 타입별 분기
│
├─ if (node is NoiseNode)
│ └─ BuildNoiseNodeInspector(noiseNode)
│
├─ if (node is GradientNode)
│ └─ BuildGradientNodeInspector(gradientNode)
│
└─ ...
↓
[5] AddDeleteButton(node)
타겟층이 모바일이니 Delete버튼이 여기도 있다 ㅎㅎ!!
걍만듬
private void AddField<TField, TValue>(string label, TValue initialValue, Action<TValue> onValueChanged)
where TField : BaseField<TValue>, new()
{
var field = new TField { label = label, value = initialValue };
// FloatField의 경우 모바일에서 너무 작은 입력값을 막기 위해 정밀도 설정
if (field is FloatField floatField)
{
floatField.formatString = "F3";
}
field.RegisterValueChangedCallback(evt =>
{
onValueChanged?.Invoke(evt.newValue);
ExecuteNode(currentNode); // currentNode를 사용하도록 변경
});
field.AddToClassList("inspector__field");
propertiesContainer.Add(field);
}
// EnumField는 특별한 초기화가 필요하므로 별도 메서드
private void AddEnumField<TEnum>(string label, TEnum initialValue, Action<TEnum> onValueChanged) where TEnum : System.Enum
{
var enumField = new EnumField(label, initialValue);
enumField.RegisterValueChangedCallback(evt =>
{
if (evt.newValue is TEnum enumValue)
{
onValueChanged?.Invoke(enumValue);
ExecuteNode(currentNode);
}
});
enumField.AddToClassList("inspector__field");
propertiesContainer.Add(enumField);
}
자 이건 제너릭 필드랑 enum필드다. 컬러필드도 있긴한데 뭐 여기 쓰진 않겠다.
Enum과 Color필드는 이게 유니티에 기본으로 없어서 따로 만든 것이다.
private void BuildNoiseNodeInspector(NoiseNode node)
{
AddSection("Noise Properties");
// EnumField (Type)
AddEnumField("Noise Type", node.noiseType, newValue => node.noiseType = newValue);
// FloatField (Scale)
AddField<FloatField, float>("Scale", node.scale, newValue => node.scale = newValue);
// IntegerField (Octaves)
AddField<IntegerField, int>("Octaves", node.octaves, newValue => node.octaves = newValue);
// FloatField (Persistence)
AddField<FloatField, float>("Persistence", node.persistence, newValue => node.persistence = newValue);
// Vector2Field (Offset)
// Vector2Field는 BaseField<Vector2>를 상속하므로 AddField 사용 가능
AddField<Vector2Field, Vector2>("Offset", node.offset, newValue => node.offset = newValue);
}
근데 이거 걍 모든 노드에 대해서 이 빌드 함수를 만들어줘야함 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
그래서 노드 늘어날때마다 이 코드들이 생성되어야한다. -> 그냥 개 문제있는 구조.
이건 내가 나중에 더 가독성있는 코드로 변경하겠다!! 이거 좀 문제 있노.
아무튼 빌드하기 전에
propertiesContainer.Clear();
이걸로 컨테이너 비우고 저 필드 추가 함수로 컨테이너 채워서 Inspector를 구현하는 것이다.

아무튼 나중에 수정해보자. 더 나은 방법이 있을 것이다!!
if (node is GradientNode gradientNode)
{
BuildGradientNodeInspector(gradientNode);
}
else if (node is NoiseNode noiseNode)
{
BuildNoiseNodeInspector(noiseNode);
}
else if (node is ShapeNode shapeNode)
{
BuildShapeNodeInspector(shapeNode);
}
else if (node is ConstantColorNode constantColorNode)
{
BuildConstantColorNodeInspector(constantColorNode);
}
...
지금 현황임... ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
제 4장 쉐이더 연산
어 이거 쉐이더 저거 왜나오냐 어어 UI에서 쉐이더 원래 안돼는거 아니냐?? 어어어어 왜이러노... 왜이러노!!!
근데 이건 누가 한 말이고 나는 이게 뭔말인지 모른다 걍 안해봐서.
아무튼 구현해보니 잘 된다.
GPU에서 쉐이더를 연산한다.
그 후 RenderTexture로 뽑는다.
이걸 걍 UIElements.Image에 넣은 것임.
[GPU Layer] [UI Layer]
┌─────────────────┐ ┌─────────────────┐
│ Shader 실행 │ │ Image Element │
│ (Compute) │ ───────► │ (Display) │
│ │ RT │ │
│ RenderTexture │ Texture │ <img src=RT> │
└─────────────────┘ └─────────────────┘
쉐이더 연산 과정이나 설명하겠다.
근데 Shader파일 내부나 실행과정까진 설명 안함. 걍 어케 돌아가는지만 설명할께요..
1. 단일 노드 연산
예)Noise
이건 걍 쉐이더 내부에서 Noise를 연산하여 이미지로 뽑은거임.
2. 연산노드
수학 노드가 있다. 수학 노드는 이제 인풋들을 연산해 줌.
얘네는 걍 들어온 두개의 텍스쳐 또는 뭐 텍스쳐나 숫자, 아니면 뭐 숫자끼리 더 한다. UV에 맞게.
그리고 연산된 것은 그 노드의 캐싱된다. 그래서 여러번 여러번 결과가 연산되서 Output 나온다. 야후!!!
5. 기타
그 Command Pattern 노드 삭제/추가 간선 연결/해제에 추가 됨.
public class CommandHistory
{
private Stack<ICommand> undoStack = new Stack<ICommand>();
private Stack<ICommand> redoStack = new Stack<ICommand>();
private const int maxHistorySize = 50; // 최대 히스토리 크기
/// <summary>
/// 명령 실행 및 히스토리에 추가
/// </summary>
public void ExecuteCommand(ICommand command)
{
command.Execute();
undoStack.Push(command);
redoStack.Clear(); // 새 명령 실행 시 redo 스택 초기화
// 최대 크기 초과 시 가장 오래된 항목 제거
if (undoStack.Count > maxHistorySize)
{
var temp = new Stack<ICommand>();
for (int i = 0; i < maxHistorySize; i++)
{
temp.Push(undoStack.Pop());
}
undoStack.Clear();
while (temp.Count > 0)
{
undoStack.Push(temp.Pop());
}
}
Debug.Log($"Command executed: {command.GetDescription()}");
}
/// <summary>
/// Undo 실행
/// </summary>
public void Undo()
{
if (undoStack.Count == 0)
{
Debug.LogWarning("Nothing to undo");
return;
}
var command = undoStack.Pop();
command.Undo();
redoStack.Push(command);
Debug.Log($"Undo: {command.GetDescription()}");
}
/// <summary>
/// Redo 실행
/// </summary>
public void Redo()
{
if (redoStack.Count == 0)
{
Debug.LogWarning("Nothing to redo");
return;
}
var command = redoStack.Pop();
command.Execute();
undoStack.Push(command);
Debug.Log($"Redo: {command.GetDescription()}");
}
/// <summary>
/// Undo 가능 여부
/// </summary>
public bool CanUndo => undoStack.Count > 0;
/// <summary>
/// Redo 가능 여부
/// </summary>
public bool CanRedo => redoStack.Count > 0;
/// <summary>
/// 히스토리 초기화
/// </summary>
public void Clear()
{
undoStack.Clear();
redoStack.Clear();
}
}
6. 걍 더 할 일
일단 어 저거 미친 구조의 인스펙터를 바꿔야한다.
그리고 지금 OutputNode에 Texture랑 Color를 받는게 이게 걍 똑같음. 뭐 뭔차이지 어 뭐 아무튼 그렇다!!
그 다음 더 많은 노드나 이미지를 임포트해서 텍스쳐로 쓰는등 뭐시기 저시기 해야한다!!.
그리고 목표는 모바일이기 때문에 모바일 친화적인 조작을 만들어야한다!! 지금은 Undo Redo 확대 등등이 모바일에서 불가능 ㅠㅠ

끝끝끝끄틑긑
ㅃㅇㅃㅇ
'개발일지 > SigmaComposer' 카테고리의 다른 글
| [DevLog] 유니티 어트리뷰트 vs 리플렉션 뭐가 이김? 나는... 직렬화가 으악!!! 안돼요, Daddy 안 돼요 안디~~~🫥😶🌫️ SigmaComposer 2탄 ㅎ (2) | 2025.11.03 |
|---|