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