앱인토스 개발자센터 로고
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, 인스턴싱, 고급 셰이더 기능을 통해 한층 더 완성도 높은 웹 게임 경험을 만들어보세요.