ต้นกำเนิดของ GLSL Rand () one-liner คืออะไร?


94

ฉันเคยเห็นตัวสร้างตัวเลขสุ่มหลอกเพื่อใช้ในเฉดสีที่อ้างถึงที่นี่และที่นั่นในเว็บ :

float rand(vec2 co){
  return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

มีหลายชื่อเรียกว่า "canonical" หรือ "ซับเดียวที่ฉันพบในเว็บที่ไหนสักแห่ง"

ที่มาของฟังก์ชันนี้คืออะไร? ค่าคงที่เป็นไปตามอำเภอใจตามที่ดูเหมือนหรือมีศิลปะในการเลือกหรือไม่? มีการพูดคุยเกี่ยวกับข้อดีของฟังก์ชันนี้หรือไม่?

แก้ไข: การอ้างอิงที่เก่าแก่ที่สุดของฟังก์ชันนี้ที่ฉันเจอคือไฟล์เก็บถาวรนี้ตั้งแต่เดือนกุมภาพันธ์ 2551ซึ่งตอนนี้หน้าเดิมหายไปจากเว็บแล้ว แต่ไม่มีการอภิปรายเกี่ยวกับเรื่องนี้มากไปกว่าที่อื่น


เป็นฟังก์ชันเสียงที่ใช้ในการสร้างภูมิประเทศที่สร้างขึ้นตามขั้นตอน คล้ายกับอะไรทำนองนี้en.wikipedia.org/wiki/Perlin_noise
foreyez

คำตอบ:


42

คำถามที่น่าสนใจมาก!

ฉันกำลังพยายามหาสิ่งนี้ในขณะที่พิมพ์คำตอบ :) วิธีง่ายๆในการเล่นกับมันก่อน: http://www.wolframalpha.com/input/?i=plot%28+mod%28+sin%28x*12.9898 +% 2B + y * 78.233% 29 + * + 43758.5453% 2C1% 29x% 3D0..2% 2C + y% 3D0..2% 29

ลองคิดดูว่าเรากำลังพยายามทำอะไรที่นี่: สำหรับสองพิกัดอินพุต x y เราจะส่งคืน "ตัวเลขสุ่ม" ตอนนี้นี่ไม่ใช่ตัวเลขสุ่ม มันเหมือนกันทุกครั้งที่เราใส่ x, y เท่ากัน มันเป็นฟังก์ชันแฮช!

สิ่งแรกที่ฟังก์ชันทำคือเปลี่ยนจาก 2d เป็น 1d นั่นไม่น่าสนใจในตัวมันเอง แต่มีการเลือกตัวเลขเพื่อไม่ให้เกิดซ้ำ นอกจากนี้เรายังมีจุดลอยตัวอีกด้วย จะมีบิตเพิ่มขึ้นอีกสองสามบิตจาก y หรือ x แต่อาจจะเลือกตัวเลขให้ถูกต้องจึงจะผสมกันได้

จากนั้นเราจะสุ่มตัวอย่างฟังก์ชัน sin () กล่องดำ สิ่งนี้จะขึ้นอยู่กับการนำไปใช้งานเป็นอย่างมาก!

สุดท้ายมันจะขยายข้อผิดพลาดในการนำ sin () ไปใช้โดยการคูณและรับเศษ

ฉันไม่คิดว่านี่เป็นฟังก์ชันแฮชที่ดีในกรณีทั่วไป sin () คือกล่องดำบน GPU ตามตัวเลข มันควรจะเป็นไปได้ที่จะสร้างสิ่งที่ดีกว่านี้โดยการใช้ฟังก์ชันแฮชเกือบทั้งหมดแล้วแปลงมัน ส่วนที่ยากคือการเปลี่ยนการดำเนินการจำนวนเต็มทั่วไปที่ใช้ในการแฮช cpu เป็นการดำเนินการแบบลอย (ครึ่งหนึ่งหรือ 32 บิต) หรือการดำเนินการจุดคงที่ แต่ก็ควรจะเป็นไปได้

อีกครั้งปัญหาที่แท้จริงของสิ่งนี้ในฐานะฟังก์ชันแฮชคือ sin () เป็นกล่องดำ


1
นี่ไม่ได้ตอบคำถามเกี่ยวกับที่มา แต่ฉันไม่คิดว่ามันจะตอบได้จริงๆ ฉันจะยอมรับคำตอบนี้เนื่องจากกราฟแสดงภาพประกอบ
Grumdrig

19

ที่มาน่าจะเป็นกระดาษ: "ในการสร้างตัวเลขสุ่มด้วยความช่วยเหลือของ y = [(a + x) sin (bx)] mod 1", WJJ Rey, การประชุมสถิติยุโรปครั้งที่ 22 และการประชุมวิลนีอุสครั้งที่ 7 เกี่ยวกับทฤษฎีความน่าจะเป็นและ สถิติทางคณิตศาสตร์สิงหาคม 2541

แก้ไข: เนื่องจากฉันไม่พบสำเนาของเอกสารนี้และการอ้างอิง "TestU01" อาจไม่ชัดเจนนี่คือรูปแบบตามที่อธิบายไว้ใน TestU01 ในหลอก C:

#define A1 ???
#define A2 ???
#define B1 pi*(sqrt(5.0)-1)/2
#define B2 ???

uint32_t n;   // position in the stream

double next() {
  double t = fract(A1     * sin(B1*n));
  double u = fract((A2+t) * sin(B2*t));
  n++;
  return u;
} 

โดยที่ค่าคงที่ที่แนะนำเท่านั้นคือ B1

สังเกตว่านี่เป็นกระแส การแปลงเป็นแฮช 1D 'n' จะกลายเป็นกริดจำนวนเต็ม ดังนั้นฉันเดาว่ามีคนเห็นสิ่งนี้และแปลง 't' เป็นฟังก์ชันธรรมดา f (x, y) การใช้ค่าคงเดิมด้านบนที่จะให้ผล:

float hash(vec2 co){
  float t = 12.9898*co.x + 78.233*co.y; 
  return fract((A2+t) * sin(t));  // any B2 is folded into 't' computation
}

3
น่าสนใจมากแน่นอน! ฉันพบกระดาษที่อ้างอิงถึงมันเช่นเดียวกับวารสารใน Google หนังสือแต่ดูเหมือนว่าคำพูดหรือกระดาษนั้นไม่ได้รวมอยู่ในวารสาร
Grumdrig

1
นอกจากนี้มันจะปรากฏขึ้นจากชื่อเรื่องว่าฟังก์ชันที่ฉันกำลังถามถึงควรจะกลับมาfract(sin(dot(co.xy ,vec2(12.9898,78.233))) * (co.xy + vec2(43758.5453, SOMENUMBER))เพื่อให้สอดคล้องกับฟังก์ชันที่กระดาษนั้นเกี่ยวกับ
Grumdrig

และอีกประการหนึ่งหากนี่เป็นที่มาของการใช้ฟังก์ชันจริง ๆ คำถามเกี่ยวกับที่มาของตัวเลขวิเศษ (ตัวเลือกaและb) ที่ใช้ซ้ำแล้วซ้ำอีก แต่อาจถูกนำมาใช้ในกระดาษที่คุณอ้างถึง
Grumdrig

หากระดาษไม่เจอแล้ว (แก้ไข: กระดาษเดียวกันกับลิงค์ด้านบน)
MB Reynolds

อัปเดตคำตอบพร้อมข้อมูลเพิ่มเติม
MB Reynolds

8

ค่าคงที่เป็นค่าตามอำเภอใจโดยเฉพาะอย่างยิ่งค่าที่มีขนาดใหญ่มากและทศนิยมสองสามตำแหน่งอยู่ห่างจากจำนวนเฉพาะ

โมดูลัสที่มากกว่า 1 ของไซนัสแอมพลิจูดสูงคูณด้วย 4000 เป็นฟังก์ชันคาบ มันเหมือนกับมู่ลี่หน้าต่างหรือโลหะลูกฟูกที่มีขนาดเล็กมากเพราะมันคูณด้วย 4000 และหมุนเป็นมุมด้วยผลิตภัณฑ์ดอท

เนื่องจากฟังก์ชันเป็น 2 มิติผลิตภัณฑ์ดอทจึงมีผลในการเปลี่ยนฟังก์ชันคาบเป็นแนวเฉียงที่สัมพันธ์กับแกน X และ Y โดยอัตราส่วน 13/79 โดยประมาณ มันไม่มีประสิทธิภาพคุณสามารถบรรลุสิ่งเดียวกันได้โดยการทำไซนัส (13x + 79y) สิ่งนี้ก็จะได้สิ่งเดียวกันกับที่ฉันคิดด้วยคณิตศาสตร์น้อยลง ..

หากคุณพบคาบของฟังก์ชันทั้ง X และ Y คุณสามารถสุ่มตัวอย่างเพื่อให้ดูเหมือนคลื่นไซน์ธรรมดาอีกครั้ง

นี่คือภาพของกราฟที่ซูมเข้า

ฉันไม่รู้ที่มา แต่มันก็คล้ายกับคนอื่น ๆ ถ้าคุณใช้มันในกราฟิกเป็นระยะ ๆ มันจะมีแนวโน้มที่จะสร้างลวดลายมัวร์และคุณจะเห็นว่ามันกลับมาอีกครั้งในที่สุด


แต่ใน GPUs X และ Y มีค่าตั้งแต่ 0..1 และจะดูสุ่มมากขึ้นหากคุณเปลี่ยนกราฟ ฉันรู้ว่านี่ฟังดูเหมือนคำสั่ง แต่จริงๆแล้วมันเป็นคำถามเพราะการศึกษาคณิตศาสตร์ของฉันจบลงเมื่ออายุ 18 ปี
สตริง

ฉันรู้ว่าฉันแค่ซูมเข้าเพื่อให้คุณเห็นว่าฟังก์ชันสุ่มเป็นรูปแบบนั้นยกเว้นว่าสันจะเปลี่ยนแปลงเร็วมากยกเว้นคุณจะต้องซูมให้เล็กเพื่อดูการเปลี่ยนแปลงทั้งหมด ... คุณสามารถจินตนาการได้ว่าการทำคะแนน บนสันเขาจะให้ตัวเลขสุ่มจากความสูง 0 ถึง 1 สำหรับค่า 1 ถึง 1 x และ y
aliential

โอ้ฉันเข้าใจและดูเหมือนจะมีเหตุผลมากสำหรับการสร้างตัวเลขสุ่มใด ๆ ที่แกนกลางใช้ฟังก์ชัน sin
สตริง

2
มันเป็นซิกแซกเชิงเส้นเป็นหลักและบาปควรจะเพิ่มรูปแบบเล็ก ๆ น้อย ๆ มันเหมือนกับว่ามีใครบางคนกำลังสะบัดการ์ดจากหนึ่งถึง 10 รอบอย่างรวดเร็วและปัดไปข้างหน้าคุณและคุณควรจะลอง วางรูปแบบของตัวเลขจากไพ่พวกเขาจะเป็นตัวเลขสุ่มเพราะมันจะสะบัดเร็วมากที่เขาจะได้รูปแบบโดยการเลือกไพ่ในการซิงโครไนซ์ที่แน่นอนซึ่งสัมพันธ์กับความเร็วของไพ่ที่หมุนรอบ
aliential

เพียงแค่ทราบก็จะไม่ได้เร็วขึ้นที่จะทำ(13x + 79y)ตั้งแต่dot(XY, AB)จะทำสิ่งที่คุณอธิบายเป็นผลิตภัณฑ์ที่จุดซึ่งx,y dot 13, 79 = (13x + 79y)
WHN

1

อาจจะเป็นการทำแผนที่แบบไม่เกิดขึ้นซ้ำแล้วซ้ำเล่าจากนั้นก็สามารถอธิบายได้หลายอย่าง แต่อาจเป็นเพียงการจัดการโดยพลการด้วยตัวเลขจำนวนมาก

แก้ไข: โดยทั่วไปแล้วฟังก์ชัน fract (sin (x) * 43758.5453) เป็นฟังก์ชันคล้ายแฮชแบบง่าย sin (x) ให้การแก้ไข sin อย่างราบรื่นระหว่าง -1 ถึง 1 ดังนั้น sin (x) * 43758.5453 จะถูกแก้ไขจาก - 43758.5453 ถึง 43758.5453 นี่เป็นช่วงที่ค่อนข้างใหญ่ดังนั้นแม้แต่ขั้นตอนเล็ก ๆ ใน x ก็จะให้ผลลัพธ์เป็นขั้นตอนใหญ่และการแปรผันของส่วนเศษส่วนมาก จำเป็นต้องใช้ "fract" เพื่อรับค่าในช่วง -0.99 ... ถึง 0.999 ... ทีนี้เมื่อเรามีบางอย่างเช่นฟังก์ชันแฮชเราควรสร้างฟังก์ชันสำหรับแฮชการผลิตจากเวกเตอร์ วิธีที่ง่ายที่สุดคือเรียก "แฮช" แบบแยกส่วนสำหรับ x ส่วนประกอบ y ใด ๆ ของเวกเตอร์อินพุต แต่เราจะมีค่าสมมาตร ดังนั้นเราควรจะได้ค่าบางอย่างจากเวกเตอร์วิธีการคือค้นหาเวกเตอร์แบบสุ่มและหาผลิตภัณฑ์ "จุด" ของเวกเตอร์นั้นไปเลย: fract (sin (dot (co.xy, vec2 (12.9898,78.233))) * 43758.5453); นอกจากนี้ตามเวกเตอร์ที่เลือกความยาวของมันควรมีความยาวพอที่จะมีฟังก์ชัน "sin" หลาย ๆ ส่วนหลังจากที่ผลิตภัณฑ์ "dot" ถูกคำนวณ


แต่ 4e5 ก็น่าจะใช้ได้เช่นกันฉันไม่เข้าใจว่าทำไมต้องใช้เลขวิเศษ 43758.5453 (เช่นกันฉันจะชดเชย x ด้วยจำนวนเศษส่วนเพื่อหลีกเลี่ยงแรนด์ (0) = 0
Fabrice NEYRET

1
ฉันคิดว่าด้วย 4e5 คุณจะไม่ได้รับความผันแปรของเศษส่วนมากนักมันจะให้ค่าเท่ากันเสมอ ดังนั้นจึงต้องตรงตามเงื่อนไข 2 ประการคือใหญ่พอและมีการแปรผันของส่วนเศษส่วนที่ดีเพียงพอ
โรมัน

คุณหมายถึงอะไร "มักจะให้คุณค่าเท่ากัน"? (ถ้าคุณหมายความว่ามันจะใช้ตัวเลขเดียวกันเสมออันดับแรกมันยังคงวุ่นวายอันดับสอง float จะถูกจัดเก็บเป็น m * 2 ^ p ไม่ใช่ 10 ^ p ดังนั้น * 4e5 ยังคงแย่งบิตอยู่)
Fabrice NEYRET

ฉันคิดว่าคุณเขียนเลขยกกำลัง 4 * 10 ^ 5 ดังนั้น sin (x) * 4e5 จะทำให้คุณไม่วุ่นวาย ฉันยอมรับว่าเศษส่วนจากคลื่นบาปจะทำให้คุณเป็นคนเก่งเช่นกัน
โรมัน

แต่มันขึ้นอยู่กับช่วงของ x ฉันหมายความว่าถ้าฟังก์ชันควรมีประสิทธิภาพสำหรับค่าขนาดเล็ก (-0.001, 0.001) และค่าขนาดใหญ่ (-1, 1) คุณสามารถลองดูความแตกต่างกับเศษส่วน (sin (x /1000.0) * 43758.5453); และเศษส่วน (sin (x /1000.0) * 4e5); โดยที่ x อยู่ในช่วง [-1., 1. ] ในภาพตัวแปรที่สองจะเป็นแบบโมโนโทนิคมากขึ้น (อย่างน้อยฉันก็เห็นความแตกต่างของเงา) แต่โดยทั่วไปฉันยอมรับว่าคุณยังสามารถใช้ 4e5 และมีผลลัพธ์ที่ดีเพียงพอ
โรมัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.