노멀 맵 입력 속성 생성
Properties
    {
        // 텍스처 입력: 기본 색상 또는 패턴 텍스처
        _BaseColor ("Base Color (Texture)", 2D) = "white" {}
        // 노멀 입력:
        [Normal] _Normal ("Normal", 2D) = "bump" {}
        // UV 스케일링을 위한 실수 값
        _UVScale ("UV Scale", Float) = 1.0
    }

노멀 구현
// 텍스처 선언
            Texture2D _BaseColor;
            Texture2D _Normal;
            // 텍스처 샘플러 상태 선언
            SamplerState sampler_BaseColor;
            SamplerState sampler_Normal;
            
            // 프래그먼트 셰이더 (픽셀 셰이더) 정의: 각 픽셀별로 실행
            half4 Frag(Varyings input) : SV_TARGET
            {
                float2 uv = input.uv0.xy;
                float2 coord = int2(floor(uv));
                float2 jitter = float2(GenerateHashedRandomFloat(asuint(coord.x)), GenerateHashedRandomFloat(asuint(coord.y) ^ ~asuint(coord.x)));
                float4 sc;
                sincos(jitter.xy * TWO_PI, sc.xy, sc.zw);
                float2x2 rotationX = float2x2(sc.x, sc.z,-sc.z, sc.x);
                float2x2 rotationZ = float2x2(sc.y, sc.w,-sc.w, sc.y);
                uv = mul(rotationX, uv);
                uv = mul(rotationZ, uv);
                // 보간된 UV 좌표를 사용하여 기본 색상 텍스처 샘플링
                half4 baseColor = _BaseColor.Sample(sampler_BaseColor, uv);
                half4 packedNormal = _Normal.Sample(sampler_Normal, uv);
                float3 unpackedNormal = UnpackNormal(packedNormal);
                // 최종 출력 색상 반환 
                return half4(unpackedNormal, 1.0h);
                return baseColor;
            }

Vertex Normal 추가
UnpackedNormal을 하긴 했지만 이건 Tangent Space의 Normal일 뿐이다. 즉, 면에서에만의 노멀. 바위와 여러 지형들 처럼 만드려면 Vertex의 노멀을 현재 Normal에 곱해주어야 면이 보고 있는 방향으로 UnpackedNormal을 회전시켜주어야 한다.
먼저, uv 좌표를 실제 폴리곤을 쓰는게 아니라 편의상 위치값을 썻던걸 Vertex의 uv로 쓰는걸로 수정하겠다.
// 텍스처 좌표 계산: 월드 공간의 XZ 평면 좌표에 스케일 적용 (타일링 목적)
                 output.uv0 = input.uv0.xy * _UVScale;

Shader "StochasticTilling"
{
    Properties
    {
        // 텍스처 입력: 기본 색상 또는 패턴 텍스처
        _BaseColor ("Base Color (Texture)", 2D) = "white" {}
        // 노멀 맵 텍스처 입력: [Normal] 어트리뷰트로 Unity Inspector에서 노멀 맵으로 표시
        [Normal] _Normal ("Normal", 2D) = "bump" {}
        // UV 스케일링을 위한 실수 값
        _UVScale ("UV Scale", Float) = 1.0
    }
    SubShader
    {
        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert // 정점 셰이더 함수 이름 지정
            #pragma fragment Frag // 프래그먼트 셰이더 함수 이름 지정
            // Unity의 기본 렌더 파이프라인(URP) 핵심 셰이더 라이브러리 포함
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            // 정점 속성 (Attributes): GPU로 전달되는 개별 메쉬 정점 데이터 구조
            struct Attributes
            {
                // 로컬 공간(모델 공간) 정점 위치
                float3 position : POSITION; 
                // 로컬 공간 정점 법선 벡터
                float3 normal : NORMAL; 
                // 로컬 공간 정점 탄젠트 벡터 (법선 매핑 등에 사용)
                float4 tangent : TANGENT; 
                // 첫 번째 UV 좌표 (텍스처 좌표)
                float2 uv0 : TEXCOORD0; 
            };
            // Varyings (인터폴레이터): 정점 셰이더에서 프래그먼트 셰이더로 보간되어 전달되는 데이터 구조
            struct Varyings
            {
                // 클립 공간 위치 (정점 셰이더의 필수 출력)
                float4 positionCS : SV_POSITION; 
                // 텍스처 좌표 (사용자 정의 보간 데이터)
                float2 uv0 : TEXCOORD0;
                // 월드 공간으로 변환된 정점 법선
                float3 normal : NORMAL;
                // 월드 공간으로 변환된 정점 탄젠트 및 w 컴포넌트(바이탄젠트 부호)
                float4 tangent : TANGENT;
            };
            // 재질별 설정 (CPU에서 설정 가능)
            cbuffer UnityPerMaterial
            {
                float _UVScale; // UV 스케일 값
            }
            
            // 정점 셰이더 정의: 각 정점별로 실행
             Varyings Vert(Attributes input)
            {
                 // 로컬 공간 위치를 월드 공간 위치로 변환
                 float3 position = TransformObjectToWorld(input.position);
                 Varyings output;
                 // 월드 공간 위치를 클립 공간 위치로 변환 (투영 및 화면 좌표 결정)
                 output.positionCS = TransformWorldToHClip(position);
                 // 텍스처 좌표 계산: 월드 공간의 XZ 평면 좌표에 스케일 적용 (타일링 목적)
                 output.uv0 = input.uv0.xy * _UVScale;
                 
                 // 로컬 법선 벡터를 월드 공간 법선 벡터로 변환
                 output.normal = TransformObjectToWorldNormal(input.normal);
                 
                 // 로컬 탄젠트 벡터 (xyz)를 월드 공간 방향 벡터로 변환하고, w 컴포넌트(부호)는 그대로 유지
                 output.tangent  = float4(TransformObjectToWorldDir(input.tangent.xyz), sign((float)input.tangent.w));
                 return output;
            }
            // 텍스처 선언
            Texture2D _BaseColor;
            Texture2D _Normal; // 노멀 맵 텍스처 선언
            // 텍스처 샘플러 상태 선언
            SamplerState sampler_BaseColor;
            SamplerState sampler_Normal; // 노멀 맵 샘플러 선언
            
            // 프래그먼트 셰이더 (픽셀 셰이더) 정의: 각 픽셀별로 실행
            half4 Frag(Varyings input) : SV_TARGET
            {
                float2 uv = input.uv0.xy;
                float2 coord = floor(uv);
                // 스토캐스틱 타일링을 위한 랜덤(해시) 오프셋/회전 값 생성
                float2 jitter = float2(GenerateHashedRandomFloat(~asuint(coord.x)), GenerateHashedRandomFloat(asuint(coord.y) ^ ~asuint(coord.x)));
                float4 sc;
                // 2D 회전을 위한 sincos 값 계산
                sincos(jitter.xy * TWO_PI, sc.xy, sc.zw);
                float2x2 rotationX = float2x2(sc.x, sc.z,-sc.z, sc.x);
                float2x2 rotationZ = float2x2(sc.y, sc.w,-sc.w, sc.y);
                
                // UV 좌표에 랜덤 회전 적용
                uv = mul(rotationX, uv);
                uv = mul(rotationZ, uv);
                // World-Space TBN (Tangent-Bitangent-Normal) 행렬 구성 시작
                // 월드 공간의 법선 벡터 (정점 셰이더에서 보간됨)
                float3 normal = normalize(input.normal); 
                // 월드 공간의 탄젠트 벡터 (정점 셰이더에서 보간됨)
                float3 tangent = normalize(input.tangent.xyz); 
                
                // 바이탄젠트 벡터 계산: 법선(N)과 탄젠트(T)의 외적(cross product) 후, 
                // 정점 데이터의 W 컴포넌트(input.tangent.w)와 Unity의 스케일링 인자(GetOddNegativeScale)를 곱하여 방향 보정
                float3 bitangent = cross(normal, tangent) * sign((float)input.tangent.w) * GetOddNegativeScale();
                
                // TBN 행렬(LookAt) 구성: Tangent, Bitangent, Normal 순서로 행렬 생성
                float3x3 lookAt = float3x3(tangent, bitangent, normal);
                // 기본 색상 텍스처 샘플링 (스토캐스틱 UV 사용)
                half4 baseColor = _BaseColor.Sample(sampler_BaseColor, uv);
                
                // 노멀 맵 샘플링 및 언팩(Unpack): 노멀 맵 텍스처에서 탄젠트 공간(Tangent Space) 노멀 벡터를 읽어옴
                half3 normalTS = UnpackNormal(_Normal.Sample(sampler_Normal, uv));
                
                // 탄젠트 공간 노멀(normalTS)을 TBN 행렬을 곱하여 월드 공간 노멀(normal)로 변환
                normal = mul(lookAt, normalTS);
                // 경고: 아래 코드는 법선 디버그 출력을 반환하므로, 최종 색상 출력을 막습니다.
                // return half4(abs(normal), 1.0h); 
                
                // 최종 출력 색상 반환 (이전 줄의 return이 제거되어야 이 코드가 실행됨)
                return baseColor;
            }
            
            ENDHLSL
        }
    }
    
}

라이팅 넣기
라이팅 라이브러리로 변경
// Unity의 기본 렌더 파이프라인(URP) 핵심 셰이더 라이브러리 포함
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
빛의 각도에 따라 지면에 닿는 면적이 달라지는데, 이를 lambert라고 부르고, 이는 cos함수로 구현한다.
		Light mainLight = GetMainLight();
                float lambert = dot(-mainLight.direction, normal);
                
                // 최종 출력 색상 반환 
                return half4(baseColor.xyz * lambert, 1.0h);
'공부 일지 > 그래픽스' 카테고리의 다른 글
| GPU로 풀 그리기 #2 (0) | 2025.10.05 | 
|---|---|
| GPU로 풀 그리기 #1 (0) | 2025.10.05 | 
| 스토카스틱 타일링 쉐이더 구현 (0) | 2025.10.05 | 
| OpenGL API [OpenGL로 배우는 3차원 컴퓨터 그래픽스] (0) | 2025.01.20 | 
| 컴퓨터 그래픽스 기본 이론 [OpenGL로 배우는 3차원 컴퓨터 그래픽스] (0) | 2025.01.20 | 
