인스턴싱 개수만큰 파이프라인에서 정점셰이더이후의 과정을 개수만큼 반복하게 되어 cpu 에서 gpu 로의 전송 과정을 아낄 수 있다
각 인스턴싱된 오브젝트들은 SV_InstanceID 로 구분할 수 있다
파티클들의 위치나 방향을 cpu 에서 gpu 로 넘겨주는게 아니라 gpu 내부에서 SturcturedBuffer 를gpu 쪽에 만들어 놓으면 gpu 내에서 현재 파티클의 정보를 g_data 에 넣고 이것에 대한 움직이는 정보를 RWStructuredBuffer 에다가 기록을 하여 파티클을 움직이도록 처리 할수 있다, GPU 내에서.
[t9 는 t 는 텍스처 같은 형태로 자료의 크기가 상수버퍼 처럼 정해져 있지 않는 것을 사용한다는 것 또한 알 수 있다]
그리고 물체들의 위치는 이전 단계인 Compute Shader 에서 위치를 먼저 계산해 놓게 된다
그려질 필요가 없다면 지오메트리 셰이더에서 파티클을 그리지 않는다
GS 는 도형 정보를 수정 할수 있따, 추가 및 제거
최적화를 위해 정점 하나만 받아서 정점이 살아 있지 않다면 정점을 더이상(메시를) 그리지 않는다 그려질 필요가 없다면 모든 정점을 받을 필요가 없기 때문
outputSteam 은 정점을 생성 할거라면 이곳에 정점들을 추가해 이 정점에 대한 메시를 그리게 된다
즉 outputSteam 이것이 비어져 넘겨지게 되면 아무것도 그리지 않는다는것
VIew space 주석이 있는 곳이 정점을 뷰시점에서 사각형을 만들어 outputSteam 에 추가해 사각형을 만들고 있다는 것을 알 수 있다
이렇게 처리하면 CPU 와 GPU 의 병복 현상을 많이 줄일 수 있어서 성능향상이 될 수있다
전체적인 순서를 컴퓨트 셰이더를 통하여 위치나 방향 생명주기를 먼저 계산한다음 이 정보를 GPU 배열에 저장해 놓고
그러나 이렇게 하면 한 개 이상의 텍스처를 샘플링하면서 샘플러를 다른 텍스처에서 “재사용”하게 셰이더를 작성할 수 있습니다. 아래 예제에서는 3개의 텍스처에 하나의 샘플러만을 사용합니다.
Texture2D _MainTex;
Texture2D _SecondTex;
Texture2D _ThirdTex;
SamplerState sampler_MainTex; // "sampler" + "_MainTex"
// ...
half4 color = _MainTex.Sample(sampler_MainTex, uv);
color += _SecondTex.Sample(sampler_MainTex, uv);
color += _ThirdTex.Sample(sampler_MainTex, uv);
그러나 DX11-style HLSL 구문은 일부 구형 플랫폼(예: OpenGL ES 2.0)에서는 동작하지 않는다는 점에 유의하십시오. 자세한 내용은Unity에서 HLSL 사용를 참조하십시오.#pragma target 3.5(셰이더 컴파일 타겟참조)을 지정해 구형 플랫폼이 해당 셰이더를 건너뛰도록 할 수 있습니다.
Unity는 이 “분리된 샘플러” 접근 방법을 사용한 선언과 텍스처 샘플링에 도움이 될만한 여러 셰이더 매크로를 제공하고 있으니빌트인 매크로를 참조하십시오. 위의 예제는 이 매크로를 사용해 이렇게 재작성할 수 있습니다.
UNITY_DECLARE_TEX2D(_MainTex);
UNITY_DECLARE_TEX2D_NOSAMPLER(_SecondTex);
UNITY_DECLARE_TEX2D_NOSAMPLER(_ThirdTex);
// ...
half4 color = UNITY_SAMPLE_TEX2D(_MainTex, uv);
color += UNITY_SAMPLE_TEX2D_SAMPLER(_SecondTex, _MainTex, uv);
color += UNITY_SAMPLE_TEX2D_SAMPLER(_ThirdTex, _MainTex, uv);
위는 Unity가 지원하는 모든 플랫폼에서 컴파일할 수 있지만 DX9 같은 구형 플랫폼에서는 3개의 샘플러를 사용하도록 폴백합니다.
인라인 샘플러 상태
“sampler”+TextureName 이름의 HLSL SamplerState 오브젝트 외에도 Unity는 샘플러 이름의 다른 패턴도 인식합니다. 간단하게 하드코드된 샘플링 상태를 셰이더에 바로 선언할 때 유용합니다. 예를 들면, 다음과 같습니다.
“AnisoX”(X는 2/4/8 또는 16일 수 있음. 예:Ansio8)를 추가하여 이방성 필터링을 요청할 수 있습니다.
다음은 각각sampler_linear_repeat샘플링 텍스처와sampler_point_repeatSamplerState의 예제로, 이름이 필터링 모드를 조절하는 방식을 보여줍니다.
다음은 각각SmpClampPoint,SmpRepeatPoint,SmpMirrorPoint,SmpMirrorOncePoint,Smp_ClampU_RepeatV_PointSamplerState의 예제로, 이름이 랩 모드를 조절하는 방식을 보여줍니다. 이 마지막 예제에서는 가로(U)와 세로(V) 축에 대해 다른 랩 모드가 설정되었습니다. 모든 경우에 텍스처 좌표는 –2.0 - +2.0입니다.
분리된 텍스처+샘플러 구문과 마찬가지로 인라인 샘플러 상태가 모든 플랫폼에서 지원되지는 않습니다. 현재는 Direct3D 11/12 및 Metal에서 구현됩니다.
참고로, “MirrorOnce” 텍스처 랩 모드는 대부분의 모바일 GPU/API에서 지원되지 않고, 이런 경우 Mirror 모드로 폴백합니다.
“AnisoX” 필터링 모드는 플랫폼 기능과 일부 API에서 최선의 수단입니다. 실제 값은 지원되는 최대 이방성 수준에 기반하여 고정됩니다(이방성 필터링이 지원되지 않는 경우의 비활성화 포함).
흔히 쓰이는 렌더링 효과 중에는 한 단계에서 GPU가 자원 R에 자료를 기록하고, 이후의 한 단계에서 그 자원 R의 자료를 읽어서 사용하는 식으로 구현하는 것들이 많다. 그런데 GPU가자원에 자료를 다 기록하지 않았거나기록을 아예 시작하지도 않은 상태에서 자원의 자료를 읽으려 하면문제가 생긴다. 이를자원 위험 상황(Resource hazard)이라고 부른다.
이 문제를 해결하기 위해, Direct3D는 자원들에게상태를 부여한다. 새로 생성된 자원은 기본 상태(default state)로 시작한다. 임의의 상태 전이를 Direct3D에게'보고'하는 것은 전적으로응용 프로그램의 몫이다.
여러 리소스 상태들
이 덕분에, GPU는 상태를 전이하고 자원 위험 상황을 방지하는 데 필요한 일들을 자유로이 진행할 수 있다. 예를 들어 텍
응용 프로그램이 상태 전이를 Direct3D에게 보고함으로써, GPU는 자원 위험을 피하는 데 필요한 조치를 할 수 있다.(이를테면 모든 쓰기 연산이 완료되길 기다린 후에 자원 읽기를 시도하는 등)
상태 전이를 보고하는 부담을 프로그래머에게 부여한 것은성능때문이다. 응용 프로그램 개발자는 그러한 전이들이 언제 일어나는지 미리 알고 있다. 만일 이러한 전이를 자동으로 추적하게 한다면 성능에 부담이 생긴다.
자원 상태 전이는전이 자원 장벽(Transition resource barrier)들의 배열을 설정해서 지정한다. 배열을 사용하는 덕분에, 한 번의 API 호출로 여러 개의 자원을 전이할 수 있다. 코드에서 자원 장벽은D3D12_RESOURCE_BARRIER_DESC구조체로 서술된다.
이 코드는 화면에 표시할 이미지를 나타내는 텍스처 자원을 제시 상태(Presentation state)에서 렌더 대상 상태로 전이한다. 자원 장벽이 명령 목록에 추가됨을 주목하기 바란다. 전이 자원 방벽이라는 것을,GPU에게 자원의 상태가 전이됨을 알려주는 하나의 명령이라고 생각하기 바란다. 명령을 통해서 자원 상태 전이를 알려주는 덕분에, GPU는 이후의 명령들을 실행할 때 자원 위험 상황을 피하는 데 필요한 단계들을 밟을 수 있다.
여기저기빛의세기에관해찾아보니“Radiance”와“Irradiance”라는용어가먼저튀어나옵니다.아~몰랑몰랑~~일단한국물리학회(The Koran Physics Society)에서발간한물리학용어집을찾아보니“Radiance”는“(복사)휘도”라고번역해놓았지만아쉽게도“Irradiance”라는용어에대해서는물리학용어집에서번역어를찾을수가없군요.참고로 Journal of the Korean Physics Society는 SCI(SCI IF=0.493)입니다. 한 14~15년 전에 논문 3편 실은 기억이 있네요... 그 때는 SCI IF 1.0 이상이었는데... 쩝!! 만일 JKPS의 IF가 3.0 이상으로 올라가면 이 곳에 논문을 다시 낼 의향은 충분히 있습니다... ^_^!!
강의를하다보면 대학생들임에도 불구하고 학생들이각도가단위를가지는물리량으로착각을하는경우가 상당히 많은데각도는단위를가지지않는무차원(Dimensionless)입니다.즉,단순히숫자를나타내는것입니다.지금 내가 말하고 있는숫자가 그냥 단순히 숫자를 나타내는 것이 아니고각도의의미로사용하고 있다는 것을 상대방에게 알려주기 위해서 편의상o, rad, sr을붙여주는것에불과합니다.
......
중략..
......
제 기준에서만 말하자면 “Radiance”와“Irradiance”의 단위로부터 강도 혹은 세기(Intensity)라는 용어보다는 단위시간당(per unit time) 단위면적당(per unit area)의 에너지량이라는 용어가 더 어울릴 것 같습니다. 일종의 유속(Flux)입니다.
빈 루트 서명은 별로 유용하지 않을 수 있지만, 입력 어셈블러만 사용하는 간단한 렌더링 패스와 설명자에 액세스하지 않는 최소 꼭짓점 및 픽셀 셰이더에 사용할 수 있습니다. 또한 혼합 단계, 렌더링 대상 및 깊이 스텐실 단계에서도 빈 루트 서명을 사용할 수 있습니다.
단일 상수
API 바인딩 슬롯에서는 이 매개 변수의 루트 인수가 명령 목록 기록 시에 바인딩됩니다. API 바인딩 슬롯 수는 루트 서명의 매개 변수 순서에 따라 암시적입니다(첫 번째는 항상 0). HLSL 바인딩 슬롯에서 셰이더는 루트 매개 변수가 표시되는 것을 알 수 있습니다. 형식(위 예제의 "uint")은 하드웨어에 알려지지 않으며 이미지의 주석으로만 표시됩니다. 하드웨어는 콘텐츠로 단일 DWORD만 알 수 있습니다.
명령 목록 기록 시간에 상수를 바인딩하려면 다음과 비슷한 명령을 사용할 수 있습니다.
syntax복사
pCmdList->SetComputeRoot32BitConstant(0,seed); // 0 is the parameter index, seed is used by the shaders
루트 상수 버퍼 뷰 추가
이 예제에서는 두 개의 루트 상수와 DWORD 슬롯 2개에 상응하는 루트 CBV(상수 버퍼 뷰)를 보여 줍니다.
상수 버퍼 뷰를 바인딩하려면 다음과 같은 명령을 사용합니다. 첫 번째 매개 변수(2)는 이미지에 표시되는 슬롯입니다. 일반적으로 상수 배열이 설정된 후 셰이더의 b0에서 CBV로 사용할 수 있게 됩니다.
루트 서명의 또 다른 기능은 크기가 4개 DWORD인 float4 루트 상수입니다. 다음 명령은 4개 중에서 가운데에 있는 두 DWORD만 바인딩합니다.
syntax복사
pCmdList->SetComputeRoot32BitConstants(0,2,myFloat2Array,1); // 2 constants starting at offset 1 (middle 2 values in float4)
좀 더 복잡한 루트 서명
이 예제에서는 대부분의 항목 형식을 갖는 조밀한 루트 서명을 보여 줍니다. 설명자 테이블 중 2개(슬롯 3 및 6)에는 바인딩되지 않은 크기 배열이 포함됩니다. 여기서는 애플리케이션이 힙의 유효한 설명자에만 연결해야 합니다. 바인딩되지 않은 배열 또는 아주 큰 배열에는 하드웨어 계층 2보다 높은 리소스 바인딩 지원이 필요합니다.
2개의 정적 샘플러(루트 서명 슬롯을 요구하지 않고 바인딩됨)가 있습니다.
슬롯 9에서 UAV u4 및 UAV u5는 동일한 설명자 테이블 오프셋에서 선언됩니다. 별칭이 지정된 설명자는 이렇게 사용되며, 메모리에 한 설명자가 HLSL 셰이더에서 u4 및 u5 둘 다로 표시됩니다. 이 경우 셰이더는 fxC의 D3D10_SHADER_RESOURCES_MAY_ALIAS 옵션 또는/res_may_alias옵션으로 컴파일되어야 합니다. 설명자에 별칭을 지정하면 셰이더를 변경하지 않고도 여러 바인딩 요소에 하나의 설명자를 바인딩할 수 있습니다.
스트리밍 셰이더 리소스 뷰
이 루트 서명은 모든 SRV가 하나의 큰 배열 내/외부로 스트리밍되는 시나리오를 보여 줍니다. 실행 시 설명자 테이블은 루트 서명이 설정될 때 한 번만 설정할 수 있습니다. 그러면 첫 번째 일부 루트 인수를 통해 공급되는 상수를 통해 배열로 인덱싱하여 모든 텍스처 읽기가 수행됩니다. 단일 설명자 힙만 필요하며, 텍스처가 사용 가능한 설명자 슬롯 내/외부로 스트리밍될 때만 업데이트됩니다.
큰 힙의 설명자 오프셋은 상수 버퍼 뷰의 상수를 사용하여 셰이더에서 식별됩니다. 예를 들어, 셰이더에 재료 ID가 지정되면 해당 상수를 사용하여 하나의 큰 배열로 인덱싱함으로써 필요한 설명자(필수 텍스처 참조)에 액세스할 수 있습니다.
BSDF (Bidrectional ScatteringDistribution Function, 양반향 산란 분포 함수)
BSDF (Bidrectional ScatteringDistribution Function, 양반향 산란 분포 함수) - 우리가 일반적으로 사용하는 shading model들은 BxDF라는 함수들을 특수화한 model들 이다. BSDF는 빛이 어떤 물체에 부딪쳤을 때 얼마나 많은 빛이 반사되는가를 나타낸다. BSDF는 반사현상을 위한 Reflected 요소와 투과현상을 위한 Transmitted 요소로 나뉘며 이를 각각 BRDF(B Reflectance D F, 양반향 반사율 분포 함수)과 BTDF(B Transmittance D F 양반향 투과율 분포 함수)라고 한다.
즉, BSDF가 BRDF와 BTDF의 상위집합이다.
BRDF는 불투명한 표면에서 빛이 반사하는 방식을 정의하는 4차 함수이며 BSSRDF를 간략화 하였다. 간단하게 말하자면 BSSRDF(Bidirectional Surface Scattering Reflectance Distribution Function)은 8차함수이며, 표면 내부에서의 산란을 표현한다.
BRDF (Bidrectional Reflectance
Function, 양반향 반사율 분포 함수) - 단순하게 말하자면 입사하는 빛에 따른 반사하는 빛의 비율에 대한 함수.
- BRDF는 빛이 표면에서 어떻게 반사되는지 설명해 주는 함수, BTDF는 표면에서 어떻게 투과되는지 설명해 주는 함수.
- BRDF의 양방향성 : 만약 BRDF가 실제 물리법칙을 따르는 함수로 만들어졌다면,
입사와 반사의 방향이 바뀌어도 그 함수값은 바뀌지 않는다.
- BRDF의 비등방성 : 입사와 반사된 방향이 고정되고 표면의 법선을 축으로 표면이 회전된다면
해당 표면요소의 반사되는 비율은 변할 것이다.
즉, 표면의 재질이 들어오는 빛의 방향에 따라 다른 반사율을 가진다는 뜻이다 (알루미늄이나 옷감등).
하지만 다른 많은 재질들이 매끄러워 표면의 위치와 방향에 상관없이 같은 반사율을 가진다 (등방성 재질)
- 일반적인 BRDF를 질적으로 다른 세 개의 컴퍼넌트로 다루는 것이 편리하다.
각각을 완전 거울 반영(perfect mirror specular)반사, 완전 산란 반사(perfect diffuse reflection), 그리고 광택반사(glossy)이다.
BRDF가 왜 중요한가?
예를 들어 물을 물처럼 보이기 위해 Lighting이라는 개념이 들어가는데 렘버트의 단순함 만으로는
사실적인 결과물을 얻기 힘들다. 렘버트는 빛이 입사하는 방향에 관계없이 모든 방향으로 같은 양의 빛을
반사한다고 가정하기 때문이다. (렘버트 모델은 상수 BRDF에 의해 완벽하게 분산 표면을 표현한다).
하지만 실제로 물을 본다면 물을 바라보는 위치에 따라 물의 빛의 반사정도나 투과정도가 다르다는 것을 알 수 있다.
즉, 특정 표면이 현재 내가 바라보는 시점과 빛과의 관계에 따른 특정값을 얻을 필요가 있는 것이다.
BRDF 이론중 특별히 중요한 미립면(microfacet) 이론.
평평해 보이는 면도 사실 세밀하게 거친 면이 있기에 거울처럼 완전 평평한 물체에서 나타나는 정반사가
나타나틑게 아니라, 각 미립자간의 상호 반사나 표면 물질의 자체적인 산란으로 인해 빛의 반사가 희미해 진다.
이 미립자 BRDF 샘플의 공식을 보면 아래와 같다.
F는 프레넬, G는 기하감쇠, D는 미세면 분포함수이다.
BSSRDF (B Surface Scattering Reflectance D F, 양방향 표면 분산 반사 분포 함수)에 대한 글.
One of the most useful effects that isn’t already present in Unity is outlines.
Screenshot from Left 4 Dead. There are some scripts online that make the geometry bigger and then render it a second time, behind the first. This isn't what we want.
Artifacts from normal extrusion
What we need is a post-processing effect. We'll start by rendering only the objects we want outlined to an image. To do this, we'll use culling masks, which use layers to filter what objects should be rendered.
Make a new 'Outline' layer Then, we attach a script to our camera, which will create and render a second camera, masking everything that isn't to be outlined.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class outline : MonoBehaviour
{
public Shader DrawAsSolidColor;
public Shader Outline;
Material _outlineMaterial;
Camera TempCam;
void Start(){
_outlineMaterial = new Material(Outline);
//setup the second camera which will render outlined objects
TempCam = new GameObject().AddComponent<Camera>();
}
//OnRenderImage is the hook after our scene's image has been rendered, so we can do post-processing.
void OnRenderImage(RenderTexture src, RenderTexture dst){
//set up the temporary camera
TempCam.CopyFrom(Camera.current);
TempCam.backgroundColor = Color.black;
TempCam.clearFlags = CameraClearFlags.Color;
//cull anything that isn't in the outline layer
TempCam.cullingMask = 1 << LayerMask.NameToLayer("Outline");
//allocate the video memory for the texture
var rt = RenderTexture.GetTemporary(src.width,src.height,0,RenderTextureFormat.R8);
//set up the camera to render to the new texture
TempCam.targetTexture = rt;
//use the simplest 3D shader you can find to redraw those objects
TempCam.RenderWithShader(DrawAsSolidColor, "");
//pass the temporary texture through the material, and to the destination texture.
Graphics.Blit(rt, dst, _outlineMaterial);
//free the video memory
RenderTexture.ReleaseTemporary(rt);
}
}
<<is the bit-shift operator. This takes the bits from the number on the left, and shifts them over by the number on the right. If 'Outline' is layer 8, it will shift 0001 over by 8 bits, giving the binary value 100000000. The hardware can this value to efficiently mask out other layers with a single bitwise AND operation.
We'll also need a shader to redraw our objects. No need for lighting or anything complicated, just a solid color.
Finally, let's make a shader that passes an image through as it received it. We'll use this file for outlines later.
Shader "Custom/Post Outline"
{
Properties
{
//Graphics.Blit() sets the "_MainTex" property to the source texture
_MainTex("Main Texture",2D)="black"{}
}
SubShader
{
Pass
{
CGPROGRAM
sampler2D _MainTex;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uvs : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
//Despite only drawing a quad to the screen from -1,-1 to 1,1, Unity altered our verts, and requires us to use UnityObjectToClipPos.
o.pos = UnityObjectToClipPos(v.vertex);
//Also, the UVs show up in the top right corner for some reason, let's fix that.
o.uvs = o.pos.xy / 2 + 0.5;
return o;
}
half4 frag(v2f i) : COLOR
{
//return the texture we just looked up
return tex2D(_MainTex,i.uvs.xy);
}
ENDCG
}
//end pass
}
//end subshader
}
//end shader
Put the outline component on the camera, drag the shaders to the component, and now we have our mask!
* Render just the geometry to be outlined
Once we have a mask, let's sample every pixel nearby, and if the mask is present, add some color to this pixel. This effectively makes the mask bigger and blurry.
Shader "Custom/Post Outline"
{
Properties
{
_MainTex("Main Texture",2D)="black"{}
}
SubShader
{
Pass
{
CGPROGRAM
sampler2D _MainTex;
//<SamplerName>_TexelSize is a float2 that says how much screen space a texel occupies.
float2 _MainTex_TexelSize;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uvs : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvs = o.pos.xy / 2 + 0.5;
return o;
}
half4 frag(v2f i) : COLOR
{
//arbitrary number of iterations for now
int NumberOfIterations=19;
//turn "_MainTex_TexelSize" into smaller words for reading
float TX_x=_MainTex_TexelSize.x;
float TX_y=_MainTex_TexelSize.y;
//and a final intensity that increments based on surrounding intensities.
float ColorIntensityInRadius=0;
//for every iteration we need to do horizontally
for(int k=0;k < NumberOfIterations;k+=1)
{
//for every iteration we need to do vertically
for(int j=0;j < NumberOfIterations;j+=1)
{
//increase our output color by the pixels in the area
ColorIntensityInRadius+=tex2D(
_MainTex,
i.uvs.xy+float2
(
(k-NumberOfIterations/2)*TX_x,
(j-NumberOfIterations/2)*TX_y
)
).r;
}
}
//output some intensity of teal
return ColorIntensityInRadius*half4(0,1,1,1)*0.005;
}
ENDCG
}
//end pass
}
//end subshader
}
//end shader
* Sampling surrounding pixels, we redraw the previous image but all colored elements are now bigger
Then, sample the input pixel directly underneath, and if it's colored, draw black instead:
*Hey, now it's looking like an outline!
Looking good! Next, we'll need to pass the scene to our shader, and have the shader draw the original scene instead of black.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class outline : MonoBehaviour
{
public Shader DrawAsSolidColor;
public Shader Outline;
Material _outlineMaterial;
Camera TempCam;
void Start()
{
_outlineMaterial = new Material(Outline);
TempCam = new GameObject().AddComponent<Camera>();
}
void OnRenderImage(RenderTexture src, RenderTexture dst){
TempCam.CopyFrom(Camera.current);
TempCam.backgroundColor = Color.black;
TempCam.clearFlags = CameraClearFlags.Color;
TempCam.cullingMask = 1 << LayerMask.NameToLayer("Outline");
var rt = RenderTexture.GetTemporary(src.width,src.height,0,RenderTextureFormat.R8);
TempCam.targetTexture = rt;
TempCam.RenderWithShader(DrawAsSolidColor, "");
_outlineMaterial.SetTexture("_SceneTex", src);
Graphics.Blit(rt, dst, _outlineMaterial);
RenderTexture.ReleaseTemporary(rt);
}
}
Shader "Custom/Post Outline"
{
Properties
{
_MainTex("Main Texture",2D)="black"{}
_SceneTex("Scene Texture",2D)="black"{}
}
SubShader
{
Pass
{
CGPROGRAM
sampler2D _MainTex;
sampler2D _SceneTex;
float2 _MainTex_TexelSize;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uvs : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvs = o.pos.xy / 2 + 0.5;
return o;
}
half4 frag(v2f i) : COLOR
{
//if something already exists underneath the fragment, discard the fragment.
if(tex2D(_MainTex,i.uvs.xy).r>0)
{
return tex2D(_SceneTex,i.uvs.xy);
}
int NumberOfIterations=19;
float TX_x=_MainTex_TexelSize.x;
float TX_y=_MainTex_TexelSize.y;
float ColorIntensityInRadius=0;
for(int k=0;k < NumberOfIterations;k+=1)
{
for(int j=0;j < NumberOfIterations;j+=1)
{
ColorIntensityInRadius+=tex2D(
_MainTex,
i.uvs.xy+float2
(
(k-NumberOfIterations/2)*TX_x,
(j-NumberOfIterations/2)*TX_y
)
).r;
}
}
//this value will be pretty high, so we won't see a blur. let's lower it for now.
ColorIntensityInRadius*=0.005;
//output some intensity of teal
half4 color= tex2D(_SceneTex,i.uvs.xy)+ColorIntensityInRadius*half4(0,1,1,1);
//don't want our teal outline to be white in cases where there's too much red
color.r=max(tex2D(_SceneTex,i.uvs.xy).r-ColorIntensityInRadius,0);
return color;
}
ENDCG
}
//end pass
}
//end subshader
}
//end shader
여기까지는 평균내어 블러 처리 한것인데 하단 부터는 가우시안 블러를 적용하여 블러 처리 한 부분으로 변경한 것이다
This is almost where we want it, but we do have one more step! There are some problems with this outline right now. You might notice it looks a little off, and that the outline doesn't contour the corners nicely.
There's also the issue of performance - at 19x19 samples per pixel for the outline, this isn't a big issue. But what if we want a 40 pixel outline? 40x40 samples would be1600 samples per pixel!
We're going to solve both of these issues with a gaussian blur. In a Gaussian blur, a kernel(a weight table) is made and colors are added equal to the nearby colors times the weight.
Gaussian blurs look nice and natural, and they have a performance benefit. If we start with a 1-dimensional Gaussian kernel, we can blur everything horizontally,
and then blur everything vertically after, to get the same result.
This means our 40 width outline will only have 80 samples per pixel.
//http://haishibai.blogspot.com/2009/09/image-processing-c-tutorial-4-gaussian.html
public static class GaussianKernel
{
public static float[] Calculate(double sigma, int size)
{
float[] ret = new float[size];
double sum = 0;
int half = size / 2;
for (int i = 0; i < size; i++)
{
ret[i] = (float) (1 / (Math.Sqrt(2 * Math.PI) * sigma) * Math.Exp(-(i - half) * (i - half) / (2 * sigma * sigma)));
sum += ret[i];
}
return ret;
}
}
The sigma is an arbitrary number, indicating the strength of the kernel and the blur.
The size is to stop generating tiny numbers in an infinite kernel. Technically, Gaussian kernels would have no zeroes, so we want to cut it off once it isn't noticeable. I recommend the width be 4 times the sigma.
We calculate the kernel, and pass it to the shader. I have it set to a fixed size, but you could pass a kernel texture instead to handle varying widths.
using System;
using UnityEngine;
public class outline : MonoBehaviour
{
public Shader DrawAsSolidColor;
public Shader Outline;
Material _outlineMaterial;
Camera TempCam;
float[] kernel;
void Start()
{
_outlineMaterial = new Material(Outline);
TempCam = new GameObject().AddComponent<Camera>();
kernel = GaussianKernel.Calculate(5, 21);
}
void OnRenderImage(RenderTexture src, RenderTexture dst)
{
TempCam.CopyFrom(Camera.current);
TempCam.backgroundColor = Color.black;
TempCam.clearFlags = CameraClearFlags.Color;
TempCam.cullingMask = 1 << LayerMask.NameToLayer("Outline");
var rt = RenderTexture.GetTemporary(src.width, src.height, 0, RenderTextureFormat.R8);
TempCam.targetTexture = rt;
TempCam.RenderWithShader(DrawAsSolidColor, "");
_outlineMaterial.SetFloatArray("kernel", kernel);
_outlineMaterial.SetInt("_kernelWidth", kernel.Length);
_outlineMaterial.SetTexture("_SceneTex", src);
//No need for more than 1 sample, which also makes the mask a little bigger than it should be.
rt.filterMode = FilterMode.Point;
Graphics.Blit(rt, dst, _outlineMaterial);
TempCam.targetTexture = src;
RenderTexture.ReleaseTemporary(rt);
}
}
We make a pass with the kernel, sampling every pixel and adding our pixel's value by the kernel times the sampled value. Call GrabPass to grab the resulting texture, and then make a second pass. The second pass does the vertical blur.
Shader "Custom/Post Outline"
{
Properties
{
_MainTex("Main Texture",2D)="black"{}
_SceneTex("Scene Texture",2D)="black"{}
_kernel("Gauss Kernel",Vector)=(0,0,0,0)
_kernelWidth("Gauss Kernel",Float)=1
}
SubShader
{
Pass
{
CGPROGRAM
float kernel[21];
float _kernelWidth;
sampler2D _MainTex;
sampler2D _SceneTex;
float2 _MainTex_TexelSize;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uvs : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvs = o.pos.xy / 2 + 0.5;
return o;
}
float4 frag(v2f i) : COLOR
{
int NumberOfIterations=_kernelWidth;
float TX_x=_MainTex_TexelSize.x;
float TX_y=_MainTex_TexelSize.y;
float ColorIntensityInRadius=0;
//for every iteration we need to do horizontally
for(int k=0;k<NumberOfIterations;k+=1)
{
ColorIntensityInRadius+=kernel[k]*tex2D(
_MainTex,
float2
(
i.uvs.x+(k-NumberOfIterations/2)*TX_x,
i.uvs.y
)
).r;
}
return ColorIntensityInRadius;
}
ENDCG
}
//end pass
//retrieve the texture rendered by the last pass, and give it to the next pass as _GrabTexture
GrabPass{}
//this pass is mostly a copy of the above one.
Pass
{
CGPROGRAM
float kernel[21];
float _kernelWidth;
sampler2D _MainTex;
sampler2D _SceneTex;
sampler2D _GrabTexture;
float2 _GrabTexture_TexelSize;
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uvs : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvs = o.pos.xy / 2 + 0.5;
return o;
}
float4 frag(v2f i) : COLOR
{
float TX_x=_GrabTexture_TexelSize.x;
float TX_y=_GrabTexture_TexelSize.y;
//if something already exists underneath the fragment, draw the scene instead.
if(tex2D(_MainTex,i.uvs.xy).r>0)
{
return tex2D(_SceneTex,i.uvs.xy);
}
int NumberOfIterations=_kernelWidth;
float4 ColorIntensityInRadius=0;
for(int k=0;k < NumberOfIterations;k+=1)
{
ColorIntensityInRadius+=kernel[k]*tex2D(
_GrabTexture,
float2(i.uvs.x,
1-i.uvs.y+(k-NumberOfIterations/2)*TX_y)
);
}
//output the scene's color, plus our outline strength in teal.
half4 color= tex2D(_SceneTex,i.uvs.xy)+ColorIntensityInRadius*half4(0,1,1,1);
return color;
}
ENDCG
}
//end pass
}
//end subshader
}
//end shader
The finished effect!
결과를 보면 좀더 밖으로 빠질 수록 자연스럽게 블러처리가 된 것을 볼 수 있다
Where to go from here
Try adding multiple colors. You could also sample the added color from another texture to make a neat pattern. Regenerate the kernel when the resolution changes to get an effect that scales up.
Help!
My outline looks blocky. Most likely, your sigma is too high and your width too low, so the kernel is cut off earlier than it should be. Or, your kernel isn't being generated or used correctly.
Performance problems/crashes Are you using RenderTexture.GetTemporary() and RenderTexture.ReleaseTemporary()? Unity doesn't release normal RTs for some reason, so you need to use the temporary methods. Check the profiler to see what's up.
Something is upside-down/too big/in the corner of the screen Unity has inconsistenciesand so does OpenGL. There is a pragma,#if UNITY_UV_STARTS_AT_TOP, that can be used.
Something isn't working. Use the frame debugger to see what's happening to your graphics. Otherwise, check for any errors.
기존의 라이트 프로브의 단점이었던 커다란 오브젝트의 라이트 프로브 문제점이 해결되었습니다. 예를 들어 대형 몬스터를 표현할때 기존의 라이트 프로브로는 몬스터 오브젝트 전체가 하나의 라이트 프로브만 영향받는 문제가 있어서 주변의 라이팅과 어울리지 않았지만 이번에 들어간 라이트 프로브 프록시 볼륨을 이용하면 훨씬 리얼한 라이팅 효과를 얻을수 있습니다.
애니메이션은 움직이는 그림을 뜻하는데 그림이 살아 움직이는 건 아니고 여러장의 장면을 빠르게 보여줌으로써 움직이는 것처럼 보이게 한다 키 프레임의 경우 보통 중요한 컷을 말한다
애니메이션의 제작, 원화와 동화
애니메이션이 여러장의 그림을 나열하는 것은 알고 있을 것이다 그런데 그러한 그림을 노트에서 페이지 넘겨서 움직이는 졸라맨 그리듯이 무작정 순서대로 그리진 않는다 먼저 원화라고 Key Animation을 그린 후 그것을 연결하는 동화를 그린다 예를들어 공이 위에서 아래로 떨어지는 애니를 만든다면 원화가는 어디에서 공이 떨어질지에 대한 그림을 그리고 어느 바닥에 도착할지에 대한 그림을 그린다 그럼 총 두 장의 키 애니메이션이 만들어진다. 하지만 이 두 장으론 애니가 성립되지 않는다. 그것을 위해 동화맨들이 두 장을 바탕으로 중간에 연결되는 그림을 왕창 그리는 것이다. 만약 원화가가 한 장은 개를 그려놓고 한 장은 우주를 그려놓으면 동화맨으로썬 이 두 장 사이에 뭘 그리라는 건지 혼란에 빠질 것이다 그러므로 키 애니메이션은 거의 모든 중요정보를 담고있는 말 그대로 그 장면 장면의 키가 되어서 누가봐도 중간 장면을 예상할 수 있어야한다
키 프레임
애니메이션과 비교
3D 프로그램에서 동영상편집, 그리고 플래시에 이르기까지 키 프레임은 거의 일상적 용어이며 위의 키 애니메이션 개념과 다르지 않다 프로그램상에서 원화가는 바로 당신을 뜻하며 동화가는 프로그램이다 예를들어 위의 공애니의 경우 공을 일정위치에 놓고 키 프레임을 하나 찍는다 그리고 또 다른 위치에 놓고 10초뒤 키 프레임을 하나 찍으면 그 중간 과정을 프로그램이 알아서 생성한다. 그럼으로써 애니메이션이 완성된다
컷 추가
프로그램상에서 타임라인의 기본은 노컷이다 완전 통짜로 되어 있어서 10초짜리 타임라인이라면 키 프레임이 들어가지 않는다면 한 장면을 10초간 보여줄 뿐이다. 여기서 키 프레임이 바로 컷의 역할을 한다 애니메이션이란게 성립할려면 최소한 2장면은 필요하다. 그래서 키 프레임이 반드시 2개 이상이 되었을때야 비로서 애니메이션이 성립하게 된다
속성
일반 애니메이션의 경우 움직임이 키의 요소지만 프로그램에선 변할 수 있는 것들은 모조리 키로 만들 수 있다 예를들면 불투명도에서 소리에 이르기까지 말이다 1초에서 불투명도를 100%로 놓은 키와 10초에서 불투명도를 0%로 놓은 키가 있다면 10초로 이르는 사이 서서히 투명해지는 애니메이션을 만들 수 있고 1초에서 볼륨을 0%로 해놓은 키와 10초에서 100%로 놓은 키가 있으면 볼륨이 서서히 커지는 효과의 애니메이션(?)을 만들 수 있는 것이다 이때 이것들은 하나의 키 프레임에서 처리하진 않고 속성들이 고유의 키 프레임을 갖는다 위와 같은 경우 키 프레임이 4개가 된다. 속성이 많으면 많을 수록 키 프레임 수도 팍팍 늘어나는데 사실 그렇게까지 많이 쓰진 않으며 각 키 프레임도 드래그로 조정 가능하게 하는 것은 기본이고 snap 기능, 복사 등등 최대한 노가다를 적게하도록 만들어져 있다. 그래도 노가다
그래프
키 프레임 자체는 잘 보인다. 그게 찍혀있으면 거기에 컷이 한장 있다는 소리고. 키 프레임을 클릭하면 수치창이 나와서 수치를 조정해줄 수 있다. 하지만 전체적인 움직임은 좀 알기 어려운 점이 있다 그래서 그러한 움직임을 직관적으로 보여주자고해서 나온 것이 그래프이다
와… 직관적이네요???? 처음보면 무슨 개풀씨나락 까먹는 직관이냐고 생각할지 모르겠지만 우리의 수학이 더러운 주입식이라 그렇지 실제 사용에서 그래프는 매우 편리한 도구다. 저것만 봐도 쪼금이라도 키 프레임 지식이있다면 바로 초록색 수직라인은 현재 타임이고 검은 네모들은 키 프레임이고 각각의 색색 곡선들은 저위의 사각형 상자에 나와있는 방향화살표랑 색깔이 매치되는 것을 알아볼 것이다 예를들면 파란색이 저 상자의 위쪽 화살표니까 10초에서는 -7쯤 위치에 있다가 100초에 다가설수록 곡선형으로 수치가 증가해 7에 다다른다 즉 상자가 10초에서 100초가 되면서 위로 천천히 오르다가 점점 빠르게 오르다가 마지막에 가서는 완만하게 오르게 되는 것을 그래프로 한눈에 알 수 있다 보통은 그러한 움직임을 알려면 직접 플레이 해보는 수밖에 없지만 이처럼 그래프는 어떻게 진행되는 지를 한장으로 보여주는데 반하지 않을 수가 없다 (참고로 본인은 수학은 잼병에 그래프는 인류의 적이라고 생각했던 사람이다 한국교육 ㅅㅂ) 왠만한 프로그램에서는 타임라인을 잘 뒤져보면 그래프를 보여주는 옵션이 있는데 거기에 익숙해지면 작업이 엄청 편해지게 되는 것은 말할 것도 없다
3D 애니메이션
본인은 3D 애니메이션이라 하면 엄청나다는 생각만 가지고 있었고 그 실체는 역시 엄청날 것이다 라고만 생각했다 즉, 엄청난 거라고 미리 겁먹고 생각할 의지가 생기지 않았다 사실 나랑 관계없는 세계니 관여할 생각도 없었고 근데 재수없게도 약간 관계가 생겼다 그래서 그 엄청난 세계에 발디뎠는데.. 알고보니 사실 똑같았다 한 장면과 다른 장면을 찍어놓으면 프로그램이 알아서 중간과정 만드는거 위랑 하나도 안다르다. 리깅과 핸들을 만들고 마지막에 애니를 재생하면서 간신히 알아낸거지만..ㅡ,.ㅡ; 단지 똑같긴 한데 보통 2D랑 쪼끔 다른건 원하는 장면을 위한 포즈를 취하게 하는게 여간 힘든게 아니였다는랑 그걸 위해서 리깅과 핸들 같은 생소한 개념이 들어가서 헷갈렸다는 것 정도다.
결론
결국 키 프레임을 만드는 것은 변화 전과 변화 후의 장면을 만드는 것이다 우린 작곡가 처럼 오선지에 음표를 넣듯이 타임라인에 키프레임을 넣어주면 된다 1초에 도를 넣고 10초에 솔을 넣으면 컴터가 알아서 도레미파솔 이라고 연주해주는 것이다 물론 레 미 파 솔도 직접 키프레임으로 찍어 넣을 수 있다. 노가다가 그렇게 하고 싶다면
여타 프로그래밍이 그렇듯이 본질은 사실 단순한데 그걸 편하게 한다고 덕지덕지 붙이고 보니 실체는 안보이고 복잡한 외부기능들만 보여서 어려워지는 것이다. 개념만 이해하고 나면 이제 어떤 프로그램이더라도 애니메이션을 만드는데 각 프로그램의 세세한 것들만 좀 다를 뿐이지 큰 장애는 없을 것이다
Direct surface scattering (left), plussubsurface scattering(middle), create the final image on the right.
Example of Subsurface scattering made inBlendersoftware.
Subsurface scattering(or SSS) is a mechanism oflighttransport in which light penetrates the surface of a translucent object, isscatteredby interacting with the material, and exits the surface at a different point. The light will generally penetrate the surface and be reflected a number of times at irregular angles inside the material, before passing back out of the material at an angle other than the angle it would have if it had been reflected directly off the surface. Subsurface scattering is important in3D computer graphics, being necessary for the realistic rendering of materials such asmarble,skin, andmilk.
Most materials used inreal-time computer graphicstoday only account for the interaction of light at the surface of an object. In reality, many materials are slightly translucent: light enters the surface; is absorbed, scattered and re-emitted — potentially at a different point. Skin is a good case in point; only about 6% of reflectance is direct, 94% is from subsurface scattering.[1]An inherent property of semitransparent materials is absorption. The further through the material light travels, the greater the proportion absorbed. To simulate this effect, a measure of the distance the light has traveled through the material must be obtained.
One method of estimating this distance is to use depth maps[2], in a manner similar toshadow mapping. The sceneis rendered from the light's point of view into a depth map,so that the distanceto the nearest surface is stored. Thedepth mapis then projected onto it using standardprojective texture mappingand the scene re-rendered.In this pass, when shading agiven point,the distancefrom the light at thepointthe rayenteredthe surfacecan be obtainedbya simple texture lookup.Bysubtractingthis valuefromthe pointthe rayexitedthe objectwecan gather an estimate of the distancethe lighthas traveled through the object.
The measure of distance obtained bythis method can be used in several ways. Onesuchwayis to useit to indexdirectly intoan artistcreated 1D texture that falls offexponentiallywith distance.Thisapproach, combined with other more traditional lighting models, allows the creation of different materials suchasmarble,jadeandwax.
Potentially, problems can arise if modelsare not convex, butdepth peeling[3]can be used to avoid the issue. Similarly, depth peeling can be used to account for varying densities beneath the surface, such as bone or muscle, to give a more accurate scattering model.
As can be seen in the image of the wax head to the right, light isn’t diffused when passing through object using this technique; back features are clearly shown. One solution to this is to take multiple samples at different points on surface of the depth map. Alternatively, a different approach to approximation can be used, known as texture-space diffusion.
As noted at the start of the section, one of the more obviouseffectsof subsurfacescattering is a general blurringof the diffuse lighting. Rather than arbitrarilymodifying the diffuse function, diffusion can be more accuratelymodeled by simulating it in texture space. This technique was pioneered in rendering faces inThe Matrix Reloaded,[4]but has recently fallen into the realm of real-time techniques.
The method unwraps the mesh of an object using a vertex shader, first calculating the lighting based on the original vertex coordinates. The vertices are then remapped using the UVtexture coordinatesas the screenposition of the vertex, suitable transformed from the [0, 1] range of texture coordinates to the [-1, 1] range of normalized device coordinates. By lighting the unwrapped mesh in this manner, we obtain a 2D image representing the lighting on the object, which can then be processed and reappliedto the model as alight map. To simulate diffusion, the light map texture can simply be blurred. Rendering the lighting to a lower-resolution texture in itself provides a certain amount of blurring. The amount of blurring required to accurately model subsurface scattering in skin is still under active research, but performing only a single blur poorly models the true effects.[5]To emulate the wavelength dependent nature of diffusion, the samples used during the (Gaussian) blur can be weighted by channel. This is somewhat of an artistic process. For human skin, the broadest scattering is in red, then green, and blue has very little scattering.
A major benefit of this method is its independence of screen resolution; shading is performed only once per texel in the texture map, rather than for every pixel on the object. An obvious requirement is thus that the object have a good UV mapping, in that each point on the texture must map to only one point of the object. Additionally, the use of texture space diffusion provides one of the several factors that contribute to soft shadows, alleviating one cause of the realism deficiency ofshadow mapping.
밉맵을 사용하다보면 정말 카메라를 가까이 들이밀지 않는한 텍스처의 디테일이 흐릿해 보이는 경우가 흔히 발생합니다. 보통 다음과 같은 방법들로 해결합니다.
흔히 쓰는 해결법 1 - 텍스처 크기 늘리기
밉레벨이 낮아질수록 텍스처 크기가 절반씩 줄어드는 거니 젤 높은 디테일의 밉멥 텍스처크기를 크게 키워주면 그만큼 흐려지는 현상이 덜합니다. 하지만 메모리를 많이 잡아먹는다는 단점이 있어서 정 필요한 때만 제한적으로 사용하곤 합니다. (왜 흔히 라고 한거지 그럼 -_-)
흔히 쓰는 해결법 2 - 밉맵 바이어스 쓰기
이걸 고치기 위해 흔히 쓰는 해결법은 밉맵 bias를 조절하는 방법입니다. 샘플러스테이트에서 정해주는 방법도 있고 셰이더에서 해주는 법도 있습니다. 뭐든 나쁜 방법은 아니고 가장 널리 쓰는 방법인데 여러가지 단점이 있습니다
고디테일의 텍스처(크기가 큼)를 더 많이 쓰도록 bias를 주므로 텍스처 캐쉬의 성능저하 (그만큼 넣어야할 데이터가 많으니)
밉맵이 원래 해결하려고 하는 거리에 따른 애일리어싱 문제가 쉽게 더 생긴다.
모든 경우에 적당히 잘 동작하는 bias 값을 찾기가 쉽지 않다.
흔히 쓰는 해결법 3 - 밉맵 생성시 사용하는 필터 다르게 사용하기
보통 bilinear 필터를 사용해서 밉맵을 만드는게 일반적입니다. 그럼 그냥 주변에 있는 이웃 픽셀들 2x2개 모아다가 균등하게 혼합하는게 전부입니다. 이 외에 kaiser 필터 등을 사용하면 좀더 낫다고 해서 그렇게 하는 사람들을 봤지만... 개인적으로는 별 효과가 없다고 생각합니다.
제 해결법 - sharpening filter
생각보다 매우 간단합니다. 그냥 밉맵 텍스처에 sharpening 필터 한번 더 먹여주면 됩니다. -_- 사실 밉맵들이 흐릿해 보이는 이유가 bilinear 필터링만 쓰면 그냥 경계를 뭉게버린다는건데 여기다 sharpening 필터 한번 먹여주면 경계부분은 다시 적당히 또렷하게 살아나거든요...
오프라인 프로세스라 게임성능에 지장도 없고... 흔히 쓰는 해결법 2에서 말씀드렸던 단점들도 없습니다.. 그냥 밉맵만드실때 이런순서로 만들어 주시면 됩니다.
Hi All, From my understanding the derivative instructions ddx,ddy,fwidth etc. Use 2x2 blocks of pixels to compute the derivative. Can someone please explain: 1.) What happens on triangle boundaries where a triangle only intersects one or 2 pixels of the 2x2 region 2.) The exact formula for the derivatives for each pixel in the 2x2 block. For example, given the block: a b c d the ddx derivative for pixel a/b/c/d could be ((b-a)+(d-c))/2 or it could be b-a for a/b and d-c for c/d. I have scoured the net and haven't found a good reference for this. I have found that if I draw a box and take the derivative of a complicated calculated value that *sometimes* i get artifacts on the border of the box's polygons. I imagine this is due to how it is computed on polygon boundaries. Any help would be appreciated. Cheers! Eric [Edited by - WizardOfOzzz on January 9, 2008 8:16:57 PM]
Original post by WizardOfOzzz From my understanding the derivative instructions ddx,ddy,fwidth etc. Use 2x2 blocks of pixels to compute the derivative.
That's correct.
Quote:
Original post by WizardOfOzzz 1.) What happens on triangle boundaries where a triangle only intersects one or 2 pixels of the 2x2 region
The shader is evaluated for all of the pixels in the 2x2 "quad", even if some of them fall outside of the triangle.
Quote:
Original post by WizardOfOzzz 2.) The exact formula for the derivatives for each pixel in the 2x2 block. For example, given the block: a b c d
Original post by WizardOfOzzz I have found that if I draw a box and take the derivative of a complicated calculated value that *sometimes* i get artifacts on the border of the box's polygons.
This problem can indeed occur if you're computing some function that is undefined or otherwise not well-behaved outside of the normal triangle interpolant range. Similar problems can occur with multi-sampling, but there you can often solve them using "centroid" interpolation. I don't recall exactly how derivatives interact with centroid interpolation but I wouldn't be surprised if it is implementation-dependent.
컴퓨터로 생성된 이미지에 사실성을 향상시키기 위한 방법들은 매우 많다. 그런 방법들 중 하나가 물체의 라이팅 방정식을 이용해 값을 구할때 그림자 효과를 계산하는 방법이다. 실시간 컴퓨터 그래픽에서 유용한 그림자 기법들은 매우 많으며, 그러한 각각 기법들은 장단점을 모두 보인다. 일반적으로, 그림자 기법들은 그림자 품질과 실행 효율 사이의 균형을 취하려 노력한다.
그런 기법들중 하나가 바로 SSAOScreen Space Ambient Occlusion라고 불리는 기법이다. 이 기법은 Martin Mittring이 2007 SIGGRAPH 컨퍼런스에서 발표한 논문 "Finding Next Gen"에서 처음 논의되었다. 이 알고리즘의 기본 컨셉은 전역 조명 과정Ambient lighting term을 장면의 한 점이 얼마나 차폐되는지를 구하는 기반으로 바꾸는 것이다. 이 방법이 AO의 컨셉을 처음으로 이용하는 기법은 아니지만, 이후의 장chapter에서 보게 되겠찌만 SSAO는 꽤나 현명한 추정과단순함으로 매우 높은 성능을 유지하면서도 만족할만한 결과를 보여준다.
그림 1. SSAO를 이용한 샘플 렌더링 화면
이 번 장에서는, SSAO의 배경이 되는 이론을 살펴보고, Direct3d 10 파이프라인을 이용하여 이 기법을 구현하는 방법을 제공하며, 성능과 품질의 향상을 위해 구현 단계에서 사용할 수 있는 파라미터들에 대하여 논의할 것이다. 마지막으로, 주어진 구현을 이용한 데모에 대하여 논의하여 이를 통해 알고리즘의 성능에 대해서 몇 가지 사항을 알리고자 한다.
알고리즘 이론
이제 우리는 SSAO의 유래에 관한 배경지식에 대해서 살펴보았으므로, 기법에 대하여 더욱 자세하게 살펴볼 준비가 되었다. SSAO는 단 하나의 단순한 명제를 사용함으로써 이전의 기법들과는 다른점을 보인다: SSAO는 이전에 레스터화되기 이전의 장면 정보를 사용하는 대신 현재 장면에서 주어진 점의 차폐의 양을 구하는데 오직 깊이 버퍼만을 이용한다. 이를 위해 깊이 버퍼를 직접적으로 사용하는 방법이나 깊이 정보를 보관하기 위해 특별한 렌더타겟을 생성하는 방법 모두 가능하다. 깊이 정보는 주어진 점의 주변점을 구하는 즉시 세밀하게 조사하여 그 점이 얼마나 차폐되었는지 계산하는데 사용한다. 차폐 인수occlution factor은 한지점에 얼마만큼의 전역광을 적용할것인지, 그 양을 조절하는데 사용한다.
SSAO 알고리즘에서 사용하는 차폐 인수을 생성하는 방법은 논리적으로 두 단계 과정으로 이루어져 있다. 먼저 우리는 장면의 현재 시점을 기준으로 깊이 텍스처를 생성해야하고, 이 깊이 텍스처를 이용하여 현 장면의 최종 화면에서 각 픽셀의 차폐 정도를 결정한다. 이 과정은 아래의 그림 2에서 볼 수 있다.
그림2. SSAO 알고리즘의 개요
깊이 버퍼만을 사용하는 이 단순함은 성능과 품질면에서 몇가지 효과가 있다. 장면 정보가 렌더타겟으로 레스터화 하는 경우, 장면을 나타내는데 사용하는 정보가 3차원에서 2차원으로 줄어들게 된다. 데이터 축소는 매우 효율적인 명령을 제공하는 표준 GPU 파이프라인 하드웨어를 통해 수행된다. 또한 장면의 전체 차원을 줄임으로, 2D 장면 표현을 장면의 가시 영역으로 한정하여 이 프레임에 보이지 않는 물체에 대한 불필요한 연산을 제거할 수 있게 된다.
데이터 축소는 차폐 인수을 계산하는데 있어서 확실한 성능상의 이득을 제공하지만, 동시에 연산시에 필요한 정보 또한 제거한다. 이것은 우리가 계산으로 얻은 차폐 인수 값이 꼭 정확한 값이 아닐 수 있음을 의미하지만, 이후의 장에서 보게 되겠지만 전역 조명 과정은 근사치 일지라도 훌륭하게 장면의 현실감을 부여해 준다.
텍스처에 장면의 깊이 정보를 저장한 뒤, 깊이 값의 직접 인접에 기반하여 각 점이 얼마나 차폐되었는지 결정해야 한다. 이 연산은 다른 AO 기법들과 꽤나 비슷하다. 그림 3에 보이는 바와같이 깊이 버퍼를 고려해야한다 :
그림 3. 뷰view 방향이 아래쪽으로 향한 샘플 깊이 버퍼
이상적으로 주어진 점을 중심으로 둘러싼 반지름 r인 구를 생성하고, 그 구와 깊이 버퍼에 있는 물체와 교차하는 부피를 구하기 구를 따라 통합해 나간다. 이 컨셉은 여기 그림 4에서 볼 수 있다.
그림 4. 차폐를 계산하기 위한 이상적인 샘플링 구
물론, 렌더된 이미지의 모든 픽셀에서 복잡한 3D 통합을 수행하는 것은 전적으로 비효율적이다. 대신, 이 구의 부피 내에 존재하는 3D 샘플링 커널을 이용하여 이 구를 추정할 것이다. 샘플링 커널은 명시된 지점의 깊이 버퍼를 살펴, 그 각 가상의 점이 깊이 표면에 의해 가려지는지 결정하는데 이용된다. 샘플링 커널의 예시는 그림 5에 나타나 있으며, 우리의 표면점에 어떻게 적용해야 하는지도 보여준다.
그림 5. 단순화한 샘플링 커널과 깊이 버퍼 내에의 위치
차폐 인수는 차폐가 발생한 샘플링 커널의 합으로 구할 수 있다. 구현 부분에서 살펴보겠지만, 이 개별적인 계산은 카메라부터의 거리, 차폐 지점과 기준점 간의 거리, 아티스가 지정한 비례축소 인수 등의 몇가지 다른 인수들에 따라 더 민감하게, 혹은 덜 민감하게 할 수 있다.
구현
알고리즘이 어떻게 기능하는지 이해했따면, 이제 구현하는 방법에 대해 논의해 보자. 알고리즘 이론 파트에서 논의했듯이, 장면의 깊이 정보가 담긴 소스가 필요하다. 간단하게 하기 위해, 우리는 깊이 정보를 저장하기 위해 서로다른 부동소수 버퍼를 사용할 것이다. 비록 조금 더 복잡할지는 모르겠지만, 더 효율적인 기법은 장면의 깊이 값을 얻기 위해 z-버퍼를 이용할 것이다. The following code snippet shows how to create the floating point buffer, as well as the render target and shader resource views that we will be binding it to the pipeline with:
깊이 정보를 생성한 후, 다음 단계는 최종 장면 내 각 픽셀의 차폐 인수를 계산하는 것이다. 이 정보는 두번째 단일 성분 부동소수 버퍼에 저장할 것이다. 이 버퍼를 생성하기 위한 코드는 부동소수 깊이 버퍼를 생성하기 위해 사용한 코드와 매우 유사하므로, 페이지 길이를 줄이기 위해 생략한다.
이제 두 버퍼가 모두 생성되었으므로, 각 버퍼에 저장할 정보를 생성하는 부분을 상세히 살펴보자. 깊이 버퍼는 뷰 공간의 깊이값을 기본적으로 저장한다. 절단 공간 깊이clip space depth 대신 선형 뷰 공간 깊이linear view space depth를 사용하여 투시 투영perspective projection으로 인한 깊이 범위 왜곡을 방지한다. 뷰 공간 깊이는 입력 버텍스와 월드뷰world-view 행렬을 곱해서 구할 수 있다. 그런뒤 뷰 공간 깊이는 단일 성분 버텍스 속성으로 픽셀 셰이더로 전달된다.
저장된 정규화된 깊이 정보를 가지고, 각 픽셀의 차폐 인수를 생성하기 위해 깊이 버퍼를 셰이더 리소스로 바인딩하고 각 차폐 버퍼에 저장한다. 차폐 버퍼 생성은 단일 전체 스크린 화면을 렌더링함으로써 초기화한다. 전체 스크린 화면의 버텍스들은 단지 단일 2-성분 속성을 가지고 있으며, 이는 깊이 버퍼 내의 자신의 위치에 대응하는 텍스처 좌표를 나타내고 있다. 버텍스 셰이더는 단순히 이 파라미터들을 픽셀 셰이더에 전달만 한다.
필셀 셰이더는 3D 샘플링 커널의 형태를 3-성분 벡터의 배열로 정의하는 것으로 시작한다. 벡터의 길이는 [ 0, 1 ] 범위 사이에서 다양히 변하며 각 차폐 테스트에서 약간의 변화를 주게 된다.
01.constfloat3avKernel[8] ={
02.normalize( float3( 1, 1, 1 ) ) * 0.125f,
03.normalize( float3( -1,-1,-1 ) ) * 0.250f,
04.normalize( float3( -1,-1, 1 ) ) * 0.375f,
05.normalize( float3( -1, 1,-1 ) ) * 0.500f,
06.normalize( float3( -1, 1 ,1 ) ) * 0.625f,
07.normalize( float3( 1,-1,-1 ) ) * 0.750f,
08.normalize( float3( 1,-1, 1 ) ) * 0.875f,
09.normalize( float3( 1, 1,-1 ) ) * 1.000f
10.}
다음으로, 픽셀 셰이더는 텍스처 룩업 내에서 샘플링 커널을 반사시키기 위한 렌덤 벡터를 찾는다. 이것은 사용한 샘플링 커널을 이용해 매우 다양한 변화를 줄 수 있으며, 이는 적은 수의 차폐 테스트를 하더라도 높은 수준의 결과를 제공할 수 있게 해준다. 이는 차폐를 계산하기 위한 깊이를 효과적으로 "흐트려jitter" 주어, 우리가 현재 픽셀의 주변 공간을 언더샘플링 한다는 사실을 숨겨준다.
이제 픽셀 셰이더가 현제 픽셀의 깊이값을 바탕으로 샘플링 커널에 적용하기 위한 확대/축소값을 계산할 것이다. 이 픽셀의 깊이값은 깊이 버퍼로부터 읽어들여 근거리 평면과 원거리 평면간의 거리를 곱함으로 뷰 공간으로 도로 확장된다. 그런 뒤 샘플링 커널의 x 와 y 성분을 위한 확대/축소값을 계산하는데, 이는 샘플링 커널의 희망 반지름( 미터 단위 )을 픽셀의 깊이값( 미터 단위 )로 나누면 된다. 이는 개별 샘플을 찾기위해 사용되는 텍스처 좌표를 확대/축소한다. z 성분의 확대/축소값은 희망 커널 반지름을 근거리 평면과 원거리 평면간의 거리로 나누어 구할 수 있다. 이는 모든 깊이값의 비교를 우리가 깊이 버퍼에 저장한것과 같은 정규화된 깊이 공간에서 이루어지도록 하게 해준다.
커널 확대/축소값이 구해지면, 개별 차폐 테스트를 수행할 수 있게 된다. 이것은 for 루프 내에서 샘플링 커널이 가르키는 각 점들을 계속해서 반복하는 것으로 이루어진다. 현재 픽셀의 텍스처 좌표는 렌덤하게 반사된 커널 벡터의 x와 y 성분을 통해 차감 계산되고 새 좌표에서 깊이값을 살펴보기 위해 사용된다. 이 깊이 값은 커널 벡터의 z 성분을 통해 차감 계산되고 현재 픽셀의 깊이와 비교된다.
깊이값의 델타값( = 차이값 )은 어플리케이션의 선택가능한 범위값으로 정규화된다. 이것은 깊이값 차이의 관계 등급을 결정하기 위한 인수를 생성하기 위함이다. 차례로, 이 정보는 이 특정의 차폐 테스트의 범위를 수정할 수 있다. 예를 들어, 한 물체가 장면의 전경에 위치하고 다른 하나의 물체가 그 뒤에 부분적으로 가려져 있다면, 전경의 물체가 유일한 차폐물인 경우 뒷쪽 물체를 위해 불필요한 차폐 테스트를 계산할 필요가 없다. 반대로 배경의 물체에는 앞 물체의 가장자리를 따라 그림자 같은 후광이 나타날 것이다. The implementation of this scaling is to linearly interpolate between a scaled version of the delta value and a default value, with the interpolation amount based on the range value.
최종 픽셀 색상을 내놓기 전의 마지막 단계는 모든 커널 샘플로부터 평균 차폐값을 계산하고, 그 값을 최대/최소 차폐 값 사이를 보간하는데 사용하는 것이다. 이 마지막 보간은 광범위한 차폐값을 압축해주고 보다 부드러운 출력을 제공해준다. 이 장의 데모 프로그램에서 주의할 것은, 사진의 차폐를 계산하는데 단지 16샘플만을 사용했다는 것이다. 만일 더 많은 샘플을 사용했다면, 출력 차폐 버퍼는 더 부드러운 결과를 보여줄 것이다. 이것은 다른 등급의 하드웨어들에서 성능과 품질을 조정하는 좋은 방법이 된다.
생성한 차폐 버퍼를 가지고, 최종 렌더링 패스에서 셰이더 자원에 포함해 이를 이용할 수 있다. 렌더된 기하는 단수히 스크린 공간 텍스쳐 좌표를 계산하고, 차폐 버퍼를 샘플링한 뒤, 그 값을 이용하여 ambient 조건을 조정하면 된다. 예시로 제공되는 파일은 단순히 다섯 샘플의 평균을 이용하는 방법을 사용하였지만, 가우시안 브럴와 같은 좀더 세련된 방법을 대신 사용하는 것도 좋다.
이번 장을 위해 만든 데모 프로그램은 단순히 다수의 정방면체를 우리의 SSAO 셰이더만을 이용하여 렌더링한 것이다. 이번 기법에서 논의된 조절가능한 파라미터들에 대해서는 화면상의 슬라이더 콘트롤러를 이용하여 실시간으로 바꿀 수 있다. 그림 6 이하는 차폐 버퍼의 모습과 최종 출력 렌더링의 결과를 볼 수 있다. 한가지 알릴 사실은 차폐 효과를 보다 과장하기 위하여 차폐 파라미터들을 조절했다.
그림 6. 데모 프로그램의 차폐 버퍼의 모습
그림 7. 데모 프로그램의 최종 출력 렌더링의 모습
결론
In this chapter we developed an efficient, screen space technique for adding realism to the ambient lighting term of the standard phong lighting model. This technique provides one implementation of the SSAO algorithm, but it is certainly not the only one. The current method can be modified for a given type of scene, with more or less occlusion for distant geometry. In addition, the minimum and maximum amounts of occlusion, interpolation techniques, and sampling kernels are all potential areas for improvement or simplification. This chapter has attempted to provide you with an insight into the inner workings of the SSAO technique as well as a sample implementation to get you started.
float compareDepths(in float depth1, in float depth2,inout int far) {
float diff = (depth1 - depth2)*100; //depth difference (0-100) float gdisplace = 0.2; //gauss bell center float garea = 2.0; //gauss bell width 2
//reduce left bell width to avoid self-shadowing if (diff<gdisplace){ garea = 0.1; }else{ far = 1; } float gauss = pow(2.7182,-2*(diff-gdisplace)*(diff-gdisplace)/(garea*garea));
AO(Ambient Occlusion)는 사실적인 그림자 효과 라이팅의 방법 중 하나로써, 전역 조명 과정에서 한 점의 차폐 양을 계산 하는 것을 나타낸다. 이로써 장면 내의 입체 볼륨감을 더욱 부각시킬 수 있다. SSAO는 이를 Screen Space에서 수행하는 것이다. HBAO, SSDO등도 이와 한 뿌리다.
실제 적용 해보면 제일 애를 먹는 것이 half-resolution에서 AO 처리를 한 뒤 upscale을 하는 부분인데, 이는 inferred lighting의 DSF필터를 적용 하면 쉽게 해결 될 듯 하다.( DSF 적용 해보진 않음;; 언능 해봐야집)
하지만 지글지글 문제는 해결이 안되지 싶은데.. 이놈때문에 엔진 디폴트로 AO는 꺼트린 상태다. 섬바뤼헬미~
삼각형 폴리곤의 각 버텍스는 부동소수점(float)로 표현된다. 소수점은 이론상으로는 연속된 값이다.
이 버텍스가 여러가지로 수치변환되어 최종적으로는 스크린 좌표로 변환된다.
이 스크린 좌표는 [ 정수 ]로, 스크린상의 점은 엄밀히 작은 정방형이다.
정수는 아는대로 불연속한 것이다.
연속이 어느새 불연속이 된것은 그다지 신경쓰지 않지만 실은 매우 중요한 것이다
연속한 폴리곤을 불연속적인 픽셀로 바꾸려면 몇가지 룰이 필요하다.
그 룰을 [ 레스터라이제이션 룰 (Rasterization Rule) ]이라고 한다.
예를 들면 아래 폴리곤에 있는 3개의 버텍스의 스크린 위치가 결정됐다고 했을대 그 주위와 내부는 어떻게 칠해질까를 생각해본다
변을 포함하고 있는 어떤 파란 색으로 칠해질까? 만일 전부 칠해지나? 면적이 넓으면 칠해지나?
이것들이 제대로 결정되어있지 않으면 외관이 완전히 변해버린다.
이것은 특히 스크린 픽셀을 의식한 렌더링( 2D의 HUD나 포스트 이펙트 렌더링 같은 것)
에 커다란 영향을 주게 된다
여기에서는 이렇게 평범할수도 있지만 중요한 래스터라이제이션 룰에 대해 설명하고자 한다.
또한 언급이 없는 한 폴리곤은 스크린의 범위로 수치가 변환되고 있다고 한다. ( 단 소수점임)
또한 사각형들은 화면의 도트 그 자체이다
① 스크린 도트와 버텍스의 위치 관계
폴리곤이 여러가지 변환을 거쳐 스크린의 범위와 같은 값이 됐다고 하자. 단 이 값은 부동소수점 그대로이다. 이때의 폴리곤의 소수점 값과 스크린의 도트는 다음과 같은 위치 관계가 된다
오렌지색 점이 폴리곤이 변환된 소수점 값이며, 빨간 숫자가 스크린좌표이다. (0이 기점인 것에 주의)
소수점의 정수값(3.0이라던지)이 도트 중앙에 있는것이 포인트이다.
DirectX에서는 [ 오렌지색 점이 폴리곤내에 포함되어있다면 기본적으로 렌더링한다 ] 라는
룰이 적용되어 있다
[ 기본적 ]이란것은, 단지 이 룰만 적용하는 것은 문제가 있다는 것이다.
예를들면, 4X4의 작은 HUD를 표시하려고 할때의 이미지는 아래와 같다
이것을 위에있던 그림과 맞춰보자
잘 보면 가로 세로 5픽셀에 걸쳐져있다. 4X4로 할려고했더니 5X5??
또 하나 별도의 문제. DirectX의 폴리곤은 삼각 폴리곤이다.
위의 사각 폴리곤을 삼각형으로 분할하면 어떻게 될까
왼쪽 하단 폴리곤과 Right-Top 폴리곤의 경계선은 오렌지색의 점을 양쪽 모두 포함하고 있다.
라는 것은 이 곳은 2번 칠해진다 라는 것이 된다.
포함하고 있는 부분을 단순히 칠한다라는 것만으로는 여러가지 문제가 생기게 되버린다는 것이다
여기서 DirectX나 각종 GUI계에서는 [ Top - Left Filling Convention ] 이라는 룰을 채택한다
② Top-Left Filling Convention
①에 있는 문제를 해결하기 위해 DirectX에서는 Top-Left Filling Convention 이라는 룰을 적용하고 있다. 이것은、「Top-Left 있는 도트는 채택, Right-Bottom에 있는 도트는 채택하지 않음」이라는 심플한 룰이다. 여기서 말하는 Left-Top과 Right-Bottom이란 무엇인가?
우선 [ Left ] 이란 삼각 폴리곤의 변이 폴리곤의 중앙으로부터 봤을때 왼쪽에 있는 측면을 말한다.
[ Right ]는 그 반대이고, [ Top ]은 수평이며 중앙보다 위에있는 부분을 가리킨다. [ Bottom ]은 Top과 반대이다
칠하려고했던 도트가 변에 대응(相当)하고 있는 경우, 그것이 Top-Left이면 채택, Right-Bottom이면 채택 하지 않는다. 이 룰을 위에서 봤던 사각 폴리곤에 적용하면 아래와 같이 된다
파란색 셀은 왼쪽 하단의 폴리곤에 의해 칠해지는 곳이다. 이 폴리곤에는 왼쪽, 오른쪽, 하단이 있지만 그 가운데 오른쪽변과 아래쪽 변에 속해있는 셀은 칠하지 않는다.
따라서 대각선의 부분과 가장 아래에있는 부분은 색칠 되지 않는다.
오른쪽상단의 0,0 도트는 왼쪽 변과 오른쪽 변의 양쪽을 공유하고 있다.
이 경우 채택하지 않는다.
한편 빨간 셀은 오른쪽 상단의 폴리곤에 의해 칠해지는 셀이다. 여기에는 Top, Left, Right 변이 있다.
오른쪽 변은 채택되지않기때문에 오른쪽 끝이 (4,4)도트가 칠해지지 않았고,
왼쪽 변( 대각선을 말함) 은 칠해진다.
이것에 의해 Right-Bottom 폴리곤과의 경계선이 여러번 칠해지는 문제도 해결된다.
사각형의 크기도 생각한대로 4X4이다
기본적으로는 이 룰에 따라 칠해지나, 예외도 있다
③ Top-Left Filling Convention가 적용되지 않는 경우
그런데 이 룰이 적용되지 않는 경우가 있다. 예를들면 아래와같은 경우이다:
방금전과 같은 크기의 사각 폴리곤이 조금 왼쪽 상단으로 이동했다.
이 경우 TLFC 룰(Top-Left Filling Convention)을 적용하면, 오른쪽이 불필요하게 비어있다.
DirectX는 이럴 경우 TLFC룰을 적용하지 않는다.
이것을 위에 그림에서 대각선의 크로스한 부분에서 판별한다
이곳이 픽셀의 경계선에 들어있지 않은 경우등, 특정 조건을 만족할 경우, TLFC룰은 사용되지 않는다
이렇게 각 규칙에 따라 잘 움직이고 있으며 외관상으로 모순없는 렌더링이 가능한 것이다
④ 또 다른 문제「픽셀에서 벗어나는 문제」
2D의 HUD등을 렌더링할때에는 가능하면 Dot by Dot 형태로 렌더링 하고 싶을 것이다.
(Dot by Dot:화면의 1도트에 텍스처 1픽셀이 렌더링 된 상태)
하지만 DirectX에서는 보이지 않는 룰에 의해 의도되지 않게 되는 경우가 있다
매우 간단한 예로 재현해보자.
2X2의 텍스처가 있다고 했을대 이것과 같은 크기의 사각 폴리곤도 있다고 하자.
이것을 아래와 같이 스크린에 렌더링 한다고 하자
이때, 실제 화상은 어떻게 될까? 아마 이렇게 될것이다
뭔가 칙칙해졌다. 이것은 혼색(混色)이 되버렸기 때문이다. 왜 이렇게 된것일까?
이것은 텍스처로부터 샘플링 하는 UV가 관계하고있다.
래스터라이즈가 수행되면, 스크린의 유효 도트에 대해서 UV가 계산된다. 구체적으로는 각 도트의 중심점(오렌지색의 점)에 대응한 값이 보간되어 계산된다. UV를 적어보면 아래와 같이 된다
UV에 있는 색은 위의 그림과 같다. 각 UV 샘플링 점이 변 및 교점에 있는 것에 주목하도록.
이 경우 샘플러의 설정에 의한 것도 있지만, 주변의 색 정보를 활용해서 중간색이 나오게 된다.
그렇기 때문에 결과 화면같이 혼색된 색이 나와버리게 된다
3D 모델을 렌더링 할때에는 애초에 Dot by Dot 가 되는것은 보통 없기 때문에, 이러한 것에 신경쓸 필요는 거의 없지만, 예를들어 포스트 이펙트를 셰이더에서 만들때 같은 경우에는 이 영향에 직격탄을 맞을 것이다.
혼색되면 안되는 건 당연하다. 그럼 어떻게 막을까? 위의 그림에 답이 있지만 원래의 폴리곤의 좌표를 Top-Left로 0.5 이동시킨다. 그러면 원래의 색을 칠하고 싶은 위치와 폴리곤이 입혀지는 위치가 외관상 맞아 떨어진다
이 UV에서 샘플링 하면, 정확히 텍스처의 색이 반영된다.
원래의 폴리곤 좌표를 ( -0.5, -0.5) 오프셋 처리 하지 않으면 이론상 Dot by Dot가 되지 않는다.
또한, 이것은 원래 폴리곤 좌표가 정수가 아니면 성립하지 않기 때문에 주의 하도록..
⑤ 어디서-0.5를 이동 시킬 것인가?
이론은 알았지만 어디서 -0.5를 이동시키면 될 것인가? 사각 폴리곤을 좌표변환이 끝난 버텍스에서 작성하고있는 사람이라면 직접 버텍스 좌표를 오프셋 처리를 하면 된다.
그렇지 않고 사각 폴리곤을 좌표변환이 끝난 버텍스를 사용하지 않고 3D 공간상의 폴리곤으로써 취급할 경우, 위치를 이동시킬 기회는 두번이 있는데....
첫번째는 월드 행렬 변환이다. 이곳에 스크린에서 봤을때 (-0.5, -0.5)이동시키는 오프셋을 추가한다. 단 일반적으로 이건 꽤 어렵다.
또 다른 방법은 셰이더 내에서 처리하는 것이다. 사각형 폴리곤 렌더링용 셰이더가 있다는 전제이지만, WVP행렬을 곱한 후의 버텍스에 대해서 직접 계산을 수행한다
// 투영공간의 좌표를 한번 스크린 좌표로 해서、 // 0.5씩 오프셋 처리를 하고 원래대로 돌아온다 // 아래와 같이 작성해도 컴파일러가 최적화를 시켜준다 Out.pos.x=(Out.pos.x*screenW-0.5f)/screenW; Out.pos.y=(Out.pos.y*screenH +0.5f)/screenH;
returnOut; }
WVPMatrix(월드 X 뷰 X 프로젝션)를 로컬 좌표로 곱한 후에 각 성분을 w성분으로 나누면
버텍스 좌표는 (-1, -1, 0) ~ (1,1,1)이라는 작은 영역으로 제한된다.
이것이 늘어나 스크린좌표가 되는 것이다.
지금은 스크린 좌표 베이스에서 0.5 오프셋을 넣는것이기에 한번 스크린 의 폭과 높이를 좌표에 곱해 스크린 좌표로 변환해서 0.5 오프셋을 넣고 다시 스크린의 폭과 높이로 나누어 작은 영역으로 되돌린다.
y 성분은 스크린의 축과 투영공간의 축의 방향이 반대이기 때문에 덧셈을 하는것에 주의 한다
Dot By Dot 표시는 2D 시대에는 당연한(애초에 그것밖에 없음)것 이었지만 3D가 베이스가 되는 DirectX에 있어서는 의외로 어려운 것이다. 하지만 래스터라이제이션 룰과 버텍스 보간의 내부를 역수로 취하면 가능 할 것이다
Alpha test is perhaps the simplest Direct3D 9 functionality to emulate. It does not require the user to set a alpha blend state. Instead the user can simply choose to discard a pixel based upon its alpha value. The following pixel shader does not draw the pixel if the alpha value is less than 0.5.
// // PS for rendering with alpha test // float4 PSAlphaTestmain(PSSceneIn input) : COLOR0 { float4 color = tex2D( g_samLinear, g_txDiffuse, input.tex ) * input.colorD; if( color.a < 0.5 ) discard; return color; }