(Dependencies cho chương trình này. vector --any
và JuicyPixels >= 2
Mã có sẵn như là Gist.)Tối ưu hóa Perlin tiếng ồn trong Haskell
{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE BangPatterns #-}
import Control.Arrow
import Data.Bits
import Data.Vector.Unboxed ((!))
import Data.Word
import System.Environment (getArgs)
import qualified Codec.Picture as P
import qualified Data.ByteString as B
import qualified Data.Vector.Unboxed as V
Tôi cố gắng để cảng Ken Perlin's improved noise để Haskell, nhưng tôi không hoàn toàn chắc chắn rằng tôi phương pháp là chính xác. Phần chính là cái gì đó nên khái quát hóa độc đáo để kích thước cao hơn và thấp hơn, nhưng đó là một cái gì đó cho sau này:
perlin3 :: (Ord a, Num a, RealFrac a, V.Unbox a) => Permutation -> (a, a, a) -> a
perlin3 p (!x', !y', !z')
= let (!xX, !x) = actuallyProperFraction x'
(!yY, !y) = actuallyProperFraction y'
(!zZ, !z) = actuallyProperFraction z'
!u = fade x
!v = fade y
!w = fade z
!h = xX
!a = next p h + yY
!b = next p (h+1) + yY
!aa = next p a + zZ
!ab = next p (a+1) + zZ
!ba = next p b + zZ
!bb = next p (b+1) + zZ
!aaa = next p aa
!aab = next p (aa+1)
!aba = next p ab
!abb = next p (ab+1)
!baa = next p ba
!bab = next p (ba+1)
!bba = next p bb
!bbb = next p (bb+1)
in
lerp w
(lerp v
(lerp u
(grad aaa (x, y, z))
(grad baa (x-1, y, z)))
(lerp u
(grad aba (x, y-1, z))
(grad bba (x-1, y-1, z))))
(lerp v
(lerp u
(grad aab (x, y, z-1))
(grad bab (x-1, y, z-1)))
(lerp u
(grad abb (x, y-1, z-1))
(grad bbb (x-1, y-1, z-1))))
Đây là khóa học kèm theo một vài chức năng được đề cập trong chức năng perlin3
, trong đó tôi hy vọng họ như hiệu quả càng tốt:
fade :: (Ord a, Num a) => a -> a
fade !t | 0 <= t, t <= 1 = t * t * t * (t * (t * 6 - 15) + 10)
lerp :: (Ord a, Num a) => a -> a -> a -> a
lerp !t !a !b | 0 <= t, t <= 1 = a + t * (b - a)
grad :: (Bits hash, Integral hash, Num a, V.Unbox a) => hash -> (a, a, a) -> a
grad !hash (!x, !y, !z) = dot3 (vks `V.unsafeIndex` fromIntegral (hash .&. 15)) (x, y, z)
where
vks = V.fromList
[ (1,1,0), (-1,1,0), (1,-1,0), (-1,-1,0)
, (1,0,1), (-1,0,1), (1,0,-1), (-1,0,-1)
, (0,1,1), (0,-1,1), (0,1,-1), (0,-1,-1)
, (1,1,0), (-1,1,0), (0,-1,1), (0,-1,-1)
]
dot3 :: Num a => (a, a, a) -> (a, a, a) -> a
dot3 (!x0, !y0, !z0) (!x1, !y1, !z1) = x0 * x1 + y0 * y1 + z0 * z1
-- Unlike `properFraction`, `actuallyProperFraction` rounds as intended.
actuallyProperFraction :: (RealFrac a, Integral b) => a -> (b, a)
actuallyProperFraction x
= let (ipart, fpart) = properFraction x
r = if x >= 0 then (ipart, fpart)
else (ipart-1, 1+fpart)
in r
Đối với nhóm hoán vị, tôi chỉ sao chép một Perlin sử dụng trên trang web của mình:
newtype Permutation = Permutation (V.Vector Word8)
mkPermutation :: [Word8] -> Permutation
mkPermutation xs
| length xs >= 256
= Permutation . V.fromList $ xs
permutation :: Permutation
permutation = mkPermutation
[151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
]
next :: Permutation -> Word8 -> Word8
next (Permutation !v) !idx'
= v `V.unsafeIndex` (fromIntegral $ idx' .&. 0xFF)
Và tất cả điều này được gắn cùng với JuicyPixels:
main = do
[target] <- getArgs
let image = P.generateImage pixelRenderer 512 512
P.writePng target image
where
pixelRenderer, pixelRenderer' :: Int -> Int -> Word8
pixelRenderer !x !y
= floor $ ((perlin3 permutation ((fromIntegral x - 256)/32,
(fromIntegral y - 256)/32, 0 :: Double))+1)/2 * 128
-- This code is much more readable, but also much slower.
pixelRenderer' x y
= (\w -> floor $ ((w+1)/2 * 128)) -- w should be in [-1,+1]
. perlin3 permutation
. (\(x,y,z) -> ((x-256)/32, (y-256)/32, (z-256)/32))
$ (fromIntegral x, fromIntegral y, 0 :: Double)
Vấn đề của tôi là perlin3
có vẻ rất chậm đối với tôi. Nếu tôi tiểu sử, pixelRenderer
cũng sẽ mất nhiều thời gian, nhưng tôi sẽ bỏ qua điều đó ngay bây giờ. Tôi không biết cách tối ưu hóa perlin3
. Tôi đã cố gắng gợi ý GHC với các mẫu hình chữ nhật, giúp cắt giảm thời gian thực hiện trong một nửa, vì vậy điều đó thật tuyệt. Chuyên sâu và nội tuyến rõ ràng hầu như không giúp được với ghc -O
. Có phải perlin3
được cho là chậm không?
CẬP NHẬT: một phiên bản trước đó của câu hỏi này đề cập đến một lỗi trong mã của tôi. Vấn đề này đã được giải quyết; hóa ra phiên bản cũ của tôi là actuallyProperFraction
là lỗi. Nó ngầm làm tròn phần không tách rời của một số dấu phẩy động thành Word8
, và sau đó trừ nó khỏi số dấu phẩy động để lấy phần phân số. Vì Word8
chỉ có thể lấy các giá trị giữa 0
và 255
, tính năng này sẽ không hoạt động chính xác cho các số nằm ngoài phạm vi đó, bao gồm cả số âm.
Bạn định dạng nó như thế nào? Việc lược tả bằng '-auto-all' sẽ vô hiệu hóa một số tối ưu hóa cho cấu hình chính xác hơn. Tôi nhận được một yếu tố 2.5 chậm lại bằng cách sử dụng '-auto-all', liên quan đến' -auto'. – Heatsink
Tôi có 'ghc -O -o/tmp/IPerlin -prof -rtsopts -auto-tất cả -caf-all -fforce-recomp IPerlin.lhs', sau đó gọi nó là'/tmp/IPerlin + RTS -p -RTS/tmp/output.png'. '-auto' thực sự nhanh hơn nhiều, nhưng bây giờ báo cáo lược tả chứa hầu như không có bất kỳ thông tin nào (không đề cập đến 'perlin3'). Ngoài ra, tôi hầu như không biết những gì tôi nên tìm kiếm: P –
Tôi nghĩ rằng 'grad' có thể được cải thiện bằng cách sử dụng một loại khác cho' vks'. Ví dụ 'Unbox' cho các bộ dữ liệu thực sự lưu trữ chúng như một bộ các mảng. Nếu bạn tạo một kiểu ba và một cá thể unbox lưu trữ các giá trị liên tiếp, nó sẽ là một sự cải tiến. Việc thực hiện ba điều nghiêm ngặt của bạn cũng sẽ đơn giản hóa một số mã khác. –