DirectX
透過 DirectX, C++ 和 XAML 產生即時、真實的捲頁
在開發期間的 Windows 8 和Visual Studio2012年,微軟 c + + 團隊創建一些開放原始碼應用程式展示的各種可用的軟體發展人員的 c + + 技術。 這些應用程式之一是"奧斯丁,"數位的筆記程式編寫 c + +,在 Windows 運行時 (WinRT) 上使用 DirectX 和 XAML 的專案。
在此應用程式,使用者可以創建筆記本和記筆記或自由曲線關係圖。 有的添加和刪除頁面、 不同的墨水顏色和添加的影像檔從一台 PC 或 SkyDrive 的支援。 圖 1 顯示 app 的一些截圖中的行動。
圖 1 專案"奧斯丁"
使用者可以查看自己的筆記本中三種方式:頁的單個行 (如圖 1)、 網格的頁面或,如果頁面彼此堆疊在一起。 在此堆積的視圖中,使用者可以翻頁由他的手指刷整個頁面,因為如果他在翻閱一本真正的書中。 數位頁面是在真正的時間,基於使用者的手指的位置,作為他翻轉頁面捲曲。 圖 2 顯示的頁面捲曲在行動中。
圖 2 頁冰壺
頁面-冰壺運動功能還處理頁直。 當使用者可以讓去雖然冰壺的頁面時,頁面行為像一塊真正的紙張:如果頁面是低於某一閾值,它 uncurls 回躺平的位置 ; 如果該頁面在臨界值以上,它 uncurls 但完成車削。
本文介紹在深度的幾何形狀、 技術和代碼用於執行即時頁面蜷縮和直的。
幾何的冰壺的頁面
探索的整體設計之前, 我會幾何和數學讓路。 此資訊從我的 MSDN 博客帖子,很大程度上採取"6 的專案奧斯丁第 2 部分:冰壺頁"(bit.ly/THF40f)。
2006 紙,"3D 電子書的翻書"(L. 香港 S.K. 卡和 j. Chen),描述了如何頁冰壺類比可以通過變形假想的錐體,周圍的檔中所示圖 3。 通過更改形狀和位置的錐體,可以類比更多 (或更少) 的冰壺運動。
圖 3 平紙錐周圍 (黑色) 冰壺 (綠色) 變得捲曲的紙 (紅色)
同樣,頁冰壺也類比可以通過變形假想的圓柱周圍的檔中所示圖 4。
圖 4 平板紙 (黑色) 冰壺圓柱周圍 (綠色) 變得捲曲的紙 (紅色)
我的頁面捲曲的方法,如下所示:
- 如果使用者從頁面的右上角捲曲,變形錐角 θ 和 apex 在座標 (0,0,Ay) 周圍的頁面。
- 如果使用者從中心-頁面右側的捲曲,變形與半徑為 r 的圓柱周圍的頁面。
- 如果使用者從頁面的右下角捲曲,變形周圍倒錐殼的頁面。
- 如果使用者在之間任何地方冰壺,變形周圍的圓錐和圓柱,基於輸入的 y 座標的線性組合的頁面。
- 變形後, 將紙繞 y 軸旋轉。
在這裡是要變換的圓柱周圍的頁面的詳細資訊。 (香港文章描述類似幾何變換錐周圍的頁)。用座標給定的點 Pflat {x 1,y1,z1 = 0} 平頁,目標是將其轉換成 Pcurl 座標 {x 2,y2,z2},躺在這本書的"脊柱"的半徑的圓柱上的點。 現在看看圖 5,其中顯示了油缸的結尾。 你可以看到在 x 和 Z 軸 (y 軸運行和頁面)。 請注意我代表平紙和油缸使用相同的顏色,在以前的數位。
圖 5 轉換到 Pcurl Pflat
關鍵的見解是從起源到 Pflat (1) 的距離相同,弧從起源到距離 Pcurl 沿柱狀體。 所以,從簡單的幾何形狀,我可以說,角 β = x 1 / r。 現在,將得到 Pcurl,拿出身的它向下移動 r 在 Z 軸上,β,圍繞旋轉,然後將它向上移動 r 在 Z 軸上。 CurlPage 法在圖 6 顯示變形的頂點緩衝區的頁面的代碼。 走抽象出來的頂點緩衝區和頁面座標資訊。
圖 6 變形的頂點緩衝區
void page_curl::curlPage(curl_parameters curlParams)
{
float theta = curlParams.theta;
float Ay = curlParams.ay;
float alpha = curlParams.alpha;
float conicContribution = curlParams.conicContribution;
// As the user grabs toward the middle-right of the page, curl the
// paper by deforming it on to a cylinder.
The cylinder radius is taken
// as the endpoint of the cone parameters: for example,
// cylRadius = R*sin(theta) distance to where R is the the rightmost
// point on the page, all the way up.
float cylR = sqrt( _vertexCountX * _vertexCountX
+ (_vertexCountY /2 - Ay)*( _vertexCountY /2 - Ay));
float cylRadius = cylR * sin(theta);
// Flipping from top corner or bottom corner?
float posNegOne;
if (conicContribution > 0)
{
// Top corner
posNegOne = 1.0f;
}
else
{
// Bottom corner
posNegOne = -1.0f;
Ay = -Ay + _vertexCountY;
}
conicContribution = abs(conicContribution);
for (int j = 0; j < _vertexCountY; j++)
{
for (int i = 0; i < _vertexCountX; i++)
{
float x = (float)i;
float y = (float)j;
float z = 0;
float coneX = x;
float coneY = y;
float coneZ = z;
{
// Compute conical parameters and deform
float R = sqrt(x * x + (y - Ay)*(y - Ay));
float r = R * sin(theta);
float beta = asin(x / R) / sin(theta);
coneX = r * sin(beta);
coneY = R + posNegOne * Ay - r * (1 - cos(beta)) * sin(theta);
coneZ = r * (1 - cos(beta)) * cos(theta);
// Then rotate by alpha about the y axis
coneX = coneX * cos(alpha) - coneZ * sin(alpha);
coneZ = coneX * sin(alpha) + coneZ * cos(alpha);
}
float cylX = x;
float cylY = y;
float cylZ = z;
{
float beta = cylX / cylRadius;
// Rotate (0,0,0) by beta around line given by x = 0, z = cylRadius
// aka Rotate (0,0,-cylRadius) by beta, then add cylRadius back
// to z coordinate
cylZ = -cylRadius;
cylX = -cylZ * sin(beta);
cylZ = cylZ * cos(beta);
cylZ += cylRadius;
// Then rotate by alpha about the y axis
cylX = cylX * cos(alpha) - cylZ * sin(alpha);
cylZ = cylX * sin(alpha) + cylZ * cos(alpha);
}
// Combine cone & cylinder results
x = conicContribution * coneX + (1-conicContribution) * cylX;
y = conicContribution * coneY + (1-conicContribution) * cylY;
z = conicContribution * coneZ + (1-conicContribution) * cylZ;
_vertexBuffer[j * _vertexCountX + i].position.x = x;
_vertexBuffer[j * _vertexCountX + i].position.y = y;
_vertexBuffer[j * _vertexCountX + i].position.z = z;
}
}
}
變數 conicContribution,從-1 到 + 1,捕獲在 y 軸上的位置,使用者已觸及。 值-1 表示觸摸底部的頁面,使用者,+ 1 表示頁面的頂部。
在 curl_parameters 中捕獲變形參數的完整集合:
struct curl_parameters
{
curl_parameters() {}
curl_parameters(float t, float a, float ang, float c) :
theta(t), ay(a), angle(ang), conicContribution(c) {}
float theta; // Angle of right-cone
float ay; // Location on y axis of cone apex
float alpha; // Rotation about y axis
float conicContribution; // South tip cone == -1, cylinder == 0,
north tip cone == 1
};
請注意圓柱體的半徑是缺少從這個結構 ; 我要帶一個快捷方式通過計算它基於錐參數,如在圖 6。
架構
與幾何讓路,我可以集中頁冰壺的體系結構與設計。 設計的目標是以允許為現實頁蜷縮和直的而不會失去流動性。 例如,使用者應該能夠部分捲曲頁、 頁 uncurls 有些,所以放手然後繼續冰壺頁,雖然仍然是流體和切合實際的動畫。
在 page_curl 類中,顯示在實施該專案奧斯丁頁捲曲結構圖 7。
圖 7 page_curl 類
class page_curl
{
public:
void attachPage(const std::shared_ptr<paper_sheet_node> &pageNode);
void startUserCurl(float x, float y);
void startAutoCurl();
void onRender();
private:
struct curl_parameters
{
curl_parameters() {}
curl_parameters(float t, float a, float ang, float c) :
theta(t), ay(a), angle(ang), conicContribution(c) {}
float theta; // Angle of right cone
float ay; // Location on y axis of cone apex
float alpha; // Rotation about y axis
float conicContribution;
// South tip cone == -1, cylinder == 0, north tip cone == 1
};
void continueAutoCurl();
page_curl::curl_parameters computeCurlParameters(float x, float y);
void page_curl::curlPage(page_curl::curl_parameters curlParams);
...
Other helpers that will be discussed later ...
std::shared_ptr<paper_sheet_node> _pageNode; // Page abstraction
bool _userCurl; // True if the user is curling the page
bool _autoCurl; // True if the page is uncurling
float _autoCurlStartTime; // The time the user let go to start uncurling
// Allows for smooth animations
curl_parameters _currentCone;
curl_parameters _nextCone;
};
這裡是該問題的方法:
作廢 page_curl::attachPage (const std::shared_ptr <paper_sheet_node> & pageNode) 每當捲曲頁由專案奧斯丁的代碼調用。 Paper_sheet_node 資料結構捕獲所有的相關資訊頁面座標系統,以及用於呈現此特定頁的 DirectX 頂點緩衝區。 在這篇文章並不被討論執行。
作廢 page_curl::startUserCurl (x,y 浮浮) 稱為專案奧斯丁使用者輸入處理常式代碼來指示使用者已按下他的手指,並且是冰壺 (x,y) 位置處。 這個程式碼會執行下列操作:
- 設置 _userCurl 狀態位來指示使用者冰壺頁
- 中要停止任何直如果它正在進行中的 _autoCurl 狀態位
- 將 _nextCurlParams 設置為基於使用者的位置 (x,y) 的變形參數
void page_curl::startAutoCurl 調用專案奧斯丁使用者輸入處理常式以表明使用者已放開的螢幕。 這個程式碼會執行下列操作:
- 中的 _userCurl 狀態位來指示使用者不再冰壺頁
- 集的 _autoCurl 狀態位來指示 uncurl 正在進行中,帶有時間戳記的 uncurl 開始的時候
void page_curl::onRender,由每個框架專案奧斯丁呈現迴圈調用。 請注意這是唯一的函數,其實變形的頂點緩衝區。 此代碼如下所示:
- 如果設置了 _userCurl 或 _autoCurl,代碼變形計算從 _nextCurlParams 和 _currentCurlParams 的參數的頂點緩衝區。 使用兩個可確保平穩的冰壺運動,如在本文稍後部分討論。
- 如果設置了 _autoCurl,該代碼調用 continueAutoCurl
- 將 _currentCurlParams 設置為 _nextCurlParams
void page_curl::continueAutoCurl 由 page_curl::onRender 調用,如果頁面直。 此代碼:
- 計算基於 uncurl 啟動時 _nextCurlParams
- 如果頁已完成冰壺,中 _autoCurl
page_curl::curl_parameters page_curl::computeCurlParameters (x,y 浮浮) 計算基於使用者輸入的參數捲曲 (theta,Ay,阿爾法,conicContribution)。
現在,您已經看到的整體體系結構,我以後再填寫每個公共和私有方法。 我選擇的整體設計,以確保平滑的動畫變得更加容易。 關鍵是 startUserCurl 和 onRender,拆散和維護兩個之間的狀態。
現在,我將討論其中一些方法,提供有關的設計決策的背景或動機。
平滑的動畫
鑒於前面所述的功能,也許看起來適當為 startUserCurl,只需讀取使用者的手指的位置和為 onRender 只是變形的那些參數頁。
不幸的是,這種特定的動畫可以看起來很醜,如果使用者移動她手指的速度非常快。 OnRender 在 60 幀 / 秒 (fps) 繪製變形頁,如果有可能兩個幀之間使用者手指中途在螢幕上移動。 上一幀,頁面變形到幾乎平的狀態。 如果在接下來的一幀上,頁面變形到完全捲曲的狀態、 動畫的流動性是丟失和... 看起來醜陋。
要解決此問題,我跟蹤的不僅是 _nextCurlParams (基於使用者輸入或直的公式所需的位置,捲曲應該去哪裡),也是在 _currentCurlParams 捲曲的目前狀態。 如果所需的捲曲位置離太遠捲曲的現有位置,然後我應該改為冰壺到中間值,以確保動畫是光滑的。
"太遠"一詞的解釋。 因為在 cone_parameters 結構中,有四個元素,每個人都是一個浮點數,我把 _currentCurlParams 和 _nextCurlParams 當作四維空間中的點。 卷毛兩個參數之間的距離就只是兩個點之間的距離。
同樣,"中間值"一詞也是開放的解釋。 如果 _nextCurlParams 是 _currentCurlParams 的距離太遠,我選擇一個中間點接近 _nextCurlParams 的兩個點之間的距離成正比的。 所以,如果使用者啟動一個扁平的網頁和卷髮它速度極快,頁面出現最初春天將提前到來快速,但然後減慢接近它獲取到所需位置。 因為這發生在 60 幀/秒,總體效果是很輕微的但從可用性的角度看結果看起來很棒。
圖 8 顯示完整呈現代碼。
圖 8 渲染代碼
void page_curl::onRender()
{
// Read state under a lock
curl_parameters nextCurlParams;
curl_parameters currentCurlParams;
bool userCurl;
bool autoCurl;
LOCK(_mutex)
{
nextCurlParams = _nextCurlParams;
currentCurlParams = _currentCurlParams;
userCurl = _userCurl;
autoCurl = _autoCurl;
}
// Smooth going from currentCurlParams to nextCurlParams
curl_parameters curl;
float dt = nextCurlParams.theta - currentCurlParams.theta;
float da = nextCurlParams.ay - currentCurlParams.ay;
float dr = nextCurlParams.alpha - currentCurlParams.alpha;
float dc = nextCurlParams.conicContribution -
currentCurlParams.conicContribution;
float distance = sqrt(dt * dt + da * da + dr * dr + dc * dc);
if (distance < constants::general::maxCurlDistance())
{
curl = nextCurlParams;
}
else
{
float scale = maxDistance / distance;
curl.theta = currentCurlParams.theta + scale * dt;
curl.ay = currentCurlParams.ay + scale * da;
curl.alpha = currentCurlParams.alpha + scale * dr;
curl.conicContribution =
currentCurlParams.conicContribution + scale * dc;
}
// Deform the vertex buffer
if (userCurl || autoCurl)
{
LOCK(_mutex)
{
_currentCurlParams = curl;
}
this->curlPage(curl);
}
// Continue (or stop) uncurling
if (autoCurl)
{
this->continueAutoCurl();
}
}
處理使用者輸入 (和缺乏)
專案奧斯丁,正在 DirectX XAML c + + 應用程式,使得使用 WinRT 的 api。 手勢識別由作業系統處理 — — 特別是通過 Windows::UI::Input::GestureRecognizer。
掛鉤要調用 startUserCurl (x,y) 的 onManipulationUpdated 事件時,使用者冰壺的頁面是直截了當。 StartUserCurl 的代碼然後是:
// x is scaled to (0, 1) and y is scaled to (-1, 1)
void page_curl::startUserCurl(float x, float y)
{
curl_parameters curl = this->computeCurlParameters(x, y);
LOCK(_mutex)
{
// Set curl state, to be consumed by onRender()
_nextCurlParams = curl;
_userCurl = true;
_autoCurl = false;
}
}
同樣地,它是直接掛鉤的 onManipulationCompleted 事件時要調用 startAutoCurl 使用者可以讓頁面去。 圖 9 顯示的代碼為 startAutoCurl。
圖 9 startAutoCurl 方法
void page_curl::startAutoCurl()
{
LOCK(_mutex)
{
// It's possible the user let go, but the page is
// already fully curled or uncurled
bool shouldAutoCurl = !this->doneAutoCurling(curl);
_userCurl = false;
_autoCurl = shouldAutoCurl;
if (shouldAutoCurl)
{
_autoCurlStartTime = constants::general::currentTime();
}
}
}
更有趣的代碼是用於處理自動直時使用者可以讓去頁 ; 頁面將繼續,直到它是完全地平或直到它完全地向前捲曲的手稿。 直開始當前 curl 參數和經過的時間 (平方) 的這一轉變。 這種方式,頁面開始慢慢地解開,但隨著時間的推移它 uncurls 越來越快。 這是廉價的方式,試圖類比重力。 [圖 10] 顯示該程式碼。
圖 10 處理汽車直
void page_curl::continueAutoCurl()
{
LOCK(_mutex)
{
if (this->doneAutoCurling(curl))
{
_autoCurl = false;
}
else
{
float time = constants::general::currentTime() -
_autoCurlStartTime;
_nextCurlParams = this->nextAutoCurlParams(
_currentCurlParams,
time * time);
}
}
}
調整為現實主義的冰壺
缺席從上一節是 computeCurl 的代碼參數、 doneAutoCurling 和 nextAutoCurlParams。 這些都是可調的職能,包括常量、 公式和試探法,是小時的艱苦試驗和錯誤的結果。
例如,我花了幾個小時,想達到 computeCurlParameters 的合理結果。 圖 11 顯示代碼的兩個版本 — — 類比的一塊厚厚的紙 (裡面的書頁),冰壺的一個和第二次類比 (軟封面的書,例如封面) 的塑膠蓋。
圖 11 冰壺兩種類型的頁面
// Helper macro for a straight line F(x) that passes through {x1, y1} and {x2, y2}.
// This can't be a template function (C++ doesn't let you have float literals
// as template parameters).
#define STRAIGHT_LINE(x1, y1, x2, y2, x)
(((y2 - y1) / (x2 - x1)) * (x - x1) + y1)
page_curl::curl_parameters page_curl::paperParams(float x, float y)
{
float theta, ay, alpha;
if (x > 0.95f)
{
theta = STRAIGHT_LINE(1.0f, 90.0f, 0.95f, 60.0f, x);
ay = STRAIGHT_LINE(1.0f, -20.0f, 0.95f, -5.0f, x);
alpha = 0.0;
}
else if (x > 0.8333f)
{
theta = STRAIGHT_LINE(0.95f, 60.0f, 0.8333f, 55.0f, x);
ay = STRAIGHT_LINE(0.95f, -5.0f, 0.8333f, -4.0f, x);
alpha = STRAIGHT_LINE(0.95f, 0.0f, 0.8333f, 13.0f, x);
}
else if (x > 0.3333f)
{
theta = STRAIGHT_LINE(0.8333f, 55.0f, 0.3333f, 45.0f, x);
ay = STRAIGHT_LINE(0.8333f, -4.0f, 0.3333f, -10.0f, x);
alpha = STRAIGHT_LINE(0.8333f, 13.0f, 0.3333f, 35.0f, x);
}
else if (x > 0.1666f)
{
theta = STRAIGHT_LINE(0.3333f, 45.0f, 0.1666f, 25.0f, x);
ay = STRAIGHT_LINE(0.3333f, -10.0f, 0.1666f, -30.0f, x);
alpha = STRAIGHT_LINE(0.3333f, 35.0f, 0.1666f, 60.0f, x);
}
else
{
theta = STRAIGHT_LINE(0.1666f, 25.0f, 0.0f, 20.0f, x);
ay = STRAIGHT_LINE(0.1666f, -30.0f, 0.0f, -40.0f, x);
alpha = STRAIGHT_LINE(0.1666f, 60.0f, 0.0f, 95.0f, x);
}
page_curl::curl_parameters cp(theta, ay, alpha, y);
return cp;
}
page_curl::curl_parameters page_curl::plasticParams(float x, float y)
{
float theta, ay, alpha;
if (x > 0.95f)
{
theta = STRAIGHT_LINE(1.0f, 90.0f, 0.9f, 40.0f, x);
ay = STRAIGHT_LINE(1.0f, -30.0f, 0.9f, -20.0f, x);
alpha = 0.0;
}
else
{
theta = STRAIGHT_LINE(0.95f, 40.0f, 0.0f, 35.0f, x);
ay = STRAIGHT_LINE(0.95f, -20.0f, 0.0f, -25.0f, x);
alpha = STRAIGHT_LINE(0.95f, 0.0f, 0.0f, 95.0f, x);
}
page_curl::curl_parameters cp(theta, ay, angle, y);
return cp;
}
知道冰壺僅僅是完成時的代碼涉及到檢查頁面是否完全平坦或完全捲曲:
bool page_curl::doneAutoCurling(curl_parameters curl)
{
bool doneCurlBackwards = (curl.theta > 89.999f)
&& (curl.ay < -69.999f)
&& (curl.alpha < 0.001f)
&& (abs(curl.conicContribution) > 0.999f);
bool doneCurlForwards = (curl.alpha > 99.999f);
return doneCurlBackwards || doneCurlForwards;
}
最後,我的自動捲曲,所示版本圖 12,捲曲開始依靠當前的捲曲位置和所用時間的平方。 直它捲曲的方式相同的頁,而我只是有捲曲參數線性方法的參數為扁平的頁面,但頁面讓秋天落後 (如果使用者放開當頁面被只是略有捲曲) 或轉發 (如果使用者放開時頁面大多捲曲)。 使用這種技術和經過的時間的平方,頁面有不錯反彈到它你放手的時候。 我真的很喜歡它的外觀。
圖 12 我的自動捲曲的版本
page_curl::curl_parameters page_curl::nextAutoCurlParams(
curl_parameters curl, float time)
{
curl_parameters nextCurl;
if (curl.alpha > 40)
{
nextCurl.theta = min(curl.theta + time/800000.0f, 50.0f);
nextCurl.ay = curl.ay;
nextCurl.alpha = min(curl.alpha + time/200000.0f, 100.0f);
}
else
{
nextCurl.theta = min(curl.theta + time/100000.0f, 90.0f);
nextCurl.ay = max(curl.ay - time/200000.0f, -70.0f);
nextCurl.alpha = max(curl.alpha - time/300000.0f, 0.0f);
}
if (curl.conicContribution > 0.0)
{
nextCurl.conicContribution =
min(curl.conicContribution + time/100000.0f, 1.0f);
}
else
{
nextCurl.conicContribution =
max(curl.conicContribution - time/100000.0f, -1.0f);
}
return nextCurl;
}
我希望我已經實施的一件事時直頁慣性。 使用者應該能夠扔在頁面。 當他們放手時,頁面應該繼續冰壺它被扔,直到它停止並放置到背平拖力方向相同。 這可通過將歷史記錄添加到 onRender,跟蹤在 nextAutoCurlParams 中的公式中的最後幾個位置的使用者的手指,並利用此實施。
效能。
CurlPage 方法已經做了相當多的數學捲曲單個頁面。 按我的計算,有九個電話到 sin 函數,八至 cos,一到 arcsin,一到 sqrt,並圍繞著兩個相乘,再加上加法和減法 — — 為紙模型中的每個頂點 — — 正在呈現的每個框架 !
奧斯丁專案的目的是為 60 幀/秒 ; 因此,處理每個幀可以採取不超過 15 毫秒,以免 app 感覺呆滯。
通過確保最內層的迴圈向量,哪裡Visual Studioc + + 編譯器生成流 SIMD 擴展 2 (SSE2) 指令,以充分利用 CPU 向量單位實現必要的性能。 編譯器之所以能夠超越函數 math.h 標頭檔中的所有向量化。
在這種情況下,內部迴圈一次計算的四個頂點的捲曲的位置。 性能提升可以釋放 CPU 其他渲染完成任務,如應用頁面捲曲陰影。
更多關於汽車-向量器使用 MSDN 和並行程式設計中的本機代碼的博客 (bit.ly/bWfC5Y)。
總結
我想感謝工作專案奧斯丁,尤其是Jorge佩雷拉、 喬治 Mileka 和AlanChan 的偉大的人。 我開始在該專案上的工作的時候他們已經有了很大的應用程式,和我感到幸運的是,花了一些時間給它添加一個小的現實主義。 它説明我理解了美的簡單性,以及如何艱難可以使簡單的事情 !
您可以找到有關該應用程式,包括一些視頻,通過搜索專案奧斯丁的 MSDN 博客上的詳細資訊。 你就會發現在代碼 austin.codeplex.com。
Eric布魯默 是在微軟工作的 Visual c + + 編譯器優化團隊軟體發展工程師。他在到達 ericbr@microsoft.com。
感謝以下技術專家對本文的審閱:喬治 Mileka (Microsoft)
喬治 Mileka 是在微軟的 Visual c + + 庫團隊工作軟體發展工程師。 他在到達 gmileka@microsoft.com。