다이내믹 오브젝트(, 이를테면 모빌리티가 무버블로 설정된 스태틱 메시 컴포넌트 및 스켈레탈 메시 컴포넌트)는, 디스턴스 필드 섀도맵에서 월드의 스태틱 섀도잉에 통합시켜야 합니다. 이는Per Object섀도로 가능합니다. 각 이동 오브젝트는 하나의 스테이셔너리 라이트에서 다이내믹 섀도를 두 개 만듭니다: 오브젝트에 드리워지는 정적인 월드에 대한 그림자와, 월드에 드리워지는 오브젝트에 대한 그림자입니다. 이런 구성에서 스테이셔너리 라이트에 드는 섀도잉 비용은, 영향을 끼치는 다이내믹 오브젝트에서만 발생합니다. 즉 다이내믹 오브젝트의 수에 따라 비용이 매우 조금 들 수도 많이 들 수도 있습니다. 다이내믹 오브젝트의 수가 일정 이상이라면, 무버블 라이트의 효율이 더 좋습니다.
섀도 맵 캐시
Movable Lights(무버블 라이트)는 완벽히 동적인 빛과 그림자를 드리우며, 위치나 방향, 색, 밝기, 감쇠, 반경 등 그 모든 프로퍼티를 변경할 수 있습니다. 여기서 나오는 라이트는 라이트 맵에 구워넣지 않으며, 현재 간접광을 받을 수 없습니다.
섀도잉
그림자를 드리우도록 설정된 무버블 라이트는 전체 씬 다이내믹 섀도를 사용하기에 퍼포먼스 비용이 엄청납니다. 퍼포먼스에 가장 큰 영향을 끼치는 부분은 라이트에 영향받는 메시의 갯수와, 해당 메시의 트라이앵글 수 입니다. 즉 커다란 반경에 그림자를 드리우는 무버블 라이트는 반경이 작은 무버블 라이트보다 비용이 몇 배가 될 수 있다는 뜻입니다.
사용법
어느 라이트든지Transform(트랜스폼) 카테고리 아래 보면Mobility(모빌리티) 이라는 풀다운 프로퍼티가 있습니다. 이 옵션을Movable(무버블)로 바꿉니다. 이 프로퍼티는 블루프린트에 추가된 라이트 컴포넌트에도 나타납니다.
섀도 맵 캐시
포인트 / 스포트 라이트가 움직이지 않을 때, 그 라이트에 대한 섀도 맵을 저장한 뒤 다음 프레임에 재사용할 수 있습니다. 그러면 배경이 잘 움직이지 않는 게임에서 무버블 포인트 / 스포트 라이트의 그림자 비용을 크게 줄일 수 있습니다. 여기서는 언리얼 엔진 4 (UE4) 프로젝트에서 그 기능을 사용하는 방법을 알아보겠습니다.
섀도 맵 캐시 & 퍼포먼스
Shadow Map Caching (섀도 맵 캐시) 기능은 어느 UE4 프로젝트에서든 자동 활성화 가능합니다. 섀도 맵 캐시를 사용했을 때의 퍼포먼스를 확인해 보려면, 다음과 같은 방법으로 섀도 맵 캐시 기능을 껐다켰다 하면 됩니다:
다음 섀도 맵 캐시 데모에서는 Sun Temple 프로젝트가 사용되었습니다. 에픽 게임스 런처의 학습 탭에서 이 맵을 받으실 수 있습니다.
프로젝트의 레벨에서 다이내믹 섀도를 드리우도록 하고픈 라이트를 전부 선택합니다.
라이트의모빌리티를무버블로 설정하고그림자 드리우기옵션이 켜졌는지 확인합니다.
물결표 (`)키를 눌러콘솔창을 열고Stat Shadowrendering이라 입력하면 현재 다이내믹 섀도 비용을 볼 수 있습니다.
다시 물결표 (\) 키를 눌러 <strong>콘솔</strong> 창을 열고r.Shadow.CacheWholeSceneShadows 0` 이라 입력하여 다이내믹 섀도 캐시 기능을 끕니다.
CallCount와InclusiveAug부분의 숫자를 유심히 봐주세요.
이제 물결표 키를 한 번 더 눌러콘솔창을 열고r.Shadow.CacheWholeSceneShadows 1이라 입력하여 섀도 캐시 기능을 다시 켭니다.CallCount와InclusiveAug숫자를 비교해 보면 이 기능이 다이내믹 섀도 퍼포먼스에 끼치는 영향을 확인할 수 있습니다.
Shadow Caching On
Shadow Caching Off
퍼포먼스
섀도 맵 캐시 기능은 퍼포먼스에 엄청난 영향을 끼칩니다. NVIDIA 970 GTX GPU 에서 1920x1200 해상도로 이 최적화를 테스트해 본 결과는 다음과 같습니다.
이 기능을 켜기 전, 그림자를 드리우는 포인트 라이트가 셋 있는 환경에서 캐시가 없는 경우 섀도 뎁스를 렌더링하는 데 걸린 시간은 14.89ms 입니다.
Cached Shadow Maps (섀도 맵 캐시) 기능을 켠 상태에서, 똑같은 렌더링 작업에 걸린 시간은 .9ms, 약16 배 빠른것입니다!
참고로 포인트 라이트 33 개의 기여 부분을 렌더링하는 데는 여전히 2ms 가 걸리는데, 이 부분은 다른 식으로 최적화시킬 수는 있지만 이러한 변화에는 영향받지 않습니다.
섀도 맵 캐시에 사용되는 최대 메모리 양은r.Shadow.WholeSceneShadowCacheMb로 조절할 수 있습니다.
한계
섀도 맵 캐시 기능이 UE4 프로젝트의 다이내믹 섀도 비용을 낮춰주기는 하지만, 지원되지 않는 기능과 함께 썼을 때 렌더링 부작용을 일으키게 되는 한계점이 몇 가지 있습니다. 여기서는 섀도 맵 캐시의 한계는 무엇이고 그와 관련해서 무엇을 할 수 있는지 알아보겠습니다.
기본적으로 오브젝트가 다음 요건을 충족했을 때만 캐시가 가능합니다:
프리미티브의모빌리티가스태틱또는스테이셔너리여야 합니다.
레벨에 사용되는 머티리얼이World Position Offset을 사용하지 않아야 합니다.
라이트는포인트또는스포트라이트여야 하고, 그모빌리티는무버블로 설정되어 있으며,그림자 드리우기옵션이 켜져 있어야 합니다.
라이트가 한 곳에 있어야 합니다.
애니메이팅Tessellation또는Pixel Depth Offset을 사용하는 머티리얼은 그림자의 뎁스를 캐시에 저장할 때 부작용이 생길 수 있습니다.
Hello, I try to make resolution settings, but when change resolution with console command with custom resolution ( r.setRes 600 x 400 ) it change the window size and if is full screen it doesn't make anything. How to make resolutions settings with this or other method?
The r.SetRes command is the right way to go, but you are missing one little feature to make it work with full screen: Usage of r.SetResr.SetRes 600x400, where n can either be "f" for fullscreen or "w" for windowed, so if you want to apply 600x400 at fullscreen, you dor.SetRes 600x400f
//액터를 붙일때의 코드 형태 (AABWeapon 클래스 안에서 웨폰을 생성하여 갖고 있는 상태) FName weaponSocket(TEXT("hand_rSocket")); auto currentWeapon = GetWorld()->SpawnActor<AABWeapon>(FVector::ZeroVector, FRotator::ZeroRotator);
if (currentWeapon != nullptr) { currentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, weaponSocket); }
newWeapon->SetOwner(this); //네트웍 리플리케이션을 위한 setOwner(this);
_currentWeapon = newWeapon;
}
}
* Set the owner of this Actor, used primarily for network replication. * @param NewOwner The Actor whom takes over ownership of this Actor */ UFUNCTION(BlueprintCallable, Category=Actor) virtual void SetOwner( AActor* NewOwner );
SetVisibility(bool) : 인게임과 에디터에서 bool 에 따라 보이거나 안보이거나 한다
SetHiddenInGame(bool) :true일때에디터에서보이지만 인게임에선 안보인다
SetActorEnableCollision : 액터 전체의 콜리전 을 껐다 켰다 한다
SetCollisionProfileName("") : 콜리전 preset 을 변경 할 수 있다
public: //sizeof(Test((From*)nullptr) 해당 함수에 대한 리턴 타입 크기를 얻는다 즉 2 또는 1이 리턴 됨 //함수 실행 없이 리턴 타입 크기를 얻어올 수 있다
enum { Value = sizeof(Test((From*)nullptr)) -1 };
};
먼저 이건 From 타입에서 To 라는 타입으로 변환 가능하면 Value 값이 1 이 되며 만약 불가능한
포인터 간의 변환이라면 컴파일 타임에 enum 의 Value 값이 0 이 된다
static uint8 Test(...); 은 static uint16 Test(To*); 이 아닌 인자를 받기 위한 함수의 가변 형이다
즉 To 타입이 아닌 타입은 리턴 타입 크기를 1로 처리하겠다는 의미
ex).. int printz(...);// valid, but the arguments cannot be accessed portably 보통 가변인자는 앞에 가변인자 개수를 알려주는 정수형이 오지만 위의 경우에는 단지 리턴 타입 사이즈를 얻기위함과 To 가 아닌 타입을 받기위한 처리로 가변인자로 처리한것
실행 없이 함수의 리턴 타입 크기를 얻어온다( 그래서 int fun(char* a); 까지만 있어도 됨
It returns the size of the return type from that function (4on my implementation since that's what aninttakes up for me), which you would discover had you run it as is, then changed the return type tochar(at which point it would give you1).
The relevant part of the C99 standard is6.5.3.4.The sizeof operator:
The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated;otherwise, the operand is not evaluatedand the result is an integer constant.
Keep in mind that bold bit, it means that the function itself isnotcalled (hence theprintfwithin it is not executed). In other words, the output is simply the size of yourinttype (followed by a newline, of course).
그다음 볼 구문은 TEnableIf
template <bool Predicate, typename Result = void> class TEnableIf;
기본 적으로 봐야 하는 부분은 Predicate 부분인데 Predicate 로 넘어온 값이 1 즉 참이면 Type 이 정의 되고 그렇지 않다면 즉 0이 넘어온다면 Type은 정의 되지 않는다 Result=void 라는 것을 잊지 않고 이어서 나가보자
이 구문은 템플릿의 부분 특수화가 적용 되어 있는 구문이다
원래 아래와 같은 이러한 템플릿이 존재 했는데
template <bool Predicate, typename Result = void> class TEnableIf
{
}
여기서 Predicate 부분에 대해서만 부분적으로 특수화를 진행한다
부분 특수화를 진행하려면 나머지 인자를 그대로 써주면 된다 그리하여
bool 타입은 true, 또는 false 임으로 이에 대해 부분 특수화를 진행하면 다음처럼 된다
template <typename Result> class TEnableIf<true, Result> { public:
typedef Result Type;
//이때는 Type 을 Result 로 정의 하겠다는 의미인데 별다른 타입 정의가 없다면 Result 는 위의 기본 디폴 트 값으로 인하여 void 타입이 된다
};
template <typename Result> class TEnableIf<false, Result> {
//즉 이때는 Type 을 정의하지 않겠다, 존재 Type 존재자체가 없어서 Type 을 쓰면
컴파일 에러를 발생시키겠다는 의미이다
};
이때
template <bool Predicate, typename Result = void> class TEnableIf
{
}
이 구문을 간략하게 {} 를 제거 하고
template <bool Predicate, typename Result = void> class TEnableIf;
TEnableIf 과 TPointerIsConvertibleFromTo 의 조합임을 알 수 있다
즉 TPointerIsConvertibleFromTo 의 결과 값인 Value 값이 1 이면 => 변환 가능한 포인터라면
TEnableIf 에서 Type 을 typede 하게 되고 Type 은 void 처리 되어 class=void 가 되어
컴파일 에러가 발생하지 않지만 그렇지 않은 경우에는 컴파일 오류를 발생시킨다
class= 이렇게만 됨으로
Copy Constructor, 는 형식이 호환 되는 경우에만 컴파일 처리한다
//컴파일 가능
template<typename = void > void fn3() { }
fn3();
....
//컴파일 불가
template<typename = > void fn3() { }
fn3();
TSubclassOf (2)
TSubclassOf 는 UClass 유형의 안전성을 보장해 주는 템플릿 클래스입니다. 예를 들어 디자이너가 대미지 유형을 지정하도록 해주는 프로젝타일 클래스를 제작중이라 가정합시다. 그냥 UPROPERTY 유형의 UClass 를 만든 다음 디자이너가 항상 UDamageType 파생 클래스만 할당하기를 바라거나, TSubclassOf 템플릿을 사용하여 선택지를 제한시킬 수도 있습니다. 그 차이점은 아래 코드와 같습니다:
두 번째 선언에서, 템플릿 클래스는 에디터의 프로퍼티 창에 UDamageType 파생 클래스만 선택되도록 합니다. 첫 번째 선언에서는 아무 UClass 나 선택할 수 있습니다. 아래 그림에서 확인됩니다.
이러한 UPROPERTY 안전성에 추가로, C++ 수준에서의 유형 안전성도 확보할 수 있습니다.
비호환 TSubclassOf 유형을 서로에게 할당하려는 순간, 컴파일 오류가 나게 됩니다.
(위에 언급한 이유로)
범용 UClass 를 할당하려는 경우, 할당이 가능한지 검증하는 실행시간 검사를 합니다.
실행시간 검사가 실패하면, 결과값은 nullptr 입니다.
UClass* ClassA = UDamageType::StaticClass();
TSubclassOf<UDamageType> ClassB;
ClassB = ClassA; // Performs a runtime check //런타임때 체크
투본 IK 는 3개의 조인트를 갖고 IK 를 구하는 방식이다 (언리얼에서의 정의 : 투 본 IK 를 사용해서 3 조인트 체인에 IK 를 적용하는 법입니다. Two Bone IK(투 본 IK) 컨트롤은 캐릭터의 사지와 같은 3 조인트 체인에 Inverse Kinematics(역운동학, IK)를 적용합니다.)
투본 IK 의 경우 본 3개로 IK 를 구현 하는데 중간 조인트가 어느 방향쪽으로 걲일지에 대한 기준있게 되고
이 기준 위치를 따라 중간 본이 어떤 방향으로 꺽일지가 정해지게 된다
(무기/총 잡을 때 등에 쓰일 수 있다)
IK Bone : 컨트롤할 본 이름, IK 에 의해 컨트롤 되는 메인 본 체인 : 오른 손을 선택
Joint Target : 오른쪽 어깨를 선택 해 놓고
2본 IK(Two Bone IK) 노드엣 Joint Target Location 의 위치를 바꾸면 그 위치 기준으로 하여
꺽을 방향이 역으로 생기게 된다
아래 이미지는 Joint Target Location 을 캐릭터가 바라보는 앞쪽으로 뺏을때 팔이 역으로 꺽이는 형태이고
Joint Target Location 를 음수로 하면 즉 캐릭터의 뒤쪽에 배치를 시키면
바르게 팔이 꺽이는 것을 볼 수 있다
이 상태에서
Effector Location 위치를 바꾸면 right hand 가 대상을 따라간다
Analytic Two-Bone IK in 2D
December 29, 2008
Due to their complexity,inverse kinematics (IK) problems are often solved with iterative solutions. While iterative solutions can handle different IK poblems with a single algorithm, they can also become computationally expensive. If a specific IK problem needs to be computed often, it is worth considering an analytic solution.
If we limit the problem to a two bone chain in a two-dimensions, we can derive the anaytic solution without much complexity. This will generally be more efficient than its iterative alternatives. While this specific case lends itself to a 2D world, it can also be used in 3D as long as the kinematic chain's motion is limited to a single plane.
The goal of is to create a robust function with the following requirements.
Let the user specify which direction to bend the bone chain (there can be up to two valid solutions, each bending in an opposite direction).
Notify the user when there was no valid solution based on the input.
When there is no valid solution, calcualte the closest alternative.
Produce the expected results for every target position. Most implementations found online or in books only function properly when the target is in a certain quadrant or when it remains in a specific range.
If you aren't interested in theexcitingmath ahead, jump to the end of the article for sample code. You can also download theRJ_Demo_IKapplication and source code to see a running implementation. If you're the type that likes to know how things work (like me), we will walk through the derivation in detail. It might look extensive at first, but it's only because I've broken it down step by step. Hopefully you'll be able to follow along without much difficulty.
You might also be interested in the following articles on different IK solvers:
원점에 조인트 하나와 중간에 꺽인(θ2 앞에) 조인트 그리고 대상으로할 Target 지점 (x,y) 이렇게 3개가 주어지고
거리 d1, d2 가 주어진다
그리고 원점에서부터 target 까지의 거리 h 로 놓을 수 있는데
Two bone ik 는 figure1 의 도형을 보면 θ2, θ1 을 구하는 것으로 좁혀진다
아래의 유도 과정은 삼각함수들을 통해 구하는 과정으로 먼저 θ2 를 구한 다음 이를 토대로 θ1을 구해 나가는 방식이고
d1 과 d2 거리의 차의 거리까지가 target 으로 접근하는 최소 거리 d1+d2 의 거리가 target 으로 향할 수 있는 최대 거리이며 target 으로 향할수 있을때 target 점 까지의 각도를 두개의 각도 θ2, θ1 를 구할 수 있지만 그 외의서는
벌을 최대한 뻗어 Target 으로 향하는 방향을 가리키게 된다는 유도 과정이다
Defining the problem
Our problem domain consists of a two bone chain where bone1is located at the origin and bone2is a child of bone1. Each bone conistis of an angleθand a distancedd. The goal is to get the end of bone2to line up with the target point(x,y). Because we have set bone1 at the origin, you will need to transform your destination point relative to bone1.Figure 1shows how our variables fit together. The distance from the orgin to the target point is defined ashh.
figure 1
The solvable domain
figure 2
Math review
Before we hop into the derivation, I want to review a few trigonometric identities that we will be using. For more information on the identities,Wikipediahas anextensive listand also has apage on the derivations.
Solving for θ2
figure 3
We now have our analytic equation forθ2, but there are a few nuances to discuss in more detail.
theta2기준으로 봤을 때 d1 과 d2 가 회전 되어서 구부러지는 것을 생각해보면 되는데 theta2가 -1이면 d1 과 d2 가 펴지고, theta2 가 1 이면 d1 과 d2 가 최대로 구부러지게 된다 Ifcosθ2is not within the range [-1,1], it is outside of thearccosθdomain. This will happen when our target point is a location the bones cannot reach. A value of -1 will give us an angle of 180° (i.e. bone2is fully bent) and a value of 1 will give us an angle of 0° (i.e. bone2is fully extended). When the value is less than -1, our bone is trying to bend father than is physically possible to reach a point that is too close to the origin. When the value is greater than 1, our bone is trying to extend farther than is physically possible to reach a point that is too far from the origin. This means that we can compute the best non-valid solution by clamping the value into the legal [-1,1] range.
Ifd1ord2is zero, we will divide by zero when computingcosθ2. When one or both bones have a zero length, we can setθ2to any value we desire (I suggest zero or the value used on a previous frame). When we solve forθ1it will correct itself according to our selectedθ2. The solvable domain for this case has also been reduced to a single circle centered at the origin with a radius ofd1+d2.
When we can evaluatearccosθ, our result is limited to the range[0,π]. If we use the result as is, we will always find the solution where our second bone turns in the positive direction. If it is our intention to find the solution that bends in the negative direction, we need to negate the newθ2.
(theta2 기준으로 보면
Solving for θ1
figure 4
여기서 tan 공식을 보면
이런 형태가 있는데 이것을 이용하여 다음처럼 전개한다
Writing the code
We can now write our algorithm in code. The function will calculate a valid IK solution if possible. If there is no valid solution, it will calculate one that gets the end of bone2as close to the target as possible. Check out theRJ_Demo_IKapplication for a functional implementation of this code.
These code samples are released under the following license.
root node 의 반대편 즉 chain 의 끝 노드(Chain End)에서부터 root node 까지 노드(Link)를 방문 하면서
Chain End 노드가 target 에 접근 하도록 각 노드(link)의 각도를 taret 방향으로 회전 시켜주는 방식이다
나중엔 Chain End 가 Target 과 같거나 가장 근접한 결과를 뽑아내는 알고리즘이다
FABRIK 와 달리 알고리즘의 해가 정확하지 존재하지 않을 수 있다
즉 CCD 는 가장 근사한 해를 찾는 알고리즘임으로 해를 찾지 못하는 경우를 대비하여
loop count 를 두고 그 횟수를 제한해야 하는 부분이 필요하다
CCD 영상
언리얼 에서의 CCD 영상
CCDIK(Cyclic Coordinate Descent Inverse Kinematics, 순환 좌표 하강 역운동학) 스켈레탈 컨트롤 노드는 경량 IK 솔버로 (FABRIK과 비슷하며) 보통 본 체인 구동에 쓰입니다. 하지만 FABRIK 와 달리, CCDIK 는 솔브 도중 본의 회전을 제한하려는 경우 유용하게 쓰일 수 있는 각 컨스트레인트를 정의하는 기능을 제공합니다.
CCDIK 노드는 아래 그림처럼 절차적 애니메이션 구동에 쓰일 수 있습니다.
위 비디오에서 CCDIK 노드 프로퍼티의Solver(솔버) 섹션을 사용하여 캐릭터의 왼쪽 어깨에Root Bone(루트 본)을 설정했습니다. 그런 다음Tip Bone(끝 본)을 캐릭터 검지 마지막 숫자로 설정합니다.Effector Location(이펙터 위치)를 조정할 때, IK 솔버가 인계하면, 체인의 본 각각이 따르고, 정의한 Rotation Limit Per Joints (조인트 당 회전 제한) 값을 기반으로 합니다. 이 각 제한은 본이 이상하게 회전하는 것을 방지하며 원하는 결과를 내기 위해서는 (본 단위로) 값을 조정해야 할 수 있습니다.
=> CCD는 조인트당 회전 되는 각도의 제한을 두어서 팔이 의도하지 않은 관절 형태로 회전 되는 것을 방지 할 수 있다
Tip bone : 본의 끝 노드로 오른손 검지 끝마디로 설정하고
Root bone 은 오른쪽 어깨 본으로 세팅 해놓으면 Root bone 부터 tip bone 까지에 걸처 본들에 대한 제한 각도들을 설정 할 수 있다(각 본에 대해 회전 제한 각도를 설정하기 위한 본들은 알아서 잡힌다, 본의 개수에 맞게끔) 가장 아래 쪽 7번 인덱스가 검지 끝 본을 말하며 6번은 검지 끝 본 이전 것노드를 말하며 각 숫자는 회저회전 할 수 있는 각도를 설정할 수 있다, 이 각도로 IK 도 중 본이 이상하게 꺽이는 현상을 방지할 수 있다
위 그림에서 보면 오른손 손가락이 타겟 즉 Effector Location 을 향하고 있는 것을 볼 수 있다, 하지만 각도를 0~2에 해당 하는 부분에 대해 0 으로 설정해 놓았기 때문에 회전 추적 되지 않고 어깨부터 3개 의 본들은 그자리에 있는 것을 볼 수 있다
나머지 3개에 대해서도 30도 정도의 회전각 반경을 주면 아래 그림 처럼 오른쪽 어깨부터 오른손 검지 손가락이
타겟지점으로 향하는 것을 볼 수 있다
Using an iterative technique to determine orientations
for a multiple link IK chain
The way we will study here was first presented by Chris Welman in his
masters thesis on IK as an extension to work developed by Li-Chun
Tommy Wang and Chih Cheng Chen in an IEEE paper 'Transactions on
Robotics and Automation'. The technique is called Cyclic Coordinate
Descent. The principal algorithm is as follows:
1 Set a loop counter to zero.
2 Start with the last link in the chain.
3 Rotate this link to point towards the target.
4 Move down the chain and repeat step 2.
5 When the base object is reached, determine if the target has been
reached or a loop limit has been reached. If so exit, if not increment
loop counter and repeat from step 2.
This algorithm has the benefit of simplicity and can be easily
implemented.
To derive the angle
, we go back to the familiar methods of using
vectors. Remember that the dot product of vectors is given by
Unfortunately, the angle does not imply direction and we are interested in
both the angle and the direction. Since the cross product of two vectors
gives a perpendicular vector, we can use this to determine the direction.
By extending the vectors from two to three dimensions, we then have a z
value. The sign of this z value will give the direction in which to rotate.
defines the direction of rotation.
When using the algorithm, we start by iterating through the link chain to
find the last link. We then create unit vectors from the pivot point of the link
to the chain end and from the pivot point to the target. The inverse cosine
of the dot product of these two vectors gives the angle of rotation and the
third term of the result of the three-value vector cross product gives the
direction of rotation. We generate a rotation matrix using this information
and record the current chain end position.
We then move one link down the chain and determine new vectors from
the pivot point of the new link to the chain end and target and repeat the
dot product and cross product calculations. We then create a rotation
matrix and calculate the new location for the chain end. This procedure is
repeated until the base object is rotated. At this point we determine if we
are close enough to the target to exit the function. If not, we must repeat
the procedure. It is possible that the target cannot be reached. The
function needs to ensure that under such circumstances the program
does not enter an infinite loop. A simple technique to avoid this is to limit
the looping to a certain number of times; if this is exceeded then the
function exits.
If the total length of the links is less than the distance to the target, then
the goal can never be achieved and it can be useful to add this condition
to the code. Under these circumstances we return to the problem of
orientating a single link, the base link, and setting all other links to zero
rotation.
The code for the Cyclic Coordinate Descent method is as follows:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
void CIKDemoDoc::SolveMultiLink()
{
//links.next 시작 부분 base 에 해당 하는 노드
CLink* link = links.next, * lastlink, * tmplink;
int loopcount =0;
double x, y, totallength, linkslength =0.0, dotprod;
double mag, theta, sign, sqdist; VECTOR pe, pt;
//Check solution is possible
//시작 부분 base link 와 target 까지의 거리를 구한다
x = target.x - link->pos.x;
y =-target.y - link->pos.y;
totallength = sqrt(x*x + y*y);
//Find the last link and links length
//link(chain)들의 총 길이를 구한다
while (link->next)
{
linkslength += link->length;
lastlink = link;
link = link->next;
}
//lastlink : chain End 마지막 노드
//체인의 총 길이가 타겟에 닿을 수 있는 범위에 있는지 판단
//즉 체인이 타겟을 추적할 수 있는 범위안에 있는지 판단
if (totallength < linkslength)
{
//Calculate Cyclic Coordinate Descent solution
//link 마지막 노드
link = lastlink;
//최대 20 번까지 접근을 한다 : 완전한 해를 구하지 못하는 경우는 도중에 끊어야 한다
while(link && loopcount<20)
{
//end effector 따라가는 target에 해당, right 는 chain end 로 가는 단위 벡터인듯
1. target 지점으로 reach 를 하면( Forward) 기준이 되는 끝 노드 base 노드가 원래 있던곳에서 떨어지게 된다
2. 이때문에 다시 역으로 target 으로 reach 했던 것을 다시 원래 최초의 끝 노드 지점을 target 으로 잡고 reach 를 하는 (backward) 방식을 FABRIK ( Forward-and-Backward Reaching Inverse Kinematics ) 이라 한다
여기서 reach 는 각 노드들마다 target 을 기준으로 삼고 원래 해당 노드에서 target 기준으로 tail 노드를 끌어와서 target 지점을 목표로 각 노드들이 따라가는 것을 말한다
Tip bone : 끝 본에 해당하며 이것을 검지 손가락으로 설정하고
Root bone 은 어깨로 설정하여
타겟 : Effector Trasnform 위 이미지에서는 현재 붉은색 동그라미 부분의 좌표축의 위치
Effector 의 위치를 변경하면 어깨부터 검지까지 Effector 를 따라 가는것을 볼 수 있지만 관절마다의 각도 제한이 없기 때문에 뒷 부분으로 갈때 팔이 기이하게 꺽이는 현상을 볼 수 있다
Effector 위치를 FABRIIK 가 따라 갈수 있는 범위내 (즉 어깨부터 검지 손가락의 총 길이 내의 위치내)
에 있고 뼈가 꼬이지 않는 범위 부근에 위치한다면 다음 그림 처럼 정상적으로 보일순 있다
즉 FABRIK 은 인체 관절 보다는 자유스러운 형태에서의 IK 에 더 적합함을 알 수 있다
FABRIK stands for Forward-and-Backward Reaching Inverse Kinematics, and it’s a very simple, fast, and clever solution to the inverse kinematics problem.
Suppose you have a robotic arm with three joints, whose job is to point to a location:
How do you calculate the correct angle of each joint in order to hit the target?
This seems stupid at first – we solve this problem all day long, inside our bodies, as we reach for objects and navigate the world. But there is surprising complexity in this problem.
In order to appreciate the FABRIK solution, watch the following video as different algorithms attempt to solve IK in 3D:
This should be fairly straight-forward, but there is one important property to notice:
This functionalwayssucceeds. The line willalwaysbe moved so that it touches the target.
Making it Iterative
How can we make the process iterative? I.e., instead of pulling around a single line, how can we pull along achainof lines?
It’s quite ingenious:
The act of sliding the tail along the linecan itself be a reach.
Think about it. We have all the ingredients.
To perform a reach, we need a line (head + tail), and a target location. Well, in a chain, the tail of one segment is the head of another segment. For each segment, we simply perform a reach with the new target, until we run out of segments – then we slide the final tail to the target:
A robot arm doesn’t move around freely like a chain – it’s fixed at the base.
The last insight of FABRIK is that since we are guaranteed the head of the chain will always reach its target, we can perform the iterative reachin reverseto ensure that the base of the arm will always stay fixed.
Said another way:
First we perform the iterative reach like normal, so the head is guaranteed to be touching the target. Then we perform the iterative reach in reverse, so that the tail is guaranteed to be touching the base.
It’s quite amazing that such a simple concept – a stretched reach – has turned into solving the complex and puzzling inverse kinematics problem.
There are a lot of ways to extend this algorithm too.
Moving into 3D complicates it because orientation of the segments matter, but by tweaking thereachfunction, it can be made to work.
Adding constraints like fixed range of motion over a joint is possible too, but requires thought about where to enforce the constraints during the iterative process.
Moving objects other than lines is possible too (i.e., rigid triangles), if you can break down the objects into lines, and traverse the heirarchy correctly while iterating.
The important part is understanding the core idea, which I hope is clear now. Go forth, experiment, and have fun!
You can call Blueprint events and functions on demand in the Unreal Editor. This can be especially useful any time you need to run the same Blueprint graph both at runtime and in the editor. For example, you can test or preview your runtime gameplay within the editor UI. However, it can also be a simple way to trigger Blueprints within the editor that require an Actor or a location in 3D space as a context.
Supported Blueprint Classes
Not all Blueprint classes allow their Custom Events and Functions to run in the Unreal Editor.
The steps described below work for any Blueprint class that you can place in a Level—that is, any class that derives directly or indirectly fromActor.
If you need access to editor-only features like working with Assets in the Content Browser, you can derive your Blueprint class from a placeable editor-only base class, such asEditorUtilityActor. However, keep in mind that you won't be able to trigger your Blueprint at runtime when you use an editor-only base class, because editor-only classes are not included in packaged Unreal Engine applications.
Editor Utility Blueprintclasses that derive from theActorbase class don't expose buttons in theDetailspanel for any Functions or Custom Events that are marked as callable in the editor. If you absolutely need to use a button in theDetailspanel to drive your Blueprint logic, create your graphs in a normal Blueprint class and not in an Editor Utility Blueprint class. However, for a much more flexible and powerful approach to creating a custom UI to drive Blueprint logic in the Unreal Editor, consider using anEditor Utility Widgetinstead.
Steps
Any time you use aCustom Eventnode in the Event Graph of your Blueprint class, you can set theGraph > Call in Editoroption in theDetailspanel:
Similarly, when you create a new function on your Blueprint class, you can select the node for your new function and set the same option in theDetailspanel:
Add an instance of your Blueprint class into your Level if you haven't already.
Select the Blueprint Actor in the Level Viewport or in theWorld Outliner. TheDetailspanel shows a button for each of the Call in Editorevents and functions you've set up. You'll typically find them in theDefaultsection, where Blueprint classes also expose variables that are marked as Instance Editable.
If your Custom Event or function has any inputs, it will not be shown in theDetailspanel.
Click these buttons to trigger execution of the Event Graphs starting from your Custom Event nodes, or to trigger your custom functions.
기술이 발전하고 시간이 지남에 따라 그래픽카드 성능 역시 계속 발전해왔습니다. 그 덕에 더욱 놀랍고 사실적인 그래픽을 표현할 수 있게 되어왔습니다. 그 결과 현대의 PC 및 콘솔 게임의 그래픽은 현실과 그래픽을 분간하기 어려울 지경입니다. 이토록 그래픽 카드는 얼마나 더 멋진 그래픽을 얼마나 더 고속으로 처리할 수 있는 지가 가장 큰 이슈이고 이에 초점을 맞추어서 발전해왔습니다.
하지만 모바일 기기의 그래픽카드는 조금 다른 행보를 갖습니다. 모바일기기는 항상 전원이 연결되어있는 상황이 아니기때문에 전력 소모가 가장 큰 이슈가 됩니다. 또한 휴대가 용이하게 만들어야 하기 때문에 칩셋을 얼마나 물리적으로 작게 만드냐가 관건입니다. 게다가 물리적으로 작게 만드려면 쿨러를 장착할 수가 없기때문에 발열도 큰 문제가 됩니다. 이러한 모바일 기기의 특징들때문에 모바일에서는 Tile Based Rendering(타일 기반 렌더링)이라는 독특한 방식을 사용합니다. 이번 글에서는 Tile Based Rendering에 대해서 알아보고, 유니티에서 Tile Based Rendering을 고려시 주의점에 대해 다루고자 합니다.
Tile Based Rendering
우선 Tile Based Rendering에 대한 설명에 앞서 데스크톱의 렌더링 과정을 간단히 살펴보겠습니다. OpenGL에서 드로우콜을 날리면 지오메트리 데이터가 버텍스 쉐이더를 거쳐서 트랜스폼된 뒤 레스터화되고 픽셀쉐이더로 넘어가서 픽셀 컬러를 거칩니다. 픽셀쉐이더의 결과물은 바로 프레임 버퍼로 출력이 되면서 필요에따라 블렌딩 처리가 됩니다. 이처럼 드로우콜의 명령이 프레임버퍼까지 전달되는 과정이 한번의 패스로 이루어지고 매번 프레임버퍼 영역 전체가 갱신이 됩니다. 즉, 드로우콜 한번 당 한번에 바로 화면 전체에 렌더링합니다. (그래서 이러한 전통적인 렌더링 방식을 Immediate Mode Rendering이라 부르기도 합니다.)
하지만 모바일에서는 조금은 다른 방식을 사용합니다. 앞서 언급하였듯이 모바일에서는 전력 소모와 물리적 크기 등을 고려해야합니다. 이를 위해서 많은 고려사항들이 반영되며 설계가 됩니다만 그 중 가장 큰 고려 사항중 하나가 바로 대역폭입니다. 대역폭을 넉넉하게 쓰다보면 전력소모가 심해지고 물리적 칩셋 크기도 커집니다. 이는 발열로 이어지게 되는데 당연히 발열을 완화시킬 쿨러를 달 공간도 없습니다. 그래서 모바일에서는 대역폭을 줄이기위해 Tile Based Rendering(이하 TBR)이라는 아키텍쳐를 채용하고 있습니다.
앞서 말했던 것 처럼 전통적으로 데스크톱의 그래픽에서는 드로우콜마다 프레임 버퍼 전체를 갱신합니다. 하지만 높은 해상도의 프레임버퍼 전체를 매 번 갱신하는 것은 높은 메모리 대역폭을 요구하게 됩니다. 따라서 모바일에서는 프레임버퍼 전체를 매 번 갱신하는것이 아니라 타일 단위로 쪼개서 갱신을 하는 방식을 사용합니다. 드로우콜 발생 시 즉시 프레임버퍼에 기록하는 것이 아니라, 칩셋에 내장된 메모리에 존재하는 타일에 렌더링합니다. 이로 인해서 매번 화면 전체를 렌더링 하는 것이 아니라 실제 도형이 그려지는 타일만 렌더링 하게 됩니다.
이미지 출처 : Performance Tuning for Tile-Based Architectures
우선, 프레임버퍼를 일정 크기의 타일로 영역을 나눕니다. (이 타일 크기는 칩셋 벤더마다 차이가 있습니다.) 드로우콜이 발생하면 지오메트리 데이터가 버텍스쉐이더를 거쳐서 트랜스폼을 수행 후 레스터화됩니다. 여기까지는 전통적인 렌더링 방식과 동일합니다만 그 이후부터가 달라집니다. 버텍스 쉐이더의 결과가 바로 픽셀 쉐이더로 넘어가지 않고 타일을 선택하는 과정을 거칩니다. 그 후에 픽셀쉐이더가 수행되고 칩 내부 버퍼에 존재하는 타일에 그려집니다. 그 후 타일들이 완성되면 프레임버퍼에 그려집니다. 이런 식으로 타일 단위로 프레임버퍼를 갱신해주기때문에 적은 대역폭으로도 화면을 렌더링 할 수 있게됩니다.
또한, TBR에서 변형되어 파생한 Tile Based Deferred Rendering(이하 TBDR) 라는 방식도 있습니다. 이 방식은 기본적으로는 TBR입니다. 다만 버텍스 쉐이더에서 트랜스폼 연산을 거치고나서 바로 픽셀 쉐이더로 넘기는 것이 아닙니다. 대신 버텍스 쉐이더의 결과를 중간 데이터를 담는 파라미터 버퍼에 담아둡니다. (이 버퍼를 ImgTec에서는 파라미터 버퍼라 부르고, ARM에서는 폴리곤 리스트라 부르는 등 여러 이름이 존재하지만 편의상 파라미터 버퍼로 통일하여 칭하겠습니다.) 이 파라미터 버퍼에 담은 후 픽셀 쉐이더로 바로 넘기는 것이 아니라, 매 드로우 콜 마다 버텍스 쉐이더의 결과를 계속 담아둡니다. 그 후 모든 드로우콜이 끝나면 그때서야 비로소 타일을 렌더링하고 프레임버퍼에 출력합니다. 그렇게되면 한 타일에 들어오는 모든 폴리곤을 한번에 처리 할 수가 있게됩니다. 이 과정에서 타일의 각 픽셀에는 은면제거가 처리되고 나서 도달하기 때문에 픽셀 오버드로우가 발생하지 않습니다. 이러한식으로 TBR을 지연해서 처리하기때문에 Tile Based Deferred Rendering(타일 기반 지연 렌더링)이라고 불립니다.
이미지 출처 : Unity: iOS and Android - Cross Platform Challenges and Solutions
예전에는 TBDR 방식이 ImgTec의 PowerVR 즉 아이폰과 아이패드에서만 사용되었으나 최근들어서는 다른 칩셋들에서도 사용되고 있습니다. 하지만 여전히 안드로이드 기기는 TBDR보다는 TBR이 많이 사용되고 있습니다. 따라서 현 시점에서는 TBR을 사용하는 디바이스와 TBDR을 사용하는 디바이스가 공존하고 있는 상태입니다. (타일 기반이 아닌 전통적인 렌더링 기법을 쓰는 디바이스는 점유율이 매우 낮아서 논외로 합니다.)
이처럼 모바일에서는 타일 단위로 쪼개서 렌더링하는 방식을 사용하다보니 몇 가지 주의 사항이 존재합니다. 서론이 좀 길어지긴 했는데 결국 전달고자 하는 내용들은 다음과 같습니다.
앞선 글에서 설명드린 바와 같이 모바일에서는 타일 단위로 쪼개서 렌더링하는 방식을 사합니다. 그러다보니 전통적인 렌더링 방식에서와는 조금 다른 주의 사항이 몇 가지 존재합니다.
알파블렌딩(Alpha Blending) VS 알파테스트(Alpha Test)
전통적으로 데스크톱 게임의 리소스에는 알파블렌딩보다 알파테스트의 사용이 권장되어왔습니다. 불투명한 철망이라든가 찢어진 옷감같은 경우는 불투명하기때문에 비싼 블렌딩 연산을 사용하기보다는 알파테스트를 사용함으로써 픽셀 연산도 절약하자는 의도였습니다. 하지만 모바일의 TBR(Tile Based Rendering, 타일 기반 렌더링)에서는 정반대로 알파테스트보다는 알파블렌딩의 사용이 권장됩니다.
알파테스트처리를 하기 위해서는 픽셀쉐이더에서 동적분기(if문)가 사용됩니다. 데스크톱에서는 쉐이더의 동적 분기가 고속으로 처리되지만, 모바일에서는 동적 분기 성능이 취약합니다. 때문에 알파테스트는 쉐이더의 성능윽 하락시키는 원인이 됩니다.
게다가, TBDR(Tile Based Deferred Rendering, 타일 기반 지연 렌더링)에서는 픽셀 차폐의 고속 처리를 깨트립니다. 앞서 언급했다시피 TBDR에서는 여러 드로우콜의 버텍스 쉐이더 결과를 모아두었다가 은면 제거(Hidden Surface Removal)를 거친 뒤 실제 보이는 픽셀만 처리합니다. 하지만 이는 알파테스트를 사용하지 않는 완전한 불투명메시일 경우에만 해당됩니다. 알파테스트를 사용하면 버텍스 처리 단계에서는 해당 폴리곤이 차폐 되는지의 여부를 판단할 수 없기때문에 Deferred 처리를 깨트릴 수 밖에 없게됩니다.
유니티에서는 이를 방지하기위해서 완전 불투명 오브젝트들을 모두 렌더링처리한 후에 알파테스트 오브젝트들을 렌더링합니다. 따라서 알파 테스트를 제한적으로만 사용한다면 그렇게 치명적이지는 않습니다. 하지만 애초에 TBDR 칩셋의 구조가 알파테스트 처리에 적합하지 않기 때문에 알파테스트를 사용하지 않는 것이 좋습니다. 그런 이유로, 유니티의 내장 쉐이더 중 Mobile 카테고리에는 알파 테스트 쉐이더가 존재하지 않습니다.
반면에, 알파블렌딩은 데스크톱에 비해서 고속으로 처리가됩니다. 알파블렌딩과정은 출력 내부적으로 대상 버퍼의 읽기/쓰기가 발생합니다. 데스크톱에서는 DRAM에 존재하는 프레임버퍼 전체에 접근해야하기때문에 높은 대역폭을 잡아먹게됩니다. 하지만 TBR에서는 이 처리가 타일 단위로 이루어지고 칩 내부에 존재하는 메모리에서 이루어지므로 고속으로 처리됩니다.
오버드로우
다만 명심해야 할 것은 알파 블렌딩 처리 자체가 빠르다는 것일 뿐이지 오버드로우에서 자유로와진다는 것은 아닙니다. 예를 들어서 넓은 영역의 파티클을 높은 밀도로 뿌리는 것은 여전히 오버드로우 문제를 일으켜서 성능 저하로 직결됩니다. 모바일은 쉐이더 성능이 기종에 따라 천차만별이므로 오버드로우로 인해서 쉐이더 싸이클이 낭비되는 것은 치명적인 문제가 됩니다. 웬만하면 불투명 오브젝트 위주로 리소스를 만들기를 권장합니다. 알파 블렌딩 오브젝트 또는 파티클은 오버드로우를 최대한 피해서 사용하시기를 권장합니다.
로우 폴리곤
너무나 당연한 이야기라 뜬금없어 보일수도 있겠지만 많은 폴리곤을 처리하면 성능이 하락합니다. 게다가, TBDR에서는 많은 폴리곤 처리의 부담이 더욱 큽니다. 앞서 설명드린 버텍스 쉐이더의 결과물들을 담아두는 파라미터 버퍼(Parameter Buffer)의 크기는 당연하게도 무한하지 않습니다. 따라서 이 버퍼가 넘쳐버리면 더 이상의 버텍스 쉐이더 결과물을 받아들이지 못하고 버퍼를 비워줘야합니다. 이 버퍼를 비워주기 위해서는 타일의 픽셀 처리 후 프레임버퍼로 출력하는 사이클을 거쳐야합니다. 때문에, 이론상으로는 TBDR에서는 픽셀의 오버드로우가 발생하지 않아야하지만, 현실적으로는 폴리곤이 많을수록 오버드로우가 발생하게 발생하게 됩니다. 그러므로 오브젝트의 렌더링 퀄리티를 높여야한다면 버텍스를 늘리는 것 보다는 픽셀쪽 연산을 늘리는 것이 오히려 이득일 수도 있습니다.
렌더 텍스쳐(Render Texture)
렌더 텍스쳐를 사용하면 유니티 내부적으로 렌더 타겟(Render Target)을 변경하는 행위를 거치게됩니다. 이러한 렌더타겟을 변경하는 행위는 데스크톱에서도 성능을 잡아먹는 행위가 됩니다. 렌더 타겟을 바꾸기위해서는 CPU가 GPU를 대기하는 과정을 거치게되면서 CPU와 GPU의 병렬 관계가 잠기 깨지는 현상이 발생하기 때문입니다.
게다가, TBDR에서는 더욱 치명적인 행위가 됩니다. 렌더 타겟을 변경할 시에는 현재 파라미터 버퍼에 쌓여있는 데이터들을 모두 처리해주고 프레임버퍼에 출력합니다. 그 후 다음 렌더 타겟을 위해서 파라미터 버퍼를 비워줍니다. 이런식으로 렌더 타겟을 바꿀 시 deferred 사이클을 추가적으로 처리해줘야 합니다. 때문에 유니티의 카메라에서 타겟 텍스쳐(Target Texture)로 렌더 텍스쳐를 사용하는 경우에는 TBDR의 효율이 떨어지게 됩니다.
이미지 후처리 효과(Image post process Effect)
최근 디바이스들은 컬러 그레이딩이나 블룸 효과 등 이미지 후처리들을 사용할 수 있을 만큼 성능이 좋아졌습니다. 하지만 이러한 이미지 후처리들을 너무 남발해서 사용하면 안되고 필요한 것만 선택적으로 사용해야 합니다.
우선, 이미지 후처리들은 내부적으로 렌더 타겟을 변경하는 행위를 합니다. 하지만, 더 큰 문제는 대역폭입니다. 물론 픽셀 처리 능력도 관건이지만 대역폭이 더욱 큰 문제가 됩니다. 이미지 후처리들은 현재 렌더링 한 결과를 담고있는 렌더 타겟을 픽셀쉐이더의 입력 텍스쳐로 가져옵니다. 이 때 입력받는 텍스쳐는 칩 내부에 있는 타일이 아니라 공용 메모리에 있는 렌더 타겟을 가져오기때문에 엄청난 대역폭을 잡아먹게 됩니다. (예 : 1080p) 그러므로 이미지 후처리는 신중하게 사용해야 합니다.
카메라 클리어(Clear)
예전의 데스크톱 그래픽카드에서는 한 프레임의 렌더링을 시작하기 전 일부러 화면을 클리어해주지 않고 렌더링을 시작하는 경우도 있었습니다. 하지만 현대의 데스크톱 그래픽카드에서는 반드시 클리어를 해주어야만 하드웨어의 고속 처리를 지원받을 수 있습니다. 이는 모바일 기기의 TBR에서도 마찬가지입니다. 클리어를 수행하여 칩 내부의 버퍼들을 비워줘야만 이후 렌더링 과정을 고속으로 처리할 수 있습니다. 따라서, 유니티의 카메라에서 Clear Flag를 Don’t clear로 두는 것은 데스크톱에서나 모바일에서나 웬만해서는 권장되지 않습니다.
MSAA
데스크톱에서는 MSAA가 매우 큰 부담이 됩니다. 역시 마찬가지로 대역폭이 가장 큰 원인입니다. 예를 들어 1080p해상도의 화면을 MSAA 2X로 처리하려면 2160p만큼의 대역폭이 필요해집니다. DRAM으로부터 그만큼의 대역폭을 요구한 다는 것은 매우 큰 부담이 되는것입니다. 하지만 TBR에서는 이 역시 칩 내부의 타일에서 이루어집니다. 16x16 혹은 32x32정도에 불과한 타일로 MSAA처리해주는 것은 그리 부담이 되지 않습니다.
유니티에서 MSAA를 사용하기위해서는 퀄리티 셋팅에서 Anti Aliasing을 2 혹은 4로 선택해주면 됩니다. 다만 개인적으로는, 매우 높은 DPI를 자랑하는 대부분의 모바일 기기에서 안티 앨리어싱이 굳이 필요할 지는 모르겠습니다 :)
프로파일링
유니티5부터 프레임 디버거(Frame Debugger)가 추가되어서 프레임 별 렌더링 과정을 디버깅해볼 수 있게 되었습니다. 이를 통해서 오브젝트의 렌더링 과정이나 배칭 현황을 손쉽게 확인 해 볼 수 있게 되었습니다.
하지만 애석하게도 유니티의 프레임 디버거만으로는 드로우콜 별 GPU 퍼포먼스나 세부 상태를 확인하기는 힘듭니다.다행히도, 칩셋 벤더마다 렌더링 과정을 세부적으로 프로파일링을 해볼 수 있는 툴을 제공해주고 있습니다. 아드레노 칩셋은 아드레노 프로파일러를 통해서, 말리 칩셋은 말리 프로파일러나 DS-5를 통해서, 아이폰은 XCODE를 통해서 프로파일링을 해볼 수 있습니다.
다만 문제가 하나 있습니다. TBR 방식을 사용하는 칩셋은 콜 별 설능을 확인하는데 어려움이 없습니다. 하지만 TBDR 방식을 사용하는 칩셋은 콜 별 성능을 직관적으로 확인하는게 사실상 불가능하다는 것입니다. TBDR은 앞서 언급했다시피, 드로우콜 발생 시 픽셀 쉐이더를 즉시 처리하는 것이 아니라 파라미터 버퍼에 결과를 담아둡니다. 그 후 모든 드로우콜을 마치고 나면 그때서야 실제 렌더링을 수행하기 때문에 콜 별 성능을 실질적으로 확인해 볼 수가 없는 것입니다. 따라서 X-code에서 아이폰의 렌더링을 프로파일링 해보면 성능 관련 숫자가 0으로 나오게 됩니다. 0이 아닌 숫자가 나오는 경우도 있지만 이 역시 신뢰할 수 없는 숫자입니다. 대신 프레임 전체에 걸린 성능을 확인해보거나 콜 당시의 사용 텍스쳐 등 주변 정보로 유추해보는 수 밖에 없습니다. 이처럼 아이폰의 프로파일링은 좀 까다로운 편입니다.
TBR에서는 렌더링을 칩 내부의 타일에다 하는 과정은 대역폭을 먹지 않지만, DRAM 영역에 존재하는 프레임 버퍼에 타일을 출력하는 과정에서는 어느 정도는 대역폭이 필요할 수 밖에 없게됩니다. 이러한 대역폭을 조금이나마 절약하기 위해서 말리에서는 트랜잭션 엘리미네이션(Transaction Elimination)이라는 기술을 사용합니다. 타일 별로, 이전 프레임과 화면 결과가 달라지지 않은 타일의 영역은 프레임버퍼를 갱신하지 않고 이전 프레임의 결과를 재활용 하는 것입니다. 그렇게 하면 칩 내부 메모리에서 시스템 메모리로 복사하는 양이 줄어드는 효과를 갖게됩니다. 아래 예시 이미지의 파란 글자 타일이 바로 그 부분에 해당합니다.
따라서, 고정 카메라를 사용한다면 스카이박스 등의 배경에는 최대한 변화를 피하는 것도 좋은 방법이 될 수도 있습니다. 현실적으로는 3D 게임에는 이러한 조건에 해당하는 경우가 많지는 않을 것입니다. 하지만 2D 게임에는 적합하는 부분이 많은 것입니다.
마치며
TBR에 관한 내용이랑 TBDR에 관한 내용을 같이 언급하긴 하였습니다. 하지만 대부분은 아이폰이나 안드로이드폰만 타겟으로 설정하고 개발할 것이고, 플랫폼마다 데이터를 별도로 제작하지는 않을 것이라 예상합니다. 따라서 현실적으로는 TBR, TBDR 모두 고려대상으로 삼고 이러한 사항들을 인지하면서 개발하여야 할 것이라 생각합니다.
Unity 시작 직후에 사용자의 작업 없이 프로젝트 에디터 스크립트 코드를 실행하는 것이 유용한 경우가 종종 있습니다. 이것은InitializeOnLoad속성을static constructor(정적 생성자)가 있는 클래스에 적용함으로써 얻을 수 있습니다. 정적 생성자는 클래스와 같은 이름의 함수로 정적으로 선언된 함수이며, 반환 값이나 인수가 없습니다(자세한 내용은여기를 확인하시기 바랍니다) : -
1
2
3
4
5
6
7
8
9
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public class Startup {
static Startup()
{
Debug.Log("Up and running");
}
}
정적 생성자는 어떤 정적 함수와 클래스의 인스턴스가 사용되는 것보다 먼저 호출되는 것이 보증되고 있지만, InitializeOnLoad 속성에 의해 에디터 시작할 때 호출되는 것을 보장합니다.
이 기술이 사용되는 예는 에디터에서 일반 콜백을 설정할 때(“프레임 업데이트” 등) 입니다. EditorApplication 클래스에는update라는 대리자(Delegate)가 있고, 이것은 에디터가 실행되는 동안 초당 여러 번 호출됩니다. 프로젝트 시작할 때 이 대리자를 사용하려면 다음과 같은 코드를 사용합니다 : -
hat I am trying to do is in myStart()method of a prefabGameObjectin my scene is I want to get the location of where this prefab is as a string but I cannot figure out how to do this.
Example of how I am using this: I put a Sword PrefabGameObjectin my scene and onStart()I would like to get the file directory of where thisGameObjectis in my project.
Includeusing UnityEditor;
NOTE: This will only work if the prefab is to to public.
Then useAssetDatabase.GetAssetPathto get the path of the prefab.
I did some experiment withPrefabUtility.GetPrefabType,PrefabUtility.GetPrefabObjectandPrefabUtility.GetPrefabParent.PrefabUtility.GetPrefabParentsolved the problem. You don't need to make the prefabpublicwithPrefabUtility.GetPrefabParent.
Reset is called when the user hits the Reset button in the Inspector's context menu or when adding the component the first time. This function is only called in editor mode. Reset is most commonly used to give good default values in the Inspector.
// Sets target to a default value. // This could be used in a follow camera.
C언어 등에서 일반적으로 사용하는 함수는 시작할 때 진입하는 지점이 하나 존재하고 함수가 모두 실행되거나, return 구문에 의해 종료되는 지점을 설정할 수있다.
이러한 함수를 Subroutine( 서브루틴 )이라 부르는데, 코루틴은 이를 더 일반화한 개념으로진입하는 시점까지 여러 개를 가질 수 있는 함수를 의미한다. 개념적으로만 본다면 서브루틴의 한 종류라고 볼 수 있겠다.
C#, JavaScript, Python 등이 제공하는 Generator가 사실 코루틴의 한 형태이다.
caller가 함수를 call하고, 함수가 caller에게 값을 return하면서 종료하는 것에 더해 return하는 대신 suspend(혹은 yield)하면 caller가 나중에 esume하여 중단된 지점부터 실행을 이어갈 수 있다.
즉, 코루틴 함수가 호출되면 코루틴 함수 내부코드가 실행되고 있다가 suspend 혹은 yield에 의해 함수가 호출된 부분에 값이 반환되고 이후 다시 코루틴 함수를 호출하게 되면 코루틴 함수 내부에 suspend 혹은 yield 지점 이후 코드로 돌아와서 실행을 하게 된다는 말이다.
부분적으로, 그리고 특정한 상황이 맞아 떨어졌을 때 실행되는 함수.
-장점
1. 성능
일반적으로 게임에서 코루틴을 사용하지 않는다면 매 프레임마다 Update구문에서 동작을 확인해야하는 상황이 발생한다.
하지만 코루틴을 사용하면 코루틴이 지정한 시간이 후에 자동으로 확인된다.
이러한 점 때문에 특히 모바일 기기에서 코루틴의 활용은 성능 향상에 큰 영향을 미친다.
2. 가독성
코드 가독성이 좋아진다. 이건 써보면 알 것 이다.
-특성
* 특정 작업을 단계적으로 발생하게 한다.
* 시간 흐름에 따라 발생하는 루틴을 작성할 수 있다.
* 다른 연산이 완료될때까지 기다리는 루틴을 작성할 수 있다.
*비동기가 아니다. = 동시에 발생하지 않는다.
Thread
현재에 와서 스레드의 경우는 멀티코어를 넘어서 many 코어 시대로 전환 되고 있기 때문에 이 자원들을 충분히 모두 이용하기 위해병렬 프로그래밍을 하게 되는데 이 병렬 처리의 핵심도구가 스레드 이다.
멀티프로세스 기께에서, 스레드는 실제 다른 스레드와 함께 동시에 코드가 실행 될 수 있다. 하지만 여러 스레드를 사용하는 멀티스레드 프로그래밍은 코드를 이해하기 복잡하게 만든다.
다른 스레드가 특정 부분을 읽고 있는 동시에 그것을 변경할 수 있기 때문이다.
이 때문에, 공유 메모리영역을 만들지 않거나, 공유된 자원을 읽거나 변경시켜야 되는 경우라면, 공유된 자원으로 부터 다른 스레드를 잠궈버림으로써( Lock ) 이런 상황이 발생하지 않도록 하고 있다.
스레드를 이용하면 하나의 프로그램에서 한 번에 하나의 일을 처리하는 것이 아니라 동시에 많은 일을 처리할 수 있다.
Now I'm not sure if I'm blaming Unity3D correctly, maybe I misinterpreted the article. But is there a Math function which does work like Mathf.Sign() only returns 0 when x == 0?
We wanted to have the enemies in Timefight Zone sort of “burn” into existence when they spawn, and burn away when they die. We began to look into how to do this, and quickly came across something called a dissolve shader.
We're going to assume you have a basic knowledge of Unity shaders for this tutorial, if you don’t, there are lots of excellent tutorials that can familiarize you with the basics just one Google search away.
Basic Dissolve Shader
So thanks to the Unity wiki pageI linked above, we have a dissolve shader to work with. It looks like this:
This shader takes a value (_SliceAmount) and a texture (_SliceGuide), and uses a function calledclip()to hide any pixels on_SliceGuidewho's brightness is less than_SliceAmount.
Below is a gif showing the basic dissolve shader in action. I mapped the same texture to_SliceGuideand_MainTexto better illustrate how the brightness of the pixel in_SliceGuidedetermines at which point a pixel is hidden.
We have added a new value (_BurnSize), and a new texture (_BurnRamp). To recap, we are hiding parts of the model usingclip(), which hides the pixel if the value passed into it is less than 0. We subtract_SliceAmountfrom the RGB value (brightness) of_SliceGuide, so if_SliceAmountis greater than the brightness of the given pixel of_SliceGuide, the pixel is hidden.
To add the burn effect, we again subtract_SliceAmountfrom the RGB value (brightness) of_SliceGuide, and this time if the resulting value is less than_BurnSize, we choose a pixel on_BurnRampto color the pixel. This means that we can change_BurnSizeto modify how far out from the dissolved area gets a burn effect. The higher_BurnSizeis, the farther the burn effect extends.
The pixel on_BurnRampis chosen based on how far past_BurnSizetestis. At 0 you get the far left pixel, and as the value approaches_BurnSize you move farther to the right side of the image. So you're generally going to want to have the bright ember colors on the left, and the dimmer charred colors on the right on your_BurnRamp.
Below is a gif of_BurnSizebeing modified in real time to illustrate it's effect.
Note how increasing_BurnSizedoes not effect the areas that are dissolved, it merely changes how large the burned area extends.
That's about it. It's a relatively simple effect to achieve, but surprisingly hard to find concrete information on. Hopefully this post will help a few people add a cool effect to their game.
Notes
The way we are coloring the burnt edges is by setting the emission (o.Emission) to the color of the pixel we chose off _BurnRamp, as well as multiplying the base color (o.Albedo). This will cause the burnt areas to glow in the dark. If you want to achieve an effect with this shader where the edges shouldn't glow, you'll have to changeo.Emission, too.Albedo, and remove the next line down where we multiplyo.Albedobyo.Emission.
Our_BurnRampis designed for a cartoony art style. If your game has a more realistic art style, you may want a smoother gradient.
Feel free to use this shader and_BurnRampin your game. We made this tutorial because we couldn't find any specific information on this type of shader outside paid Unity assets. Hopefully this has been informative, but even if it made absolutely no sense, we'd like for more people to be able to achieve this effect in their games, so just steal our version. :)
애니메이터 오버라이드 컨트롤러는 원본의 애니메이터 컨트롤러를 확장하는 에셋으로 사용된 특정 애니메이션을 대체하지만 원본 구조, 파라미터와 로직을 유지합니다.
이를 통해 여러 배리언트의 동일한 기본 상태 머신을 만들 수 있습니다. 하지만 각각 다른 애니메이션 세트를 이용합니다. 예를 들어, 게임상에서 다양한 NPC 타입이 있지만 각 타입(고블린, 오거, 엘프 등)은 걷기, 대기, 앉기 등 동작에서 고유의 애니메이션을 가집니다.
모든 NPC 타입을 위한 로직을 가진 하나의 “베이스” 애니메이터 컨트롤러를 만들어서 각 타입의 오버라이드를 작성할 수 있으며 이를 각각의 애니메이션 파일에 연결할 수 있습니다.
이를 시연하기 위해 대표적인 애니메이터 컨트롤러 에셋을 살펴보겠습니다.
다음은 간단한 상태 머신을 담고 있는 애니메이터 컨트롤러를 나타내며 여기에는 다음과 같이 생긴 네 방향 블렌드 트리 조절 애니메이션과 대기 상태 에니메이션을 포함하고 있습니다.
이런 일반적인 NPC 상태 머신을 유니크한 애니메이션을 사용하는 오거 타입으로 확장하기 위해서는 애니메이터 오버라이드 컨트롤러를 만들어서 오거의 애니메이션 클립 안에 드롭함으로써 원본 애니메이션 클립을 대체할 수 있습니다. 오거는 더 느리고 무거우며 근육질적인 움직임 같이 다른 대기 방식과 움직임을 가질 수 있습니다. 그러나 애니메이터 오버라이드 컨트롤러를 사용하면 캐릭터의 움직임 상태 사이의 트랜지션과 블렌드가 어떻게 일어나는지에 대한 기본적인 로직이 다른 애니메이션 세트를 가진 캐릭터들 간에 공유될 수 있습니다. 그렇게 되면 상태 머신을 구축하고 변경하는 데 들어가는 작업을 줄일 수 있습니다.
새 애니메이터 오버라이드 컨트롤러를 만드려면 에셋(Assets) -> 생성(Create) 메뉴를 사용하거나 프로젝트 뷰의 생성(Create) 버튼에서 애니메이터 오버라이드 컨트롤러를 선택합니다.
애니메이터 오버라이드 컨트롤러는 애니메이터 컨트롤러와 비슷한 아이콘을 가지고 있지만, 전자는 아이콘 구석에 “플러스” 기호가 있고 후자는 “재생” 기호가 있습니다.
인스펙터에서 새 애니메이터 오버라이드 컨트롤러를 선택할 때, 처음에는 애니메이터 컨트롤러가 할당되지 않은 상태이며 아래와 같은 모습입니다.
오버라이드 컨트롤러를 사용하기 위해서는 원본 컨트롤러 에셋을 인스펙터의 새로운 오버라이드 컨트롤러에 할당해야 합니다. 이렇게 하면 원본 컨트롤러에 사용된 모든 애니메이션이 오버라이드 컨트롤러의 인스펙터 리스트상에 나타납니다.
다음으로 원본 애니메이션 클립을 오버라이드 하기 위해 새로운 애니메이션을 할당합니다. 이 예제에서 모든 애니메이션 클립은 “오거” 버전으로 오버라이드 되었습니다.
오버라이드 컨트롤러는 이제부터 애니메이터 컨트롤러로서 오거 캐릭터의 게임 오브젝트의 Animator 컴포넌트에 사용될 수 있습니다. 이는 원본 애니메이터 컨트롤러와 동일한 로직을 사용하지만 원본 대신 새롭게 할당된 애니메이션을 재생합니다.
처음 열린 애니메이터 뷰에는 3개의 스테이트(State)가 생성되어 있습니다. 각 스테이트의 역할은 다음과 같습니다.
스테이트 종류
역할
Entry
시작 스테이트로 최초의 진입점이다.
Exit
종료 스테이트로 모든 스테이트가 종료되는 마지막 스테이트다.
Any State
현재 어느 스테이트를 실행하고 있더라도 조건에 만족하면 분기시켜야 하는 스테이트가 있을 경우 사용한다.
Has Exit Time 속성은 수행중인 애니메이션이 다 끝난 후 전이가 되도록 하는 속성입니다. 바로 전이를 발생시키고자 할 때는 Has Exit Time 속성을 언체크합니다.
유니티 메카님에서는 트랜지션이 일어나는 도중에 다른 트랜지션에 의해서 해당 트랜지션을 중단시킬 수 있는 기능이 있습니다. 이것은 4.x 버전에서는 atomic 설정의 on/off 를 통해서 트랜지션 중단의 가능 여부를 지정할 수 있었습니다. 유니티 5에서는 이 기능을 더 확장하여 더욱 상세한 설정이 가능한 Transition interruption source 옵션 항목이 추가되었습니다. (atomic 설정방식은 5 버전에서는 제거되었습니다.)
트랜지션의 인스펙터 창을 열어봅니다:
[Settings] 설정 안에 [Interruption Source] 옵션이 있는 것을 볼 수 있습니다. 이 옵션을 통해서 사용자는 해당 트랜지션이 어떤 스테이트의 트랜지션들에 의해서 중단될 수 있는 지를 설정할 수 있습니다.
그럼 어떠한 옵션항목이 있는지 드롭다운 메뉴를 열어보겠습니다:
위 그림에서와 같이 5가지 interruption source 옵션 항목이 있습니다. 그러면 각 항목들이 의미하는 것은 무엇인지 알아보겠습니다. 조금 더 이해를 쉽게 하기 위해서 아래 그림와 같은 상황을 가정한 상태에서 설명할까 합니다:
그림에서 파란색으로 표시된 트랜지션 t 의 Interruption Source를 설정한다고 했을 때 각 옵션 항목들을 아래와 같이 설명할 수 있습니다.
1) None: 트랜지션 t는 어떠한 상황에서도 간섭 받지 않습니다. 4.x 버전에서 atomic을 켰을 때와 동일합니다.
2) Current State: 트랜지션 t는 A 스테이트에서의 다른 트랜지션들에 의해 중단될 수 있습니다. 그림 상에서 볼 때, 만약 해당 트랜지션이 일어나고 있는 도중에 A->B 또는 A->C의 트랜지션이 일어나는 조건이 만족하게 된다면 트랜지션 t는 중단되고 조건을 충족한 새로운 트랜지션이 일어나게 됩니다.
3) Next State: 트랜지션 t는 X 스테이트에서의 다른 트랜지션들에 의해 중단될 수 있습니다. 그림 상에서 볼 때, 만약 해당 트랜지션이 일어나고 있는 도중에 X->Y 또는 X->Z의 트랜지션이 일어나는 조건이 만족하게 된다면 해당 트랜지션은 중단되어 버리고 조건을 충족한 새로운 트랜지션이 일어나게 됩니다.
4) Current State Then Next State: 트랜지션 t는 A와 X 스테이트의 다른 트렌지션들에 의해 중단될 수 있습니다. 단, A 스테이트가 우선순위가 높으므로, 양쪽 스테이트의 트랜지션들이 서로 동시에 발생할 수 있는 조건일 경우 A 스테이트에 속한 트랜지션이 먼저 선정됩니다.
5) Next State Then Current State: 트랜지션 t는 A와 X 스테이트의 다른 트렌지션들에 의해 중단될 수 있습니다. Current State Then Next State 와는 반대로 X 스테이트가 우선순위가 높습니다. 따라서 X 스테이트에 속한 트랜지션이 먼저 선정됩니다.
Notes: For colliders that overlap the box at the start of the sweep, RaycastHit.normal is set opposite to the direction of the sweep, RaycastHit.distance is set to zero, and the zero vector gets returned in RaycastHit.point. You might want to check whether this is the case in your particular query and perform additional queries to refine the result.
박스 단위로 충돌을 검사하는 함수입니다
유효한 충돌 범위는
(대상 오브젝트의 center+ halfExtents 위치 기준=)maxDistance > 충돌 가능 범위 > center+ halfExtents
.99 그이하 미소 범위[e] 에 대해선 소수점 오차로 인해 충돌 판별 되는 양상을 띕니다
가정
Cast 보낼 오브젝트 위치 정보
pos : 0,0,-30
scale : 10,10,10
대상 오브젝트 정보 [1]
pos : 0,0,-5
scale : 10,10,10
maxDistance 15
그외 정보는 일반적으로 unit 한 정보라 가정합니다(normal 은 당연히 시작 오브젝트에서 대상을 바라본다 가정 합니다)
위의 경우 충돌이 되지만
대상 오브젝트가 [2]
pos : 0,0,-4.5
scale : 10,10,10
인 경우에는 충돌되지 않습니다
계산
충돌 시작점 + half
center+ halfExtents 의 위치에서부터 maxDistance 이 계산 됨으로
-30 + 15 + 5 = -10
즉 대상체의 충돌 검출 시작점은 -10 (충돌 가능 범위 기준) 이내 범위어야합니다
그리하여 (대상 오브젝트가 [2])
-4.5 - 5 = -9.5 의 범위에선 충돌되지 않지만
그리하여 (대상 오브젝트가 [1])
-5.0 - 5 = -10 의 범위에선 충돌 검출을 하게 됩니다
만약 충돌될 대상 오브젝트가 충돌 시작위치의 halfExtents 을 침범하게 된다면?
주의
은 출돌검사를 시작하는 center 위치에서 halfExtents 까지의 범위 안에 충돌체가 들어왔고 이 충돌체와 충돌 하게 되면 pint 값이 0 이 되며
normal 값은 sweep 의 방향에 반대되는 (- 가 곱해진) normal 을 만듭니다
First, Download Custom Web Search of Visual Studio Extension program and select Options -> Environment-> keyboard,
and search Custom Web Search shortcut in 'Show commands containing' blank to go Unity web help page, and then assign your short key to 'Press shortcut keys'
AddListener to OnPointerDown of Button instead of onClick
I'd like to register a callback to the OnPointerDown and OnPointerUp events of the UnityEngine.UI.Button(s) in my game to trigger two different sounds for down and up click. However, only the onClick event is exposed. Is there any clean method to get these properties through code? I'd rather not add the UnityEngine.EventTrigger component to every button individually and set them up in the scene, because of the amount of buttons in my project and I might want to change behaviour while testing, and therefore rather do it from one central location in code.
I was expecting this to be built into the Unity button component, because it's such a common thing to ask for when assigning sounds to buttons and also the button must use it's OnPointerDown event internally to trigger sprite transitions etc. Why wouldn't they just expose these callback publicly like onClick already is?
For every button that needs additional listeners, I add an EventTrigger component and the appropriate PointerDown EventTriggerType. This has much more overhead than I wanted it to have, but it still works better than adding sound components to 20 buttons manually.
PS: Of course, I'd still be interested in seeing a better solution than mine.
Edit - Custom UI Button
I just figured, that I could also subclass the existing Unity Button to add my desired functionality. I keep forgetting that the UI source is extendable. This works very well and feels much cleaner. Just to spread community knowledge, here is my button extension, which I use to later add two different click sounds for down and up.
usingUnityEngine;
usingUnityEngine.UI;
usingUnityEngine.Events;
usingUnityEngine.EventSystems;
usingSystem;
// Button that raises onDown event when OnPointerDown is called.
[AddComponentMenu("Aeronauts/AeButton")]
publicclassAeButton:Button
{
// Event delegate triggered on mouse or touch down.
There are, of course, a variety of shaders and more efficient ways to get at them than Find(), but this is the answer for someone just looking to put a transparent cube on their early-on n00b project -- "how hard can it be?!"
I'm sorry you didn't find my answer sufficient, but I did say "You need to use a shader that has transparency." The second part ("The default diffuse shader doesn't") was merely a qualifier to the more important first part. I figured it would have been fairly obvious that you'd take another look at the shader drop-down list, see the "Transparent" section there, and say "Oh, I get it." Because that is exactly the reaction of several people in the past who've asked this question and gotten the same answer from me. I try to provide only the relevant info rather than wasting your time with fluff.
Eric: yeah, good points all. Perhaps I wasn't clear that I was trying to do all of this in script, at which point "use a different shader" is, as I said, technically correct but not helpful. I'm not trying to "start up" with you -- I just noticed that a very many of the answers on this site are like that. I come from a strong StackOverflow background, where the answers are typically more overt, it all. No worries, I'll get the hang of things around here.
Want to re-resurrect, I have also gotten this to work, I got allot of errors, but I copy and pasted your code, swapped some stuff, deleted your code and put in my own and it works,
EDIT: I would also like to note that structs in structs work also, very good for organization!
Notes: For colliders that overlap the box at the start of the sweep, RaycastHit.normal is set opposite to the direction of the sweep, RaycastHit.distance is set to zero, and the zero vector gets returned in RaycastHit.point. You might want to check whether this is the case in your particular query and perform additional queries to refine the result.
I'm sorry you didn't find my answer sufficient, but I did say "You need to use a shader that has transparency." The second part ("The default diffuse shader doesn't") was merely a qualifier to the more important first part. I figured it would have been fairly obvious that you'd take another look at the shader drop-down list, see the "Transparent" section there, and say "Oh, I get it." Because that is exactly the reaction of several people in the past who've asked this question and gotten the same answer from me. I try to provide only the relevant info rather than wasting your time with fluff.
Also, if you find an answer to be unclear, just post a comment under it saying so, and I'll expand it.
Eric: yeah, good points all. Perhaps I wasn't clear that I was trying to do all of this in script, at which point "use a different shader" is, as I said, technically correct but not helpful. I'm not trying to "start up" with you -- I just noticed that a very many of the answers on this site are like that. I come from a strong StackOverflow background, where the answers are typically more overt, it all. No worries, I'll get the hang of things around here.