티스토리 뷰
기본코드 (기본 내장 쉐이더)
https://github.com/mmagdics/noneuclideanunity/blob/main/Assets/Noneuclid/README.txt
noneuclideanunity/Assets/Noneuclid/README.txt at main · mmagdics/noneuclideanunity
Contribute to mmagdics/noneuclideanunity development by creating an account on GitHub.
github.com
123으로 구성됨.
1. 쌤플씬 URP로 바꾸기.
URP에서 쌍곡점(noneuclidean)/
직선이던것들이 동그래진다고해야하나..
이 쉐이더 활용하면됨..
NonEuclideanGeometry_URP.shader
Shader "NonEuclid/NonEuclideanGeometry_URP"
{
Properties
{
LorentzSign("Curve", Float) = 0
globalScale("globalScale", Float) = 0.001
_Color("Color", Color) = (1,1,1,1)
_MainTex("Albedo", 2D) = "white" {}
_EmissionColor("EmissionColor", Color) = (0,0,0,1)
_EmissionMap("EmissionMap", 2D) = "white" {}
_Glossiness("Smoothness", Range(0,1)) = 0.1
_Metallic("Metallic", Range(0,1)) = 0.1
_MetallicGlossMap("Metallic", 2D) = "white" {}
_Cutoff("Alpha Cutoff", Range(0,1)) = 0.0
_IsAntipodal("Is Antipodal", Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass
{
Name "UniversalForward"
Tags { "LightMode"="UniversalForward" }
Cull Off
HLSLPROGRAM
#pragma vertex vertNonEuclid
#pragma fragment frag
// URP Core includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// === CommonIncludes.cginc 부분 ===
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 wPos : TEXCOORD1;
};
// === Material Properties ===
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float4 _Color;
float4 _EmissionColor;
float _Metallic;
float _Glossiness;
float _Cutoff;
float _IsAntipodal;
float LorentzSign;
float globalScale;
CBUFFER_END
// Texture declarations
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_EmissionMap);
SAMPLER(sampler_EmissionMap);
TEXTURE2D(_MetallicGlossMap);
SAMPLER(sampler_MetallicGlossMap);
// === NonEuclid.cginc 부분 (수학 함수들) ===
float dotProduct(float4 u, float4 v)
{
return u.x * v.x + u.y * v.y + u.z * v.z + LorentzSign * u.w * v.w;
}
float4 direction(float4 to, float4 from)
{
if (LorentzSign > 0) {
float cosd = dotProduct(from, to);
float sind = sqrt(1 - cosd * cosd);
return (to - from * cosd) / sind;
}
if (LorentzSign < 0) {
float coshd = -dotProduct(from, to);
float sinhd = sqrt(coshd * coshd - 1);
return (to - from * coshd) / sinhd;
}
return normalize(to - from);
}
float4 portEucToCurved(float4 eucPoint)
{
float3 P = eucPoint.xyz;
float distance = length(P);
if (distance < 0.0001f) return eucPoint;
if (LorentzSign > 0) return float4(P / distance * sin(distance), cos(distance));
if (LorentzSign < 0) return float4(P / distance * sinh(distance), cosh(distance));
return eucPoint;
}
float4 portEucToCurved(float3 eucPoint)
{
return portEucToCurved(float4(eucPoint, 1));
}
float4x4 TranslateMatrix(float4 to)
{
if (LorentzSign != 0) {
float denom = 1 + to.w;
return transpose(float4x4(
1 - LorentzSign * to.x * to.x / denom, -LorentzSign * to.x * to.y / denom, -LorentzSign * to.x * to.z / denom, -LorentzSign * to.x,
-LorentzSign * to.y * to.x / denom, 1 - LorentzSign * to.y * to.y / denom, -LorentzSign * to.y * to.z / denom, -LorentzSign * to.y,
-LorentzSign * to.z * to.x / denom, -LorentzSign * to.z * to.y / denom, 1 - LorentzSign * to.z * to.z / denom, -LorentzSign * to.z,
to.x, to.y, to.z, to.w
));
}
return transpose(float4x4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
to.x, to.y, to.z, 1
));
}
float4x4 ViewMat()
{
float4 ic = float4(UNITY_MATRIX_V[0].xyz, 0);
float4 jc = float4(UNITY_MATRIX_V[1].xyz, 0);
float4 kc = float4(UNITY_MATRIX_V[2].xyz, 0);
float4 geomEye = portEucToCurved(GetCameraPositionWS() * globalScale);
float4x4 eyeTranslate = TranslateMatrix(geomEye);
float4 icp, jcp, kcp;
icp = mul(eyeTranslate, ic);
jcp = mul(eyeTranslate, jc);
kcp = mul(eyeTranslate, kc);
if (abs(LorentzSign) < 0.001)
{
return UNITY_MATRIX_V;
}
return transpose(float4x4(
icp.x, jcp.x, kcp.x, LorentzSign * geomEye.x,
icp.y, jcp.y, kcp.y, LorentzSign * geomEye.y,
icp.z, jcp.z, kcp.z, LorentzSign * geomEye.z,
LorentzSign * icp.w, LorentzSign * jcp.w, LorentzSign * kcp.w, geomEye.w
));
}
float4x4 ProjMat()
{
float sFovX = UNITY_MATRIX_P._m00;
float sFovY = UNITY_MATRIX_P._m11;
float fp = max(0.005, _ProjectionParams.y * globalScale);
if (LorentzSign <= 0.00001)
{
return UNITY_MATRIX_P;
}
return transpose(float4x4(
sFovX, 0, 0, 0,
0, sFovY, 0, 0,
0, 0, 0, -1,
0, 0, -fp, 0
));
}
// === Vertex Shader ===
v2f vertNonEuclid(appdata v)
{
v2f o;
float4 eucVtxPos = v.vertex;
eucVtxPos = mul(GetObjectToWorldMatrix(), eucVtxPos);
float3 wPos = eucVtxPos.xyz;
eucVtxPos.xyz *= globalScale;
float4 geomPoint = portEucToCurved(eucVtxPos);
// 원본의 _IsAntipodal 처리
if (_IsAntipodal > 0.5 && LorentzSign > 0.001)
{
geomPoint = -geomPoint;
}
o.pos = mul(ViewMat(), geomPoint);
o.pos = mul(ProjMat(), o.pos);
o.wPos = wPos;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
// === Fragment Shader (Unlit) ===
half4 frag(v2f i) : SV_Target
{
// 텍스처 샘플링
half4 mainTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
half4 emissionTex = SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, i.uv);
// 알베도와 이미션만 사용 (조명 계산 없음)
half3 albedo = mainTex.rgb * _Color.rgb;
half3 emission = emissionTex.rgb * _EmissionColor.rgb;
half outputAlpha = mainTex.a * _Color.a;
// 최종 색상 = 알베도 + 이미션 (언릿)
half4 c = half4(albedo + emission, outputAlpha);
// Alpha cutoff
if (c.a < _Cutoff) discard;
return c;
}
ENDHLSL
}
}
FallBack "Hidden/Universal Render Pipeline/FallbackError"
}
카메라 설정 스크립트 *
이건필수 !
씬 전체를 비유클리드 기하학으로 렌더링하는 **"기하학 변환 엔진"**
// 세 가지 기하학 모드 public enum Geometry{ Euclidean = 0, // 평면 기하학 (일반) Elliptic = 1, // 구형 기하학 (LorentzSign = 1) Hyperbolic = -1 // 쌍곡 기하학 (LorentzSign = -1)
메터리얼을 세개 만들어서 조절해야함 !
// [최종 수정 완료] URP용 GeometryControl.cs
using UnityEngine;
using UnityEngine.Rendering; // RenderPipelineManager 사용을 위해 필수
using System.Collections.Generic;
[RequireComponent(typeof(Camera))]
public class GeometryControl : MonoBehaviour
{
// --- 변수 선언부 ---
public enum Geometry { Euclidean = 0, Elliptic = 1, Hyperbolic = -1 };
public Geometry geometry;
[Range(0.001f, 1.0f)]
public float globalScale = 0.1f;
// 인스펙터에서 URP용 셰이더를 직접 연결해야 합니다.
public Shader shader;
private Camera cam;
private Material nonEuclidMaterial;
private List<MeshRenderer> sceneMeshRenderers = new List<MeshRenderer>();
// --- 프로퍼티 ---
private float LorentzSign { get { return (int)geometry; } }
// --- 유니티 생명주기 함수 ---
void Start()
{
cam = GetComponent<Camera>();
if (shader == null)
{
Debug.LogError("오류: GeometryControl 컴포넌트의 Shader 슬롯에 URP용 셰이더를 직접 드래그해서 연결해주세요!", this);
this.enabled = false; // 스크립트 자체를 비활성화
return;
}
nonEuclidMaterial = new Material(shader);
//sceneMeshRenderers.AddRange(FindObjectsByType<MeshRenderer>(FindObjectSortMode.None));
//sceneMeshRenderers.AddRange(FindObjectsOfType<MeshRenderer>());
sceneMeshRenderers.AddRange(FindObjectsByType<MeshRenderer>(FindObjectsSortMode.None));
}
void OnEnable()
{
// URP의 렌더링 이벤트에 Render 함수를 구독합니다.
RenderPipelineManager.endCameraRendering += Render;
}
void OnDisable()
{
// 스크립트 비활성화 시 반드시 구독을 해제해야 메모리 누수를 막습니다.
RenderPipelineManager.endCameraRendering -= Render;
}
void OnPreCull()
{
if (globalScale != 1)
{
cam.cullingMatrix = Matrix4x4.Ortho(-99999, 99999, -99999, 99999, 0.001f, 99999) *
Matrix4x4.Translate(Vector3.forward * -99999 / 2f) *
cam.worldToCameraMatrix;
}
if (globalScale < 1)
{
cam.nearClipPlane = 0.01f;
}
}
// URP의 렌더링 이벤트 핸들러
void Render(ScriptableRenderContext context, Camera camera)
{
// 이 스크립트가 붙어있는 카메라에 대해서만 렌더링을 실행합니다.
if (camera != this.cam || nonEuclidMaterial == null) return;
// 머티리얼에 셰이더 변수 설정
nonEuclidMaterial.SetFloat("LorentzSign", LorentzSign);
nonEuclidMaterial.SetFloat("globalScale", globalScale);
// 첫 번째 패스: 일반 렌더링
DrawAllMeshes(0.0f);
// 두 번째 패스: 척력점 렌더링 (타원 기하학일 때만)
if (geometry == Geometry.Elliptic)
{
DrawAllMeshes(1.0f);
}
}
// 헬퍼(도우미) 함수
void DrawAllMeshes(float isAntipodal)
{
nonEuclidMaterial.SetFloat("_IsAntipodal", isAntipodal);
nonEuclidMaterial.SetPass(0);
foreach (var rend in sceneMeshRenderers)
{
if (rend != null && rend.enabled && rend.isVisible)
{
MeshFilter mf = rend.GetComponent<MeshFilter>();
if (mf != null && mf.sharedMesh != null)
{
Graphics.DrawMeshNow(mf.sharedMesh, rend.transform.localToWorldMatrix);
}
}
}
}
} // <--- 이 마지막 중괄호가 클래스의 끝입니다. 모든 코드가 이 안에 있어야 합니다.
이건옵션 : 카메라 컨드롤
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class NonEuclidCameraController : MonoBehaviour
{
[Header("이동 설정")]
[Range(1f, 20f)]
public float moveSpeed = 5f;
[Range(1f, 10f)]
public float fastMoveSpeed = 10f;
[Header("마우스 Look 설정")]
[Range(50f, 500f)]
public float mouseSensitivity = 200f;
public bool invertY = false;
[Header("고급 설정")]
public bool lockCursor = true;
public KeyCode fastMoveKey = KeyCode.LeftShift;
public KeyCode unlockCursorKey = KeyCode.Escape;
[Header("디버그")]
public bool showDebugInfo = true;
private float xRotation = 0f;
private Vector3 lastPosition;
private Camera cam;
private GeometryControl geometryControl;
void Start()
{
cam = GetComponent<Camera>();
geometryControl = GetComponent<GeometryControl>();
if (lockCursor)
{
Cursor.lockState = CursorLockMode.Locked;
}
lastPosition = transform.position;
Debug.Log("🎮 NonEuclid 카메라 컨트롤러 활성화!");
Debug.Log("WASD: 이동, 마우스: 시점, Shift: 빠른 이동, ESC: 커서 해제");
Debug.Log("하이퍼볼릭 모드에서 이동하면서 공간 왜곡을 체험해보세요!");
}
void Update()
{
HandleMouseLook();
HandleMovement();
HandleCursorControl();
if (showDebugInfo && Input.GetKeyDown(KeyCode.F1))
{
ShowDebugInfo();
}
}
void HandleMouseLook()
{
if (Cursor.lockState != CursorLockMode.Locked) return;
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
if (invertY)
mouseY = -mouseY;
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -90f, 90f);
transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
transform.parent.Rotate(Vector3.up * mouseX);
}
void HandleMovement()
{
float currentSpeed = Input.GetKey(fastMoveKey) ? fastMoveSpeed : moveSpeed;
float horizontal = Input.GetAxis("Horizontal"); // A, D
float vertical = Input.GetAxis("Vertical"); // W, S
float upDown = 0f;
// Q, E로 위아래 이동
if (Input.GetKey(KeyCode.Q)) upDown = -1f;
if (Input.GetKey(KeyCode.E)) upDown = 1f;
Vector3 direction = transform.right * horizontal + transform.forward * vertical + Vector3.up * upDown;
if (direction.magnitude > 1f)
direction.Normalize();
transform.position += direction * currentSpeed * Time.deltaTime;
// 이동 거리 계산 (디버그용)
if (showDebugInfo && Vector3.Distance(transform.position, lastPosition) > 0.1f)
{
lastPosition = transform.position;
}
}
void HandleCursorControl()
{
if (Input.GetKeyDown(unlockCursorKey))
{
if (Cursor.lockState == CursorLockMode.Locked)
{
Cursor.lockState = CursorLockMode.None;
Debug.Log("커서 해제됨. 클릭하면 다시 잠김.");
}
}
if (Cursor.lockState == CursorLockMode.None && Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked;
}
}
void ShowDebugInfo()
{
Debug.Log("=== 카메라 컨트롤러 정보 ===");
Debug.Log($"위치: {transform.position}");
Debug.Log($"회전: {transform.eulerAngles}");
Debug.Log($"이동 속도: {moveSpeed} (빠른 속도: {fastMoveSpeed})");
Debug.Log($"마우스 감도: {mouseSensitivity}");
if (geometryControl != null)
{
Debug.Log($"현재 기하학: {geometryControl.geometry}");
Debug.Log($"Global Scale: {geometryControl.globalScale}");
}
}
void OnGUI()
{
if (!showDebugInfo) return;
// 화면 우측 상단에 조작법 표시
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.white;
style.fontSize = 14;
string controls =
"🎮 카메라 조작법\n" +
"WASD: 이동\n" +
"Q/E: 위/아래\n" +
"마우스: 시점 회전\n" +
"Shift: 빠른 이동\n" +
"ESC: 커서 해제\n" +
"F1: 디버그 정보\n" +
"\n🔥 NonEuclid 팁:\n" +
"3키 → 하이퍼볼릭\n" +
"PageDown → 곡률 증가";
GUI.Box(new Rect(Screen.width - 250, 10, 240, 200), "");
GUI.Label(new Rect(Screen.width - 240, 20, 220, 180), controls, style);
// 현재 상태 표시
if (geometryControl != null)
{
string status = $"기하학: {geometryControl.geometry}\nScale: {geometryControl.globalScale:F3}";
GUI.Label(new Rect(Screen.width - 240, 220, 220, 40), status, style);
}
}
}
메터리얼
이게 뭐냐면..
*NonEuclid 셰이더: 세상의 모든 사물을 그리는 데 사용된 '특수 잉크'
Material -1 0 1
*GeometryControl 스크립트: 당신의 눈(카메라)에 씌워진 '마법 안경'. 이 안경에는 렌즈를 갈아 끼울 수 있다
Euclidean 렌즈: 세상을 평범하게 보여줍니다. (Curve = 0)
Elliptic 렌즈: 세상을 볼록하게(어안렌즈처럼) 보여줍니다. (Curve = 1)
Hyperbolic 렌즈: 세상을 오목하게(블랙홀처럼) 보여줍니다. (Curve = -1)
2. 이건 좀 실패인데, 스피어 그리드 3*3 로 비 유클리드 공간만들기 (스피어 + 그리드 코드임)
약간 응용: 여러 개의 독립적인 3D 오브젝트(영상 스크린)가 놓인 공간 자체를 왜곡하여 관찰하는 경험
: 맵을 잘 설계하지 않는이상,, 뭔가 카메라에 짤린다그래야하나 그래서 잘 안보임. (누가 해결방법좀 알려줘...)
using UnityEngine;
using UnityEngine.Video;
public class VideoGrid3x3Setup : MonoBehaviour
{
// 이제 머티리얼 템플릿은 필요 없습니다.
[Header("영상 설정")]
public VideoClip[] videoClips = new VideoClip[9];
[Header("그리드 & 스피어 설정")]
public float objectSize = 10f;
public float spacing = 12f;
public bool setupOnStart = true;
private GeometryControl geometryControl;
void Start()
{
geometryControl = Camera.main.GetComponent<GeometryControl>();
if (geometryControl == null)
{
Debug.LogError("오류: Main Camera에 GeometryControl 스크립트가 없습니다!");
this.enabled = false;
return;
}
if (setupOnStart)
{
RebuildObjects();
}
}
public void RebuildObjects()
{
// 재건축 전에, GeometryControl에게 "이전 오브젝트 정보 다 지워줘" 라고 요청
geometryControl.ClearObjects();
CreateObjects();
}
void CreateObjects()
{
GameObject gridParent = GetOrCreateParent("VideoGrid");
GameObject sphereParent = GetOrCreateParent("VideoSpheres");
for (int x = 0; x < 3; x++)
{
for (int z = 0; z < 3; z++)
{
int index = x + z * 3;
VideoClip clip = (index < videoClips.Length) ? videoClips[index] : null;
if (clip == null) continue; // 클립 없으면 생성 안함
Vector3 position = new Vector3((x - 1) * spacing, 0, (z - 1) * spacing);
CreateSingleObject(PrimitiveType.Plane, clip, position, gridParent.transform);
Vector3 spherePosition = position + Vector3.up * (objectSize * 0.75f);
CreateSingleObject(PrimitiveType.Sphere, clip, spherePosition, sphereParent.transform);
}
}
}
void CreateSingleObject(PrimitiveType type, VideoClip clip, Vector3 position, Transform parent)
{
GameObject obj = GameObject.CreatePrimitive(type);
obj.name = $"{type.ToString()}_{clip.name}";
obj.transform.parent = parent;
obj.transform.position = position;
float scaleMultiplier = (type == PrimitiveType.Plane) ? 0.1f : 1.0f;
obj.transform.localScale = Vector3.one * (objectSize * scaleMultiplier);
MeshRenderer objRenderer = obj.GetComponent<MeshRenderer>();
// 그림자 및 라이팅 설정
objRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
objRenderer.receiveShadows = false;
// ★★★ 여기가 마지막 최종 수정입니다 ★★★
// Unity가 자동으로 넣어준 기본 머티리얼을 완전히 제거합니다.
// 빈 배열을 할당하여 'List is Empty' 상태로 만듭니다.
objRenderer.materials = new Material[0];
// 모든 정보를 GeometryControl에게 넘깁니다.
geometryControl.RegisterObject(objRenderer, clip);
}
GameObject GetOrCreateParent(string name)
{
GameObject parentObj = GameObject.Find(name);
if (parentObj == null)
{
parentObj = new GameObject(name);
}
else
{
foreach (Transform child in parentObj.transform)
{
Destroy(child.gameObject);
}
}
return parentObj;
}
}
using UnityEngine;
public class SwitchGeometry : MonoBehaviour
{
GeometryControl geomControl;
[Header("디버그 정보")]
public bool showDebugInfo = true;
void Start()
{
if (geomControl == null)
{
geomControl = FindAnyObjectByType<GeometryControl>();
}
if (geomControl == null)
{
Debug.LogError("GeometryControl을 찾을 수 없습니다! Main Camera에 GeometryControl 컴포넌트를 추가해주세요.");
enabled = false;
return;
}
if (showDebugInfo)
{
Debug.Log($"SwitchGeometry 초기화 완료. 현재 기하학: {geomControl.geometry}");
Debug.Log("키 조작: 1=Euclidean, 2=Elliptic, 3=Hyperbolic, PageUp/Down=Scale");
}
}
void Update()
{
if (geomControl == null) return;
GeometryControl.Geometry previousGeometry = geomControl.geometry;
float previousScale = geomControl.globalScale;
// 기하학 전환
if (Input.GetKeyDown(KeyCode.Alpha1))
{
geomControl.geometry = GeometryControl.Geometry.Euclidean;
if (showDebugInfo) Debug.Log("🟢 Euclidean 기하학으로 전환");
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
geomControl.geometry = GeometryControl.Geometry.Elliptic;
if (showDebugInfo) Debug.Log("🔵 Elliptic 기하학으로 전환");
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
geomControl.geometry = GeometryControl.Geometry.Hyperbolic;
if (showDebugInfo) Debug.Log("🔴 Hyperbolic 기하학으로 전환");
}
// 스케일 조정
if (Input.GetKey(KeyCode.PageUp))
{
geomControl.globalScale *= 1.03f;
geomControl.globalScale = Mathf.Clamp(geomControl.globalScale, 0.001f, 10f);
}
if (Input.GetKey(KeyCode.PageDown))
{
geomControl.globalScale /= 1.03f;
geomControl.globalScale = Mathf.Clamp(geomControl.globalScale, 0.001f, 10f);
}
// 디버그 정보 출력 (값이 변경될 때만)
if (showDebugInfo)
{
if (previousGeometry != geomControl.geometry)
{
Debug.Log($"기하학 변경: {previousGeometry} → {geomControl.geometry}");
Debug.Log($"LorentzSign 값: {(int)geomControl.geometry}");
}
if (Mathf.Abs(previousScale - geomControl.globalScale) > 0.001f)
{
Debug.Log($"Global Scale: {geomControl.globalScale:F3}");
}
}
// 강제 디버그 (스페이스바)
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("=== NonEuclid 상태 정보 ===");
Debug.Log($"현재 기하학: {geomControl.geometry} (LorentzSign: {(int)geomControl.geometry})");
Debug.Log($"Global Scale: {geomControl.globalScale}");
Debug.Log($"Shader 연결됨: {geomControl.shader != null}");
Debug.Log($"GeometryControl 활성화: {geomControl.enabled}");
// 씬의 MeshRenderer 개수 확인
MeshRenderer[] renderers = FindObjectsByType<MeshRenderer>(FindObjectsSortMode.None);
Debug.Log($"씬의 MeshRenderer 개수: {renderers.Length}");
if (renderers.Length == 0)
{
Debug.LogWarning("⚠️ 씬에 MeshRenderer가 없습니다! Cube, Sphere 등을 추가해주세요.");
}
}
}
void OnGUI()
{
if (!showDebugInfo) return;
GUI.Box(new Rect(10, 10, 300, 120), "");
GUI.Label(new Rect(20, 20, 280, 20), $"기하학: {geomControl?.geometry}");
GUI.Label(new Rect(20, 40, 280, 20), $"Scale: {geomControl?.globalScale:F3}");
GUI.Label(new Rect(20, 60, 280, 20), "1=Euclidean, 2=Elliptic, 3=Hyperbolic");
GUI.Label(new Rect(20, 80, 280, 20), "PageUp/Down=Scale, Space=Debug");
GUI.Label(new Rect(20, 100, 280, 20), $"Shader: {(geomControl?.shader != null ? "OK" : "없음")}");
}
}
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Video;
using System.Collections.Generic;
[RequireComponent(typeof(Camera))]
public class GeometryControl : MonoBehaviour
{
private class RenderObject
{
public MeshRenderer renderer;
public VideoPlayer videoPlayer;
public RenderTexture renderTexture;
}
public enum Geometry { Euclidean = 0, Elliptic = 1, Hyperbolic = -1 };
public Geometry geometry;
[Range(0.001f, 1.0f)]
public float globalScale = 0.1f;
public Shader shader;
private Camera cam;
private Material nonEuclidMaterial;
private List<RenderObject> renderObjects = new List<RenderObject>();
private float LorentzSign { get { return (int)geometry; } }
// ★★★ 바로 이 함수입니다. 이제 이름이 'RegisterObject' 하나로 통일되었습니다.
public void RegisterObject(MeshRenderer renderer, VideoClip clip)
{
if (renderer == null || clip == null) return;
var renderObject = new RenderObject();
renderObject.renderer = renderer;
renderObject.videoPlayer = renderer.gameObject.AddComponent<VideoPlayer>();
renderObject.videoPlayer.playOnAwake = false;
renderObject.videoPlayer.isLooping = true;
renderObject.videoPlayer.renderMode = VideoRenderMode.RenderTexture;
renderObject.videoPlayer.clip = clip;
renderObject.renderTexture = new RenderTexture((int)clip.width, (int)clip.height, 24);
renderObject.renderTexture.Create();
renderObject.videoPlayer.targetTexture = renderObject.renderTexture;
renderObject.videoPlayer.Prepare();
renderObjects.Add(renderObject);
}
public void ClearObjects()
{
foreach (var obj in renderObjects)
{
if (obj != null)
{
if (obj.videoPlayer != null) Destroy(obj.videoPlayer);
if (obj.renderTexture != null) obj.renderTexture.Release();
}
}
renderObjects.Clear();
}
void Start()
{
cam = GetComponent<Camera>();
if (shader == null) { this.enabled = false; return; }
nonEuclidMaterial = new Material(shader);
}
void OnEnable() { RenderPipelineManager.endCameraRendering += Render; }
void OnDisable() { RenderPipelineManager.endCameraRendering -= Render; }
void Render(ScriptableRenderContext context, Camera camera)
{
if (camera != this.cam || nonEuclidMaterial == null) return;
nonEuclidMaterial.SetFloat("LorentzSign", LorentzSign);
nonEuclidMaterial.SetFloat("globalScale", globalScale);
DrawAllMeshes(0.0f);
if (geometry == Geometry.Elliptic) DrawAllMeshes(1.0f);
}
void DrawAllMeshes(float isAntipodal)
{
nonEuclidMaterial.SetFloat("_IsAntipodal", isAntipodal);
foreach (var obj in renderObjects)
{
if (obj == null || obj.renderer == null || !obj.renderer.enabled) continue;
if(obj.videoPlayer.isPrepared)
{
nonEuclidMaterial.SetTexture("_MainTex", obj.renderTexture);
} else {
nonEuclidMaterial.SetTexture("_MainTex", Texture2D.whiteTexture);
}
nonEuclidMaterial.SetPass(0);
MeshFilter mf = obj.renderer.GetComponent<MeshFilter>();
if (mf != null && mf.sharedMesh != null)
{
Graphics.DrawMeshNow(mf.sharedMesh, obj.renderer.transform.localToWorldMatrix);
}
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
foreach (var obj in renderObjects)
{
if (obj.videoPlayer != null && obj.videoPlayer.isPrepared)
{
if (obj.videoPlayer.isPlaying) obj.videoPlayer.Pause();
else obj.videoPlayer.Play();
}
}
}
}
void OnPreCull()
{
if (globalScale != 1)
{
cam.cullingMatrix = Matrix4x4.Ortho(-99999, 99999, -99999, 99999, 0.001f, 99999) *
Matrix4x4.Translate(Vector3.forward * -99999 / 2f) *
cam.worldToCameraMatrix;
}
if (globalScale < 1)
{
cam.nearClipPlane = 0.01f;
}
}
}
3. 하나의 텍스쳐 >> 비유클리드 영화관
다른걸 해보자: 하나의 거대한 배경 영상을 탐험하며, 플레이어의 이동에 따라 영상이 전환되는 경험
영상 전환 관찰 특정 거리만큼 이동하면 갑자기 배경의 영상이 바뀌는 것. 이때 전환 효과(공간 접힘이나 양자 도약)가 발생하면서 새로운 공간으로 이동했다는 것을 시각적으로 확인할 수 있게해보기
>>>
첫째, PixelPlayer 태그 설정은 스크립트가 플레이어 오브젝트를 자동으로 찾을 수 있게 해주는 식별자 역할을 합니다. 이는 마치 도서관에서 책에 고유 번호를 매기는 것과 같은 개념입니다. 스크립트는 이 태그를 통해 수많은 오브젝트 중에서 정확히 플레이어를 찾아내어 그 위치를 추적하고 비유클리드 공간에서의 이동을 계산할 수 있게 됩니다.
둘째, VideoPlayer의 렌더 모드 설정은 영상이 화면에 어떻게 표시될지를 결정하는 중요한 부분입니다. Camera-Far Plane 모드를 선택한 이유는 영상이 카메라의 뒤쪽에 배경으로 렌더되어, 마치 우리가 거대한 영상 속을 탐험하는 듯한 느낌을 만들어내기 때문입니다. 이는 카프카적 공간감을 표현하는 데 매우 적합한 설정입니다.
셋째, HyperbolicVideoNavigator 스크립트 연결은 전체 시스템의 뇌와 같은 역할을 하는 중앙 제어 장치를 활성화시키는 과정이었습니다. 이 스크립트가 VideoPlayer를 제어할 수 있게 됨으로써, 플레이어의 움직임에 따라 영상들이 동적으로 변화하는 마법 같은 효과가 가능해집니다.
- Total
- Today
- Yesterday
- krea
- 4d guassian splatting
- Express
- 유니티
- DeepLeaning
- three.js
- MCP
- AI
- ai film
- node.js
- Python
- MQTT
- houdini
- colab
- Arduino
- VR
- VFXgraph
- 라즈베리파이
- sequelize
- Unity
- opticalflow
- opencv
- Java
- TouchDesigner
- Midjourney
- RNN
- 4dgs
- CNC
- 후디니
- docker
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |