單一波源在二維平面產生的水波位移:
disp = sin(dist) * force + disp_center
// 推廣到任意點(離散化波長 wave_length)
disp = sin(dist % wave_length) * force + disp_center
其中 dist 為點到波源中心的歐氏距離,force 為振幅,disp_center 為中心位移。
由於 dist % wave_length 取值有限(0 ~ wave_length-1),可預計算所有可能位移值,渲染時直接查表,避免實時 sin/sqrt 運算。
int disp[12]; // 波長 12 的位移查找表
int dist[SCREEN_HEIGHT][SCREEN_WIDTH]; // 每像素到波源的距離模波長
inline void Build_Disp_Matrix() {
for (int i = 0; i < 12; ++i)
disp[i] = static_cast<int>(sin((double)i * 30 * DXPI / 360) * PRECISION);
}
void Create_Ripple(int center_x, int center_y, int force, int damp, int wave_length) {
// 縮放位移表
for (int i = 0; i < wave_length; ++i)
disp[i] = force * disp[i] / PRECISION;
// 計算每像素距離模波長
for (int i = 0; i < SCREEN_HEIGHT; ++i) {
for (int j = 0; j < SCREEN_WIDTH; ++j) {
int dist_x = j - center_x;
int dist_y = i - center_y;
dist[i][j] = static_cast<int>(sqrt(dist_x*dist_x + dist_y*dist_y) + 0.5) % wave_length;
}
}
}
利用鄰域位移差計算折射偏移,從後台緩衝區采樣:
void Render_Ripple(LPDIRECTDRAWSURFACE7 lpdds) {
DDSURFACEDESC2 ddsd = { sizeof(ddsd) };
if (disp_index < 0) disp_index = 12;
--disp_index; // 時間動畫索引
lpdds->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);
short* lpSrcBuffer = static_cast<short*>(pBmpBack->GetBitmapBuffer());
int pitch = ddsd.lPitch >> 1;
short* lpBuffer = static_cast<short*>(ddsd.lpSurface);
for (int i = 1; i < SCREEN_HEIGHT - 2; ++i) {
for (int j = 1; j < SCREEN_WIDTH - 2; ++j) {
// 四鄰域位移差
int left = (disp_index + dist[i][j-1]) % 12;
int right = (disp_index + dist[i][j+1]) % 12;
int top = (disp_index + dist[i-1][j]) % 12;
int bottom = (disp_index + dist[i+1][j]) % 12;
int x_off = disp[left] - disp[right];
int y_off = disp[top] - disp[bottom];
int index_x = j + x_off;
int index_y = i + y_off;
int pos1 = i * pitch + j;
int pos2 = index_y * 800 + index_x; // 假設後台寬 800
if (pos2 >= 0 && pos2 < 480000)
lpBuffer[pos1] = lpSrcBuffer[pos2];
}
}
pBmpBack->ReleaseBuffer();
lpdds->Unlock(NULL);
}
pBmpBack 保存原始圖像,前台 Surface 渲染扭曲後幀pos2 有效性此算法在 90 年代 PC 上即可實時運行,展示了「預計算 + 查表 + 定點數」在圖形模擬中的經典威力。