[ Graph 만들기 ]
URL : http://catlikecoding.com/unity/tutorials/basics/building-a-graph/
Create a prefab.
Instantiate a line of cubes.
Show a mathematical function.
Create a custom shader.
Make the graph move.
게임 오브젝트를 활용한 그래픽적인 표현을 하고, 이러한 부분들은 수학적인 공식을 통해 구현됨
[ 1 Creating a Line of Cubes ]
그래프는 함수의 기능을 빠르게 파악하는데 도움을 줄 수 있음
x값의 변위에 따른 결과로 y값의 변위를 한눈으로 확인할 수 있음
새로운 프로젝트를 위해서 씬을 새로 만들기
Menu : File / New Scene
[ 1.1 Prefabs ] - 위의 그림에서 표현된 것처럼 그래프의 단위인 Cube를 만들고, Prefab화 하기
Menu : GameObject >> 3D Object / Cube
미리 만들어 놓은 게임 오브젝트
Prefab 만들기
Cube(GameObject) 드래그 >> (to) Project Window
[ 1.2 Graph Component ] - 결과물의 최상위 GameObject 만들기
Graph 생성
Menu : GameObject >> Empty Object
Name : Graph
Project View : Create >> C# Script
Name : Graph
Graph(Script) 드래그 >> (to) Graph
Scene 내의 Cube(GameObject) 삭제
[ 1.3 Instantiating Prefabs ] - prefab을 씬으로 가져오기 테스트(스크립트 상에서)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Graph : MonoBehaviour {
public Transform pointPrefab;
// Use this for initialization
void Awake() {
//Instantiate (pointPrefab);
Transform point = Instantiate (pointPrefab); // Transform으로 리턴 값을 받을 수 있다는 것 중요 (즉, 만들어진 객체를 다룰 수 있음)
point.localPosition = Vector3.right; // Vector(1,0,0)
point = Instantiate (pointPrefab);
point.localPosition = Vector3.right * 2;
}
}
Cube 연결
Cube(Prefab) 드래그 >> (to) Graph:Graph >> Point Prefab
Instantiate
ref : https://docs.unity3d.com/kr/current/ScriptReference/Object.Instantiate.html
GameObject(Prefab)를 복제
Instantiate는 게임실행 중에 빈번하게 호출되는 경우 프레임이 떨어질 수 있기 때문에 되도록 줄이는 것이 좋음
[ 1.4 Code Loops ]
x 좌표 10개에 상응하는 그래프 그리기
void Awake() {
int i = 0;
while (i < 10) {
Transform point = Instantiate (pointPrefab);
point.localPosition = Vector3.right * i;
i = i + 1;
}
}
[ 1.5 Concise Syntax ]
i++ / ++i , i = i+1 , while/for
[ 1.6 Changing the Domain ]
x 좌표값을 기준으로 0 ~ 10까지 표현되는 것을 -1 ~ 1까지 표현될 수 있도록 영역을 조정
point.localScale = Vector3.one / 5f;
point.localPosition = Vector3.right * ((i+0.5f)/5 - 1f);
나누기 전에 0.5를 더해줘서 불능 상태를 방지
[ 1.7 Hoisting the Vectors out of the Loop ]
void Awake() {
Vector3 scale = Vector3.one / 5f;
Vector3 position;
position.y = position.z = 0f;
for (int i = 0; i < 10 ; i++) {
Transform point = Instantiate (pointPrefab);
//point.localPosition = Vector3.right * ((i+0.5f) / 5 - 1f);
position.x = (i + 0.5f) / 5f - 1f;
point.localPosition = position;
point.localScale = scale;
}
}
Unity의 Vector3
Unity Engine에서 사용하는 특정 Vector3(Transform의 position, scale, rotation)는 한번 만들어지면 값을 변경하기 못하도록 만들었음
값을 변경하기 위해서는 따로 Vector3를 만들어서 대입해서 처리해야 함
[ 1.8 Using X to Define Y ] - 그래프 표현하기
position.y = position.x ( y = x )
position.y = position.x * position.x ( y = x*x)
[ 2 Creating More Cubes ]
[ 2.1 Variable Resolution ]
x좌표를 기준으로 -1 ~ 1까지 큐브의 개수를 설정할 수 있도록 함(밀집도)
[ Range( 10, 100) ] public int resolution = 50;
[ Range ] : 유니티에서 정의된 속성으로 범위를 Slider로 정할 수 있도록 해줌
[ 2.2 Variable Instantiation ]
void Awake() {
float step = 2f / resolution;
Vector3 scale = Vector3.one * step;
Vector3 position;
position.y = position.z = 0f;
for (int i = 0; i < resolution ; i++) {
Transform point = Instantiate (pointPrefab);
//point.localPosition = Vector3.right * ((i+0.5f) / 5 - 1f);
position.x = (i + 0.5f) * step - 1f;
position.y = position.x * position.x;
point.localPosition = position;
point.localScale = scale;
}
}
[ 2.3 Setting the Parent ]
point.SetParent(transform); 인자로 전달되는 transform의 객체를 부모로 하는 계층 구조를 가지게 됨
두번째인자가 true인 경우 parent의 좌표값을 계산하여 상대적인 위치 값으로 계산되어 world 좌표 값을 가지게 됨(씬상에서의 변화가 없음)
false인 경우에는 parent의 Transform을 기준으로 자신의 transform 값이 적용됨 (따라서, 부모의 위치, 스케일, 회전의 영향을 받은 상태에서 자신의 회전, 스케일, 위치가 변화됨)
void Awake() {
float step = 2f / resolution;
Vector3 scale = Vector3.one * step;
Vector3 position;
position.y = position.z = 0f;
for (int i = 0; i < resolution ; i++) {
Transform point = Instantiate (pointPrefab);
//point.localPosition = Vector3.right * ((i+0.5f) / 5 - 1f);
position.x = (i + 0.5f) * step - 1f;
position.y = position.x * position.x;
point.localPosition = position;
point.localScale = scale;
point.SetParent (transform,false);
}
}
[ 3 Coloring the Graph ]
그래프를 표현하는 큐브들에 다양한 색깔을 입히기
[ 3.1 Creating a Custom Shader ]
Shader Program : GPU를 활용해서 3D object를 랜더링(Lighting 계산 등)
Unity Shader 생성
Project Window : Create >> Shader >> Standard Surface Shader
Name : ColoredPoint
Open File (더블 클릭)
유니티에서 지칭한 Surface shader를 작성할 수 있도록 됨
Surface Shader
Shader "Custom/ColoredPoint" {
Properties {
// _Color ("Color", Color) = (1,1,1,1)
// _MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
// sampler2D _MainTex;
struct Input {
// float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
// fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
// fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
// o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
// o.Alpha = c.a;
o.Alpha = 1;
}
ENDCG
}
FallBack "Diffuse"
}
Albedo
매질이 띠고 있는 색상
Alpha
투명도의 정도 (0 이면 완전 투명, 1 이면 완전 불투명)
struct Input { float3 worldPos; };
Cube의 위치 정보를 기반으로 Cube의 색깔을 바꿀 수 있도록 하기 위함
worldPos : 유니티의 내장 셰이더에 있는 월드공간상의 좌표를 받아옴
Material 생성
Project Window : Create >> Material
Name : ColoredPoint
(ColoredPoint Material) Inspector : Shader >> Custom/ColoredPoint
ColoredPoint(Material) 드래그 >> (to) Cube(Prefab)
[ 3.2 Coloring Based on World Position ]
o.Albedo.r = IN.worldPos.x * 0.5 + 0.5;
Output Albedo의 Red 값을 Input을 들어오는 Cube의 x좌표값을 사용
x의 값이 -1 ~ 1 사이의 값으로 되어 있기 때문에, 0 ~ 1사이의 값으로 환산할 수 있도록 하고 있음
o.Albedo.rg = IN.worldPos.xy * 0.5 + 0.5;
x, y 두축의 값을 기반으로 처리될 수 있도록 하고 있음
position.y = position.x * position.x * position.x ;
y 좌표값이 -1 일 때는 o.Albedo.g가 0이 됨
[ 4 Animating the Graph ]
시간의 흐름에 따라 변행되는 그래프를 만들자
[ 4.1 Keeping Track of the Points ]
매 프레임마다 모든 큐브(그래프)를 삭제하고 다시 그릴 수도 있지만, 효율적인 측면에서 매 프레임마다 큐브들의 위치를 바꿔 주는 것이 좋음
Transform[] points;
모든 Cube의 위치값들을 저장하기 위해 변수 선언
public Transform pointPrefab;
[ Range( 10, 100) ] public int resolution = 50;
Transform[] points;
void Awake() {
float step = 2f / resolution;
Vector3 scale = Vector3.one * step;
Vector3 position;
position.y = position.z = 0f;
points = new Transform[resolution];
for (int i = 0; i < resolution ; i++) {
Transform point = Instantiate (pointPrefab);
//point.localPosition = Vector3.right * ((i+0.5f) / 5 - 1f);
position.x = (i + 0.5f) * step - 1f;
position.y = position.x * position.x * position.x;
point.localPosition = position;
point.localScale = scale;
point.SetParent (transform,false);
points [i] = point;
}
}
[ 4.2 Updating the Points ]
Animation Graph를 만들기 위해서는 Awake() 함수 내에서는 더이상 y 좌표값을 계산할 필요가 없음( x 값은 고정적이므로 그대로 두어야 함)
Update() 에서는 x 값에 따라 변하게될 y값을 정의할 수 있도록 해야 함
void Awake() {
float step = 2f / resolution;
Vector3 scale = Vector3.one * step;
Vector3 position;
position.y = position.z = 0f;
points = new Transform[resolution];
for (int i = 0; i < resolution ; i++) {
Transform point = Instantiate (pointPrefab);
//point.localPosition = Vector3.right * ((i+0.5f) / 5 - 1f);
position.x = (i + 0.5f) * step - 1f;
//position.y = position.x * position.x * position.x;
point.localPosition = position;
point.localScale = scale;
point.SetParent (transform,false);
points [i] = point;
}
}
void Update() {
for (int i = 0; i < points.Length; i++) {
Transform point = points [i];
Vector3 position = point.localPosition;
position.y = position.x * position.x * position.x;
point.localPosition = position;
}
}
[ 4.3 Showing a Sine Wave ]
참고 사이트 : 그래프 그려주는 사이트
Mathf
float 형으로되어 있는 자료형을 수학적(mathematiclal) 계산을 할 수 있도록 하는 함수들을 모아 놓은 클래스
Mathf.Sin
어떤 인자 값을 넣어도 -1 ~ 1 범위의 숫자값만 가지게 된다.
position.y = Mathf.Sin(Mathf.PI * position.x);
x(-1 ~ 1)에 PI를 곱하고 이를 사인함수에 적용하면 마찬가지로 y도 (-1 ~ 1) 사이의 값을 가지게 됨
position.y = Mathf.Sin(Mathf.PI * (position.x + Time.time));
위의 공식에 변위되는 시간 값을 적용하면, y값은 항상 변하게 됨