앱인토스 개발자센터 로고
Skip to content

WebGL 2.0 렌더링

앱인토스 Unity 게임에서 WebGL 2.0의 고급 기능을 활용하여 성능과 품질을 크게 향상시키는 방법을 제공해요.


1. WebGL 2.0 vs WebGL 1.0

주요 개선사항

🚀 WebGL 2.0 고급 기능
├── 렌더링 향상
│   ├── Multiple Render Targets (MRT)
│   ├── Instanced Rendering
│   ├── Uniform Buffer Objects (UBO)
│   └── Transform Feedback
├── 텍스처 기능
│   ├── 3D Textures
│   ├── Texture Arrays
│   ├── Integer Textures
│   └── Compressed Texture Formats
├── 컴퓨팅 기능
│   ├── Vertex Array Objects (VAO)
│   ├── Sampler Objects
│   ├── Sync Objects
│   └── Query Objects
└── 셰이더 향상
    ├── GLSL ES 3.0
    ├── Fragment Shader 고급 기능
    ├── Vertex Shader 확장
    └── 조건부 렌더링

WebGL 2.0 지원 감지

c#
public class WebGL2Detector : MonoBehaviour
{
    public static bool IsWebGL2Supported()
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        return Application.platform == RuntimePlatform.WebGLPlayer && 
               SystemInfo.graphicsDeviceVersion.Contains("WebGL 2.0");
        #else
        return false;
        #endif
    }
    
    public static WebGLCapabilities GetWebGLCapabilities()
    {
        var capabilities = new WebGLCapabilities();
        
        #if UNITY_WEBGL && !UNITY_EDITOR
        // WebGL 버전 확인
        capabilities.isWebGL2 = IsWebGL2Supported();
        
        // 최대 텍스처 크기
        capabilities.maxTextureSize = SystemInfo.maxTextureSize;
        
        // 렌더 타겟 지원
        capabilities.supportsMultipleRenderTargets = SystemInfo.supportedRenderTargetCount > 1;
        
        // 인스턴싱 지원
        capabilities.supportsInstancing = SystemInfo.supportsInstancing;
        
        // 컴퓨트 셰이더 지원 (WebGL 2.0에서는 제한적)
        capabilities.supportsComputeShaders = SystemInfo.supportsComputeShaders;
        
        // 압축 텍스처 포맷
        capabilities.supportsASTC = SystemInfo.SupportsTextureFormat(TextureFormat.ASTC_4x4);
        capabilities.supportsETC2 = SystemInfo.SupportsTextureFormat(TextureFormat.ETC2_RGBA8);
        capabilities.supportsDXT = SystemInfo.SupportsTextureFormat(TextureFormat.DXT5);
        
        Debug.Log($"WebGL 능력: {capabilities}");
        #endif
        
        return capabilities;
    }
}

[System.Serializable]
public class WebGLCapabilities
{
    public bool isWebGL2;
    public int maxTextureSize;
    public bool supportsMultipleRenderTargets;
    public bool supportsInstancing;
    public bool supportsComputeShaders;
    public bool supportsASTC;
    public bool supportsETC2;
    public bool supportsDXT;
    
    public override string ToString()
    {
        return $"WebGL2: {isWebGL2}, MaxTexSize: {maxTextureSize}, MRT: {supportsMultipleRenderTargets}, " +
               $"Instancing: {supportsInstancing}, ASTC: {supportsASTC}, ETC2: {supportsETC2}";
    }
}

2. WebGL 2.0 렌더링 최적화

Multiple Render Targets (MRT) 활용

c#
public class WebGL2MRTRenderer : MonoBehaviour
{
    [Header("MRT 설정")]
    public bool enableMRT = true;
    public int renderTargetCount = 4;
    public RenderTextureFormat[] rtFormats = {
        RenderTextureFormat.ARGB32,    // 알베도
        RenderTextureFormat.ARGB32,    // 노멀
        RenderTextureFormat.RG16,      // 깊이/거칠기
        RenderTextureFormat.ARGB32     // 이미시브/AO
    };
    
    [Header("앱인토스 최적화")]
    public bool adaptToDevicePerformance = true;
    public bool enableQualityScaling = true;
    
    private RenderTexture[] renderTargets;
    private Camera targetCamera;
    private Material deferredMaterial;
    
    void Start()
    {
        #if UNITY_WEBGL
        if (WebGL2Detector.IsWebGL2Supported() && enableMRT)
        {
            SetupMultipleRenderTargets();
        }
        else
        {
            SetupFallbackRendering();
        }
        #endif
    }
    
    void SetupMultipleRenderTargets()
    {
        targetCamera = GetComponent<Camera>();
        
        // 기기 성능에 따른 렌더 타겟 수 조정
        if (adaptToDevicePerformance)
        {
            AdjustRenderTargetCount();
        }
        
        // 렌더 타겟 생성
        CreateRenderTargets();
        
        // MRT 셰이더 설정
        SetupMRTShader();
        
        Debug.Log($"WebGL 2.0 MRT 렌더링 설정 완료: {renderTargetCount}개 타겟");
    }
    
    void AdjustRenderTargetCount()
    {
        // 앱인토스 환경에서 성능에 따른 MRT 개수 조정
        int memoryMB = SystemInfo.systemMemorySize / 1024;
        
        if (memoryMB < 2048) // 2GB 미만
        {
            renderTargetCount = 2; // 기본 + 노멀
        }
        else if (memoryMB < 4096) // 4GB 미만  
        {
            renderTargetCount = 3; // + 깊이/거칠기
        }
        // 4GB 이상은 기본값 4개 유지
        
        Debug.Log($"기기 메모리 ({memoryMB}MB)에 따른 MRT 타겟 수 조정: {renderTargetCount}");
    }
    
    void CreateRenderTargets()
    {
        renderTargets = new RenderTexture[renderTargetCount];
        
        int width = Screen.width;
        int height = Screen.height;
        
        // 앱인토스 환경에 따른 해상도 스케일링
        if (enableQualityScaling)
        {
            float qualityScale = GetQualityScale();
            width = Mathf.RoundToInt(width * qualityScale);
            height = Mathf.RoundToInt(height * qualityScale);
        }
        
        for (int i = 0; i < renderTargetCount; i++)
        {
            renderTargets[i] = new RenderTexture(width, height, 0, rtFormats[i])
            {
                name = $"MRT_Target_{i}",
                enableRandomWrite = false,
                useMipMap = false,
                antiAliasing = 1
            };
            renderTargets[i].Create();
        }
        
        // 카메라에 렌더 타겟 설정
        targetCamera.SetTargetBuffers(
            renderTargets.Select(rt => rt.colorBuffer).ToArray(),
            renderTargets[0].depthBuffer
        );
    }
    
    float GetQualityScale()
    {
        // 앱인토스 성능 분석을 통한 품질 스케일 결정
        var perfMonitor = AppsInTossPerformanceMonitor.Instance;
        
        if (perfMonitor != null)
        {
            var currentPerf = perfMonitor.GetCurrentPerformance();
            if (currentPerf != null)
            {
                if (currentPerf.fps < 20) return 0.7f;
                else if (currentPerf.fps < 30) return 0.85f;
                else return 1.0f;
            }
        }
        
        return 1.0f; // 기본값
    }
    
    void SetupMRTShader()
    {
        // MRT용 셰이더 로드 및 설정
        deferredMaterial = Resources.Load<Material>("Shaders/AppsInToss_WebGL2_MRT");
        
        if (deferredMaterial != null)
        {
            // 셰이더 키워드 설정
            deferredMaterial.EnableKeyword("WEBGL2_MRT");
            deferredMaterial.SetInt("_RenderTargetCount", renderTargetCount);
            
            // 앱인토스 특화 설정
            deferredMaterial.EnableKeyword("APPS_IN_TOSS_OPTIMIZED");
        }
    }
    
    void SetupFallbackRendering()
    {
        Debug.Log("WebGL 2.0 미지원 - 전통적 렌더링 사용");
        
        // WebGL 1.0 호환 렌더링 설정
        targetCamera = GetComponent<Camera>();
        targetCamera.renderingPath = RenderingPath.Forward;
    }
    
    // 디퍼드 셰이딩을 위한 G-Buffer 합성
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (renderTargets == null || deferredMaterial == null)
        {
            Graphics.Blit(source, destination);
            return;
        }
        
        // G-Buffer를 사용한 디퍼드 셰이딩
        deferredMaterial.SetTexture("_GBuffer0", renderTargets[0]); // 알베도
        deferredMaterial.SetTexture("_GBuffer1", renderTargets[1]); // 노멀
        
        if (renderTargetCount > 2)
        {
            deferredMaterial.SetTexture("_GBuffer2", renderTargets[2]); // 깊이/거칠기
        }
        
        if (renderTargetCount > 3)
        {
            deferredMaterial.SetTexture("_GBuffer3", renderTargets[3]); // 이미시브/AO
        }
        
        // 최종 렌더링
        Graphics.Blit(source, destination, deferredMaterial);
    }
    
    void OnDestroy()
    {
        if (renderTargets != null)
        {
            foreach (var rt in renderTargets)
            {
                if (rt != null)
                {
                    rt.Release();
                }
            }
        }
    }
}

3. 인스턴스드 렌더링

GPU 인스턴싱으로 드로우콜 최적화

c#
public class WebGL2InstancedRenderer : MonoBehaviour
{
    [Header("인스턴싱 설정")]
    public Mesh instanceMesh;
    public Material instanceMaterial;
    public int maxInstanceCount = 1000;
    
    [Header("앱인토스 최적화")]
    public bool enableAdaptiveInstancing = true;
    public bool enableFrustumCulling = true;
    public bool enableLODInstancing = true;
    
    // 인스턴스 데이터
    private Matrix4x4[] instanceMatrices;
    private Vector4[] instanceColors;
    private float[] instanceLODs;
    private MaterialPropertyBlock propertyBlock;
    
    // WebGL 2.0 전용 버퍼
    private ComputeBuffer matrixBuffer;
    private ComputeBuffer colorBuffer;
    private ComputeBuffer argsBuffer;
    
    void Start()
    {
        #if UNITY_WEBGL
        if (WebGL2Detector.IsWebGL2Supported())
        {
            SetupInstancedRendering();
        }
        else
        {
            SetupFallbackInstancing();
        }
        #endif
    }
    
    void SetupInstancedRendering()
    {
        // 앱인토스 환경에 맞는 인스턴스 수 조정
        if (enableAdaptiveInstancing)
        {
            AdjustInstanceCountForDevice();
        }
        
        // 데이터 배열 초기화
        InitializeInstanceData();
        
        // WebGL 2.0 버퍼 생성
        CreateInstanceBuffers();
        
        // 머티리얼 설정
        SetupInstanceMaterial();
        
        Debug.Log($"WebGL 2.0 인스턴스드 렌더링 설정: {maxInstanceCount}개 인스턴스");
    }
    
    void AdjustInstanceCountForDevice()
    {
        var capabilities = WebGL2Detector.GetWebGLCapabilities();
        
        // GPU 메모리 기반 인스턴스 수 조정
        int gpuMemoryMB = SystemInfo.graphicsMemorySize;
        
        if (gpuMemoryMB < 512) // 저사양 GPU
        {
            maxInstanceCount = 250;
        }
        else if (gpuMemoryMB < 1024) // 중사양 GPU
        {
            maxInstanceCount = 500;
        }
        // 고사양은 기본값 1000 유지
        
        Debug.Log($"GPU 메모리 ({gpuMemoryMB}MB)에 따른 인스턴스 수 조정: {maxInstanceCount}");
    }
    
    void InitializeInstanceData()
    {
        instanceMatrices = new Matrix4x4[maxInstanceCount];
        instanceColors = new Vector4[maxInstanceCount];
        instanceLODs = new float[maxInstanceCount];
        
        // 초기 인스턴스 위치 및 속성 설정
        for (int i = 0; i < maxInstanceCount; i++)
        {
            // 랜덤 위치 생성
            Vector3 position = new Vector3(
                UnityEngine.Random.Range(-50f, 50f),
                0,
                UnityEngine.Random.Range(-50f, 50f)
            );
            
            instanceMatrices[i] = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
            instanceColors[i] = new Vector4(
                UnityEngine.Random.value,
                UnityEngine.Random.value,
                UnityEngine.Random.value,
                1.0f
            );
            instanceLODs[i] = CalculateLODLevel(position);
        }
        
        propertyBlock = new MaterialPropertyBlock();
    }
    
    float CalculateLODLevel(Vector3 position)
    {
        if (!enableLODInstancing) return 1.0f;
        
        Camera cam = Camera.main;
        if (cam == null) return 1.0f;
        
        float distance = Vector3.Distance(cam.transform.position, position);
        
        if (distance < 20f) return 1.0f;      // 고품질
        else if (distance < 50f) return 0.7f; // 중품질
        else return 0.4f;                     // 저품질
    }
    
    void CreateInstanceBuffers()
    {
        // 변환 행렬 버퍼
        matrixBuffer = new ComputeBuffer(maxInstanceCount, 16 * sizeof(float)); // Matrix4x4
        matrixBuffer.SetData(instanceMatrices);
        
        // 색상 버퍼  
        colorBuffer = new ComputeBuffer(maxInstanceCount, 4 * sizeof(float)); // Vector4
        colorBuffer.SetData(instanceColors);
        
        // 간접 렌더링 인수 버퍼
        uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
        args[0] = (uint)instanceMesh.GetIndexCount(0); // 인덱스 수
        args[1] = (uint)maxInstanceCount;              // 인스턴스 수
        args[2] = (uint)instanceMesh.GetIndexStart(0); // 시작 인덱스
        args[3] = (uint)instanceMesh.GetBaseVertex(0); // 기본 버텍스
        
        argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
        argsBuffer.SetData(args);
    }
    
    void SetupInstanceMaterial()
    {
        if (instanceMaterial != null)
        {
            // WebGL 2.0 인스턴싱 키워드 활성화
            instanceMaterial.EnableKeyword("WEBGL2_INSTANCING");
            instanceMaterial.EnableKeyword("APPS_IN_TOSS_OPTIMIZED");
            
            // 버퍼를 머티리얼에 바인딩
            instanceMaterial.SetBuffer("_InstanceMatrices", matrixBuffer);
            instanceMaterial.SetBuffer("_InstanceColors", colorBuffer);
            
            // 앱인토스 특화 설정
            instanceMaterial.SetFloat("_AppsInTossQuality", GetCurrentQualityLevel());
        }
    }
    
    float GetCurrentQualityLevel()
    {
        // 현재 앱인토스 성능에 따른 품질 레벨 반환
        var perfMonitor = AppsInTossPerformanceMonitor.Instance;
        
        if (perfMonitor != null)
        {
            var currentPerf = perfMonitor.GetCurrentPerformance();
            if (currentPerf != null)
            {
                if (currentPerf.fps >= 45) return 1.0f;      // 고품질
                else if (currentPerf.fps >= 25) return 0.7f; // 중품질
                else return 0.4f;                            // 저품질
            }
        }
        
        return 0.7f; // 기본값
    }
    
    void Update()
    {
        if (instanceMaterial == null || argsBuffer == null) return;
        
        // 프러스텀 컬링 (옵션)
        if (enableFrustumCulling)
        {
            UpdateVisibleInstances();
        }
        
        // 인스턴스 데이터 업데이트
        UpdateInstanceData();
        
        // GPU 인스턴스드 렌더링 실행
        Graphics.DrawMeshInstancedIndirect(
            instanceMesh,
            0,
            instanceMaterial,
            new Bounds(Vector3.zero, new Vector3(100, 100, 100)),
            argsBuffer,
            0,
            propertyBlock
        );
    }
    
    void UpdateVisibleInstances()
    {
        Camera cam = Camera.main;
        if (cam == null) return;
        
        Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(cam);
        int visibleCount = 0;
        
        for (int i = 0; i < maxInstanceCount; i++)
        {
            Vector3 position = instanceMatrices[i].GetColumn(3);
            
            if (GeometryUtility.TestPlanesAABB(frustumPlanes, new Bounds(position, Vector3.one)))
            {
                // 보이는 인스턴스만 렌더링 배열 앞쪽으로 이동
                if (visibleCount != i)
                {
                    instanceMatrices[visibleCount] = instanceMatrices[i];
                    instanceColors[visibleCount] = instanceColors[i];
                }
                visibleCount++;
            }
        }
        
        // 렌더링할 인스턴스 수 업데이트
        uint[] args = new uint[5];
        argsBuffer.GetData(args);
        args[1] = (uint)visibleCount;
        argsBuffer.SetData(args);
    }
    
    void UpdateInstanceData()
    {
        // 동적 인스턴스 데이터 업데이트 (필요한 경우)
        bool needsUpdate = false;
        
        // LOD 레벨 업데이트
        if (enableLODInstancing)
        {
            for (int i = 0; i < maxInstanceCount; i++)
            {
                Vector3 position = instanceMatrices[i].GetColumn(3);
                float newLOD = CalculateLODLevel(position);
                
                if (Mathf.Abs(instanceLODs[i] - newLOD) > 0.1f)
                {
                    instanceLODs[i] = newLOD;
                    needsUpdate = true;
                }
            }
        }
        
        // 버퍼 업데이트 (필요한 경우만)
        if (needsUpdate)
        {
            matrixBuffer.SetData(instanceMatrices);
            colorBuffer.SetData(instanceColors);
        }
    }
    
    void SetupFallbackInstancing()
    {
        Debug.Log("WebGL 2.0 미지원 - 기본 인스턴싱 사용");
        
        // WebGL 1.0 호환 인스턴싱 (제한적)
        maxInstanceCount = 100; // 성능상 제한
        InitializeInstanceData();
    }
    
    void OnDestroy()
    {
        // 버퍼 정리
        matrixBuffer?.Release();
        colorBuffer?.Release();
        argsBuffer?.Release();
    }
    
    // 성능 통계
    void OnGUI()
    {
        if (Application.isEditor)
        {
            GUILayout.BeginArea(new Rect(10, 100, 300, 100));
            GUILayout.Label($"WebGL 2.0 인스턴싱 통계:");
            GUILayout.Label($"인스턴스 수: {maxInstanceCount}");
            GUILayout.Label($"메시: {instanceMesh?.name}");
            GUILayout.Label($"GPU 메모리: {SystemInfo.graphicsMemorySize}MB");
            GUILayout.EndArea();
        }
    }
}

4. 고급 셰이더 기능

GLSL ES 3.0 활용

hlsl
// AppsInToss_WebGL2_Advanced.shader
Shader "AppsInToss/WebGL2/Advanced"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Smoothness ("Smoothness", Range(0,1)) = 0.5
        
        [Header(Apps In Toss Optimization)]
        _QualityLevel ("Quality Level", Range(0,1)) = 1.0
        _PerformanceMode ("Performance Mode", Float) = 0
    }
    
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }
        LOD 300
        
        Pass
        {
            Name "FORWARD"
            Tags { "LightMode"="ForwardBase" }
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            // WebGL 2.0 전용 기능 활성화
            #pragma target 3.0
            #pragma require webgl2
            
            // 앱인토스 최적화 키워드
            #pragma shader_feature APPS_IN_TOSS_OPTIMIZED
            #pragma shader_feature WEBGL2_MRT
            #pragma shader_feature WEBGL2_INSTANCING
            #pragma shader_feature MOBILE_OPTIMIZED
            
            // 인스턴싱 지원
            #pragma multi_compile_instancing
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
                UNITY_VERTEX_OUTPUT_STEREO
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            half _Metallic;
            half _Smoothness;
            
            // 앱인토스 최적화 변수
            half _QualityLevel;
            half _PerformanceMode;
            
            // WebGL 2.0 전용 Uniform Buffer Object
            #ifdef WEBGL2_UBO
            layout(std140) uniform AppsInTossSettings
            {
                float4 globalTint;
                float4 lightingSettings;
                float2 screenParams;
                float qualityScale;
                float performanceLevel;
            };
            #endif
            
            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                
                TRANSFER_SHADOW(o);
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                // 앱인토스 성능 모드에 따른 적응적 품질
                half qualityFactor = lerp(0.5, 1.0, _QualityLevel);
                
                // 기본 텍스처 샘플링
                fixed4 albedo = tex2D(_MainTex, i.uv) * _Color;
                
                #ifdef APPS_IN_TOSS_OPTIMIZED
                // 앱인토스 최적화: 거리 기반 품질 조정
                float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
                float distance = length(_WorldSpaceCameraPos - i.worldPos);
                
                // 거리에 따른 디테일 감소
                half distanceFactor = saturate(1.0 - distance / 100.0);
                qualityFactor *= distanceFactor;
                
                // 성능 모드가 활성화된 경우 간소화된 라이팅
                if (_PerformanceMode > 0.5)
                {
                    // 간단한 Lambert 라이팅
                    half NdotL = dot(i.worldNormal, _WorldSpaceLightPos0.xyz);
                    half lambert = saturate(NdotL);
                    
                    fixed3 lighting = lambert * _LightColor0.rgb;
                    albedo.rgb *= lighting;
                    
                    return albedo;
                }
                #endif
                
                // 고품질 PBR 라이팅 (품질에 따라 조정)
                half3 worldNormal = normalize(i.worldNormal);
                half3 worldView = normalize(_WorldSpaceCameraPos - i.worldPos);
                half3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                
                // 정반사
                half3 specular = pow(saturate(dot(reflect(-worldLight, worldNormal), worldView)), 
                                    lerp(8, 32, _Smoothness * qualityFactor)) * _Metallic;
                
                // 그림자
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                
                // 최종 색상 계산
                fixed3 lighting = saturate(dot(worldNormal, worldLight)) * _LightColor0.rgb * atten;
                lighting += specular * qualityFactor;
                
                albedo.rgb *= lighting;
                
                #ifdef WEBGL2_MRT
                // Multiple Render Targets 출력 (WebGL 2.0 전용)
                // 이 부분은 MRT 패스에서만 사용됨
                #endif
                
                return albedo;
            }
            ENDCG
        }
        
        // 그림자 패스
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #pragma multi_compile_shadowcaster
            #pragma multi_compile_instancing
            
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            
            struct v2f
            {
                V2F_SHADOW_CASTER;
                UNITY_VERTEX_OUTPUT_STEREO
            };
            
            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
    
    // WebGL 1.0 호환성을 위한 폴백
    FallBack "Mobile/Diffuse"
}

WebGL 2.0의 고급 기능을 활용하면 앱인토스 미니앱의 렌더링 성능과 품질을 크게 높일 수 있어요.
MRT, 인스턴싱, 고급 셰이더 기능을 통해 한층 더 완성도 높은 웹 게임 경험을 만들어보세요.