0. 참고할 사이트
누군가의 글 => 기존의 다양한 접근이 적혀져 있으나 소스코드 없음.
Ndivia => 안친절하고 완전한 코드 없음
좋은 리포트 => 바로 위의 Ndivia 를 기초로한 잘 정리되어 있는 글
1. Tessellation 기법
위의 잘 정리된 글에서의 명칭을 그대로 사용함
1. Distance Approach
삼각형의 세 정점과 카메라의 거리를 평균내서 사용
2. Sphere Approach
사각형의 변을 지름으로 하는 구를 만들어서 이를 Project Space 에서의 크기를 구함.
만약 이게 정해진 삼각형보다 크면 쪼개고 작으면 안쪼갬.
그러면 거리 관계없이 정해진 길이에 근접한 삼각형들만 렌더링됨.
이때 Projected-Clip-Culled 된 삼각형을 쓰지않고 구를 쓰는 이유는 오작동을 막기 위해임.
예를들어 삼각형이 너무 가까이 있지만 Clip 되어 조금만 그려진 경우에 안쪼개게 됨.
2. HLSL
const static float _height_offset = 50; VertexPositionUv VS(VertexPositionUv v) { return v; } struct HS_CONSTANT_DATA_OUTPUT { float Edges[4] : SV_TessFactor; float Inside[2] : SV_InsideTessFactor; }; struct HS_OUTPUT { float3 position : POSITION; float2 uv : UV0; }; Texture2D<float4> heightMap : register(t0); //https://victorbush.com/2015/01/tessellated-terrain/ float DLodCameraDistance(float4 pos0, float4 pos1, float2 uv0, float2 uv1) { pos0.y = heightMap.Sample(samp_linear, uv0).r * _height_offset; pos1.y = heightMap.Sample(samp_linear, uv1).r * _height_offset; const float min_depth = 10.0f; const float max_depth = 10000.0f; float d0 = clamp((abs(pos0.z) - min_depth) / (max_depth - min_depth), 0.0, 1.0); float d1 = clamp((abs(pos1.z) - min_depth) / (max_depth - min_depth), 0.0, 1.0); float t = lerp(64, 2, (d0 + d1) * 0.5); if (t <= 2.0) return 2.0; else if (t <= 4.0) return 4.0; else if (t <= 8.0) return 8.0; else if (t <= 16.0) return 16.0; else if (t <= 32.0) return 32.0; } float DLodSphere(float4 pos0, float4 pos1, float2 uv0, float2 uv1) { pos0.y = heightMap.SampleLevel(samp_point, uv0, 0).r * _height_offset; pos1.y = heightMap.SampleLevel(samp_point, uv1, 0).r * _height_offset; float4 v_center = (pos0 + pos1) * 0.5f; float4 v_view0 = mul(v_center, _view); float4 v_view1 = v_view0; v_view1.x += length(pos0.xyz - pos1.xyz); float4 v_clip0 = mul(v_view0, _proj); v_clip0 /= v_clip0.w; float4 v_clip1 = mul(v_view1, _proj); v_clip1 /= v_clip1.w; float2 v_screen0 = pack(v_clip0.xy) * _resolution; float2 v_screen1 = pack(v_clip1.xy) * _resolution; float d = length(v_screen0 - v_screen1); float t = clamp(d / 4.f, 0, 16); return t; if (t <= 2.0) return 2.0; else if (t <= 4.0) return 4.0; else if (t <= 8.0) return 8.0; else if (t <= 16.0) return 16.0; else if (t <= 32.0) return 32.0; } HS_CONSTANT_DATA_OUTPUT ConstantHS(InputPatch<VertexPositionUv, 4> patch) { HS_CONSTANT_DATA_OUTPUT Output; // 0 3 15 12 Output.Edges[0] = DLodSphere(patch[3].position, patch[0].position, patch[3].uv, patch[0].uv); Output.Edges[1] = DLodSphere(patch[0].position, patch[1].position, patch[0].uv, patch[1].uv); Output.Edges[2] = DLodSphere(patch[1].position, patch[2].position, patch[1].uv, patch[2].uv); Output.Edges[3] = DLodSphere(patch[2].position, patch[3].position, patch[2].uv, patch[3].uv); Output.Inside[0] = (Output.Edges[0] + Output.Edges[3]) * 0.5; Output.Inside[1] = (Output.Edges[2] + Output.Edges[1]) * 0.5; return Output; } [domain("quad")] [partitioning("fractional_odd")] // [outputtopology("triangle_ccw")] [outputcontrolpoints(4)] [patchconstantfunc("ConstantHS")] [maxtessfactor(64.0f)] HS_OUTPUT HS(uint pointID : SV_OutputControlPointID, uint primitive_id : SV_PrimitiveID, InputPatch<VertexPositionUv, 4> patch) // , { HS_OUTPUT output = (HS_OUTPUT) 0; output.position.xz = patch[pointID].position.xz; output.uv = patch[pointID].uv; output.position.y = heightMap.SampleLevel(samp_point, output.uv, 0).r * _height_offset; return output; } [domain("quad")] PixelPositionUvNormal DS(HS_CONSTANT_DATA_OUTPUT input, float2 uv : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 4> patch) { PixelPositionUvNormal output; float2 uv1 = float2(uv.x + 0.01, uv.y); float2 uv2 = float2(uv.x, uv.y + 0.01); float3 p0, p1, p2 = 0; p0.xz = Interporate2(patch[0].position.xz, patch[1].position.xz, patch[2].position.xz, patch[3].position.xz, uv); p0.y = heightMap.SampleLevel(samp_linear_mirror, Interporate2(patch[0].uv, patch[1].uv, patch[2].uv, patch[3].uv, uv), 0).r * _height_offset; p1.xz = Interporate2(patch[0].position.xz, patch[1].position.xz, patch[2].position.xz, patch[3].position.xz, uv1); p1.y = heightMap.SampleLevel(samp_linear_mirror, Interporate2(patch[0].uv, patch[1].uv, patch[2].uv, patch[3].uv, uv1), 0).r * _height_offset; p2.xz = Interporate2(patch[0].position.xz, patch[1].position.xz, patch[2].position.xz, patch[3].position.xz, uv2); p2.y = heightMap.SampleLevel(samp_linear_mirror, Interporate2(patch[0].uv, patch[1].uv, patch[2].uv, patch[3].uv, uv2), 0).r * _height_offset; output.position = mul(float4(p0, 1), _viewproj); output.normal = normalize(cross(p2 - p0, p1 - p0)); output.uv = Interporate2(patch[0].uv, patch[1].uv, patch[2].uv, patch[3].uv, uv); output.uv = input.Edges[2].r; return output; } struct PixelOutput { float4 Albedo : SV_Target0; float4 Normal : SV_Target1; }; Texture2D<float4> _texture1 : register(t0); Texture2D<float4> _cloudMap2 : register(t1); PixelOutput PS(PixelPositionUvNormal input) { PixelOutput result; result.Albedo = float4(0.3, 0.3, 0.3, 1); result.Normal = float4(pack(input.normal), 0); return result; }
위의 3번째가 주던 코드를 directX 로 바꾸면서 약간 손댄 부분이다.
내가 잘못읽은건진 모르겠는데 글쓴이는 tess_factor 가 2의 승수단위로 이어져야 크랙이 없다고 하는거 같은데, 그냥 odd 로 하고 실수로 해도 상관없어보인다.