[Unity] 여러 장의 텍스처를 섞어서 새로운 텍스처 만들기

박민주·2022년 3월 15일
0

Unity

목록 보기
22/40
post-thumbnail

캐릭터 합성 시 새로운 캐릭터를 생성해내기 위해 런타임에 수행될 텍스처 믹스 기능이 필요했다.


여기 예제에서는 프로젝트에서 쓰는 리소스 대신 무료 에셋을 이용했다
색깔을 아무거나 설정했더니 색조합이 구리당,,

이 기능을 위해 다음과 같이 목표와 구현하고자 하는 기능을 리스트업하고 하나씩 진행했다

목표

  • 레퍼런스 텍스처들에서 몸통과 글로브 부분의 색깔을 랜덤으로 추출
  • 추출된 색이 적용된 새로운 텍스처 생성
  • 생성된 텍스처를 메쉬에 적용

구현하고자 하는 기능

1. 원본 텍스처에서 특정 픽셀 색깔 추출

  • Texture2D의 public Color GetPixel(int x, int y); 을 이용했다.

2. 1번에서 뽑은 색을 '부분 믹스용 텍스처'의 알파만 있는 픽셀에 적용

  • 포토샵에서 텍스처의 몸통 부분(예시)만 알파값 0으로 만든 믹스용 텍스처를 만들었다.
  • 그리고 GetPixel을 이용해 픽셀 컬러를 가져와서 color.a == 0 인지 확인했다.
  • 해당 픽셀에 1번에서 뽑은 색깔을 public void SetPixel(int x, int y, Color color); 을 이용해서 설정해주었다.

3. 2번에서 만든 텍스처를 또 다른 텍스처와 합쳐서 새 텍스처 만들기

  • alpha값이 0이어서 따로 색을 변경해준 픽셀 외에는 원본 텍스처의 색을 그대로 적용했다.

4. 생성된 텍스처를 몬스터의 material에 적용

  • material의 shader에서 텍스처 프로퍼티의 name을 통해 적용해주었다.
  • ex. monsterMainMesh.material.SetTexture("_BaseMap", resultTex);
  • 이 때 텍스처 프로퍼티의 name은 쉐이더마다 다를 수 있는데,
    아래 코드를 통해 쉐이더에서 사용되는 프로퍼티를 출력한 다음,
    Texture인 것 중 메인텍스처일 것 같은 걸로 몇 개 해보았다.
            // 쉐이더 프로퍼티 출력
            string[] shaderPropertyTypes = new string[] { "Color", "Vector", "Float", "Range", "Texture" };
            int propertyCount = ShaderUtil.GetPropertyCount(monsterMainMesh.material.shader);

            for (int index = 0; index < propertyCount; ++index)
            {
                Debug.Log(ShaderUtil.GetPropertyName(monsterMainMesh.material.shader, index) + "      "
                + shaderPropertyTypes[(int)ShaderUtil.GetPropertyType(monsterMainMesh.material.shader, index)]);
            }
  • 특히 복잡한 쉐이더의 경우에는 쉐이더 코드를 봐도 잘 안보여서 이렇게 해야했다..!
    shaderPropertyTypes에 Texture만 지정하면 더 찾기 쉬울 것 같다.

5. 새로 생성된 텍스처를 로컬에 저장

  • System.IO를 이용해서 구현했다.

배운 점

1. Texture2D의 SetPixel/GetPixel 함수를 사용하려면 Read/Write Enabled가 true 여야 한다

2. GetPixel을 통해 색상 값을 가져오면 정확한 값이 나오지 않을 때도 있다 (?)

  • 처음에는 알파값이 아닌 흰색인 부분을 인식해서 픽셀 색상을 변경해주려고 했는데
    아래 사진과 같이 굴곡이 있는 부분은 구분이 잘 안되었다.
  • 실제 로그를 출력해봤을 때도 흰색, 검정색만 출력될 것이란 기대와 달리 이상한 색상 값들도 많이 출력됐다.
  • SetPixel 함수는 ARGB32, RGB24 와 Alpha8 텍스쳐 포맷에서만 사용 가능하다.
    원래 텍스처는 해당 포맷들이 아니라서 Texture2D 생성 시 포맷을 새로 지정해주었다.

전체 코드

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class TextureMix : MonoBehaviour
{
    public Button mixTextureBtn;

    public Texture2D[] parentTextures;
    public Texture2D alphaMixTexture1;
    public Texture2D alphaMixTexture2;

    public RawImage rawImagePart1;
    public RawImage rawImagePart2;
    public RawImage rawImageResult;

    public Renderer monsterMainMesh;
    public Renderer monsterPartMesh;

    private string directoryPath = "Assets/Resources/NewTextures/";
    public string fileName = "";


    void Start()
    {
        mixTextureBtn.onClick.AddListener(() =>
        {
            // 포맷이 될 텍스처 선정
            Texture2D formatTexture = parentTextures[0];
            // 믹스용 텍스처에서 픽셀을 전부 가져옴
            Color[] sourcePixels = alphaMixTexture1.GetPixels();

            // --------------------- Part 1 --------------------------------
            // - 레퍼런스 텍스처 중 하나 선정
            int randomIdx = UnityEngine.Random.Range(0, parentTextures.Length);
            Texture2D parentTexture = parentTextures[randomIdx];

            // - 몬스터에 적용시킬 resultTex 생성
            Texture2D resultTex = new Texture2D(formatTexture.width, formatTexture.height, TextureFormat.ARGB32, false);
            // - 선정된 부분 텍스처 확인을 위한 partTex_1 생성
            Texture2D partTex_1 = new Texture2D(formatTexture.width, formatTexture.height, TextureFormat.ARGB32, false);

            // - 믹스용 텍스처의 pixel을 조회하면서 Alpha값이 0인 부분이 나오면, parentTexture에서 그 부분의 색깔을 가져다가 입힘
            for (int h = 0; h < parentTexture.height; h++)
            {
                for (int w = 0; w < parentTexture.width; w++)
                {
                    Color color = sourcePixels[h * formatTexture.width + w];
                    // 부모의 컬러값을 받아옴 
                    Color parentColor = parentTexture.GetPixel(w, h);
                    if (color.a == 0)
                    {
                        partTex_1.SetPixel(w, h, parentColor); // 이 함수는 ARGB32, RGB24 와 Alpha8 텍스쳐 포맷에서만 작동
                    }
                    resultTex.SetPixel(w, h, parentColor);
                }
            }

            partTex_1.Apply();
            resultTex.Apply();

            rawImagePart1.texture = partTex_1;

            // --------------------- Part 2 --------------------------------
            randomIdx = UnityEngine.Random.Range(0, parentTextures.Length);
            parentTexture = parentTextures[randomIdx];
            
            Texture2D partTex_2 = new Texture2D(formatTexture.width, formatTexture.height, TextureFormat.ARGB32, false);
            sourcePixels = alphaMixTexture2.GetPixels();

            for (int h = 0; h < formatTexture.height; h++)
            {
                for (int w = 0; w < formatTexture.width; w++)
                {
                    Color color = sourcePixels[h * formatTexture.width + w];
                    Color parentColor = parentTexture.GetPixel(w, h);
                    if (color.a == 0)
                    {
                        partTex_2.SetPixel(w, h, parentColor);
                        resultTex.SetPixel(w, h, parentColor);
                    }                
                }
            }
            partTex_2.Apply();
            resultTex.Apply();

            rawImagePart2.texture = partTex_2;
            rawImageResult.texture = resultTex;

            monsterMainMesh.material.SetTexture("_BaseMap", resultTex);
            monsterPartMesh.material.SetTexture("_BaseMap", resultTex);

			// 새로 생성된 텍스처를 로컬에 저장
            SaveTexture2DToPNGFile(resultTex, directoryPath, fileName);
        });
       
    }

    private void SaveTexture2DToPNGFile(Texture2D texture, string directoryPath, string fileName)
    {
        
        if(false == Directory.Exists(directoryPath))
        {
            Directory.CreateDirectory(directoryPath);
        }

        byte[] texturePNGBytes = texture.EncodeToPNG();

        string filePath = directoryPath + fileName + ".png";
        File.WriteAllBytes(filePath, texturePNGBytes);
 
    }

}

부분 믹스용 텍스처 리소스 참고

profile
Game Programmer

1개의 댓글

comment-user-thumbnail
2022년 3월 22일

오 잘 보고 갑니다!

답글 달기