Làm cách nào để sử dụng các dẫn xuất không gian màn hình để antialias một hình dạng tham số trong trình đổ bóng pixel?


8

Trong bài báo Độ phóng đại Alpha của Valve , nó đề cập đến việc sử dụng "các dẫn xuất không gian màn hình trên mỗi pixel" để thực hiện khử răng cưa. Sự hiểu biết của tôi là đây là ddxddycác chức năng nội tại trong HLSL?

Tôi đang cố gắng vẽ các hình dạng tham số (ví dụ: hình tròn: x² + y² <1 ) trong một shader và tôi không biết cách sử dụng kỹ thuật này để khử chính xác các pixel cạnh của hình dạng của mình. Ai đó có thể cung cấp một ví dụ?

Để hoàn thiện, đây là một ví dụ về loại trình tạo bóng pixel mà tôi đang tạo:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float dist = input.TexCoord.x * input.TexCoord.x
               + input.TexCoord.y * input.TexCoord.y;
    if(dist < 1)
        return float4(0, 0, 0, 1);
    else
        return float4(1, 1, 1, 1);
}

Câu trả lời:


10

Lấy ví dụ của bạn, bạn có một hàm bước của khoảng cách, tạo ra một cạnh hoàn toàn cứng (bí danh). Một cách đơn giản để chống lại vòng tròn sẽ là biến nó thành một ngưỡng mềm, như:

float distFromEdge = 1.0 - dist;  // positive when inside the circle
float thresholdWidth = 0.01;  // a constant you'd tune to get the right level of softness
float antialiasedCircle = saturate((distFromEdge / thresholdWidth) + 0.5);
return lerp(outsideColor, insideColor, antialiasedCircle);

Ở đây tôi đã sử dụng đoạn đường nối được kẹp cho hàm ngưỡng mềm, nhưng bạn cũng có thể sử dụng smoothstephoặc một cái gì đó khác. Đây + 0.5là để tập trung các đoạn đường nối vào vị trí toán học của cạnh. Dù sao, vấn đề là chức năng này trơn tru thay đổi từ outsideColorđến insideColorqua một số phạm vi khoảng cách, vì vậy nếu bạn chọn thresholdWidthmột cách thích hợp bạn sẽ nhận được một cạnh chống răng cưa nhìn.

Nhưng bạn nên chọn thresholdWidthnhư thế nào? Nếu nó quá nhỏ, bạn sẽ lại bị răng cưa và nếu nó quá lớn thì cạnh sẽ bị mờ quá mức. Ngoài ra, thông thường sẽ phụ thuộc vào vị trí camera: nếu distđược đo bằng đơn vị không gian thế giới hoặc không gian kết cấu, thì một thresholdWidthvị trí hoạt động cho một vị trí camera sẽ bị sai đối với vị trí khác.

Đây là nơi các dẫn xuất không gian màn hình xuất hiện (vâng, chúng là ddxvà các ddychức năng như bạn đoán). Bằng cách tính toán độ dài của độ dốc, distbạn có thể biết được tốc độ thay đổi trong không gian màn hình và sử dụng điều đó để ước tính thresholdWidth, như:

float derivX = ddx(distFromEdge);
float derivY = ddy(distFromEdge);
float gradientLength = length(float2(derivX, derivY));
float thresholdWidth = 2.0 * gradientLength;  // the 2.0 is a constant you can tune

Bạn vẫn có một giá trị bạn có thể điều chỉnh để có được mức độ mềm mong muốn, nhưng bây giờ bạn sẽ nhận được kết quả nhất quán bất kể vị trí máy ảnh.


Câu trả lời tốt - mã hoạt động độc đáo. +1 và được chấp nhận. Nhưng tôi có thể làm phiền bạn để mở rộng câu trả lời của bạn và giải thích lý do tại sao điều này hoạt động? Tôi hơi bối rối về những gì derivXderivYthực sự đại diện.
Andrew Russell

@AndrewRussell Bạn có quen thuộc với các công cụ phái sinh, như trong tính toán không? derivXderivYchỉ là (xấp xỉ) các đạo hàm riêng của bất kỳ biểu thức nào bạn truyền vào ddxddy, liên quan đến không gian màn hình x và y. Nếu bạn chưa học tính toán, đó là một chủ đề lớn hơn tôi có thể giải thích ở đây. :)
Nathan Reed

Tính toán của tôi là một chút gỉ - tôi đã phải đi và tìm hiểu lại đạo hàm một phần là gì. Nhưng từ đó tôi nghĩ rằng tôi đã hiểu được. Cảm ơn :)
Andrew Russell

OpenGL không hỗ trợ ddx / ddy, vì vậy trong những trường hợp đó, bạn có thể cần http.developer.nvidia.com/GPUGems/gpugems_ch25.html về cơ bản sử dụng kết cấu được tính toán trước để xác định mức mipmap được sử dụng, để xấp xỉ ddx / ddy.
Runonthespot

2
@Runonthespot OpenGL có các dẫn xuất; họ chỉ gọi một cái gì đó khác: dFdx/dFdy thay vì ddx/ ddy.
Nathan Reed
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.