1 คะแนน โดย GN⁺ 2 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • เชดเดอร์บนเบราว์เซอร์ผสาน การกระเจิงแบบ Rayleigh, การกระเจิงแบบ Mie และการดูดกลืนของโอโซน เพื่อเรนเดอร์ท้องฟ้าสีฟ้าและแสงอาทิตย์ยามอาทิตย์ตก·อาทิตย์ขึ้นแบบเรียลไทม์
  • ระบบจะสะสม optical depth ของรังสีกล้องและค่า transmissivity ตามกฎของ Beer's Law พร้อมคำนวณการกระจายของการกระเจิงตามทิศทางดวงอาทิตย์ด้วย phase function
  • เอฟเฟกต์อาทิตย์ตกทำโดยรัน light-march แยกต่างหากไปตามทิศทางดวงอาทิตย์ในแต่ละ sample เพื่อสะท้อนปริมาณแสงอาทิตย์ที่สูญเสียไประหว่างผ่านชั้นบรรยากาศ
  • เชดเดอร์ท้องฟ้าแบบระนาบจะกลายเป็น เอฟเฟกต์ post-processing ด้วย depth buffer และการกู้คืนพิกัดโลก จึงจัดการได้แม้กระทั่งหมอกบรรยากาศระหว่างวัตถุในฉาก
  • ในระดับสเกลดาวเคราะห์ ระบบจะขยายต่อด้วย logarithmic depth buffer, ray-sphere intersection และ LUT สำหรับ Transmittance·Sky-view·Aerial Perspective

เป้าหมายของเชดเดอร์การกระเจิงในชั้นบรรยากาศและแหล่งอ้างอิง

  • เป้าหมายคือการจำลองชั้นสีที่ต่อเนื่องจาก สีส้มเข้ม·สีน้ำเงิน·สีดำของฉากหลังอวกาศ ในชั้นบรรยากาศชั้นบนของโลกให้เหมือนภาพถ่ายพระอาทิตย์ตกวงโคจรต่ำของกระสวยอวกาศ Endeavour ด้วยเชดเดอร์บนเบราว์เซอร์
  • ขอบเขตการพัฒนาเริ่มจาก sky dome ที่สมจริงซึ่งผสาน raymarching, การกระเจิงแบบ Rayleigh, การกระเจิงแบบ Mie และการดูดกลืนของโอโซน แล้วขยายต่อไปยังเปลือกบรรยากาศรอบดาวเคราะห์และการปรับแต่งประสิทธิภาพด้วย LUT
  • แหล่งอ้างอิงหลักคือ Three Geospatial, A Scalable and Production Ready Sky and Atmosphere Rendering Technique ของ Sébastien Hillaire และ Atmospheric Scattering (and also just faking it)

โมเดลพื้นฐานของการเรนเดอร์ท้องฟ้า

  • เหตุใด gradient แบบง่ายจึงไม่เพียงพอ

    • สีของท้องฟ้าไม่ควรถูกมองเป็นเพียงฉากหลังสีน้ำเงินธรรมดา แต่เป็นผลลัพธ์จากการที่แสงมีปฏิสัมพันธ์กับอากาศและองค์ประกอบต่าง ๆ ของมัน
    • ต้องคำนึงถึงตัวแปรอย่างระดับความสูงของผู้สังเกต ปริมาณฝุ่น และช่วงเวลาในวัน โดยการคำนวณจะเกิดขึ้นภายใน ปริมาตร(volume)
  • การสุ่มตัวอย่างความหนาแน่นของบรรยากาศ

    • ชั้นบรรยากาศจะถูกสุ่มตัวอย่างด้วย raymarching คล้าย volumetric clouds หรือ volumetric light
    • ระบบจะยิงรังสีจากตำแหน่งกล้องและค่อย ๆ เคลื่อนไปตามตัวกลางโปร่งใส พร้อมคำนวณทั้ง transmittance ซึ่งคือแสงที่ยังคงเหลือหลังผ่านชั้นบรรยากาศ และ scattering ที่ถูกเปลี่ยนทิศกลับเข้าหากล้องในแต่ละ sample
    • หากต้องการทบทวน raymarching สามารถดู Painting with Math: A Gentle Study of Raymarching
  • ความหนาแน่นแบบ Rayleigh และ optical depth

    • หากต้องการหาค่า transmittance จำเป็นต้องสะสมความหนาแน่นของบรรยากาศที่รังสีเดินทางผ่านเพื่อคำนวณ optical depth
    • ฟังก์ชันความหนาแน่นแบบ Rayleigh แสดงให้เห็นว่าในระดับความสูง h มี “อากาศ” อยู่มากเพียงใด และสะท้อนผลที่ว่าชั้นบรรยากาศจะเบาบางลงเมื่อระดับความสูงเพิ่มขึ้น
    • ตัวอย่างการใช้งานใช้ค่า RAYLEIGH_SCALE_HEIGHT = 8.0km, ATMOSPHERE_HEIGHT = 100.0km, VIEW_DISTANCE = 200.0km, PRIMARY_STEPS = 24
    • rayleighDensity(h) คือ exp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT) และในลูปจะสะสมด้วย viewOpticalDepth += dR * stepSize
  • กฎของ Beer's Law และสีฟ้าของท้องฟ้ากลางวัน

    • ระบบจะคำนวณค่า transmittance T ของจุดหนึ่งจาก optical depth โดย T=1.0 หมายถึงไม่มีการสูญเสียแสง และ T=0.0 หมายถึงแสงหายไปทั้งหมด
    • ค่า transmittance คำนวณด้วย Beer's Law โดยโค้ดตัวอย่างใช้ vec3 transmittance = exp(-rayleighBeta * viewOpticalDepth)
    • rayleighBeta คือค่าสัมประสิทธิ์การกระเจิงแบบ Rayleigh และในเชดเดอร์เก็บเป็น vec3(0.0058, 0.0135, 0.0331)
    • มุมระหว่างทิศทางแสงอาทิตย์กับรังสีสายตาถูกจำลองด้วย Rayleigh phase function ในรูป 3.0 / (16.0 * PI) * (1.0 + mu * mu)
    • ด้วยค่าสัมประสิทธิ์การกระเจิงแบบ Rayleigh ทำให้สีแดงแทบไม่ถูกกระเจิง สีเขียวถูกกระเจิงมากขึ้นเล็กน้อย และสีน้ำเงินถูกกระเจิงมากที่สุด จึงทำให้ท้องฟ้ากลางวันดูเป็นสีฟ้า
    • เมื่อขยายเป็นหนึ่งรังสีต่อหนึ่งพิกเซล บริเวณใกล้ขอบฟ้าจะดูเหมือนหมอกขาวสว่างเพราะแสงต้องผ่านบรรยากาศมากกว่า ส่วนเมื่อระดับความสูงมากขึ้นก็จะเปลี่ยนเป็นสีน้ำเงินที่เข้มและมืดขึ้น

การกระเจิงแบบ Mie และการดูดกลืนของโอโซน

  • เอฟเฟกต์ที่ Rayleigh เพียงอย่างเดียวยังไม่พอ

    • แม้การกระเจิงแบบ Rayleigh เพียงอย่างเดียวจะให้ผลลัพธ์ที่ดีพอสมควร แต่ท้องฟ้าที่สมจริงยิ่งขึ้นยังต้องการเอฟเฟกต์บรรยากาศเพิ่มเติม
    • การกระเจิงแบบ Mie แสดงปฏิสัมพันธ์ระหว่างแสงกับอนุภาคที่มีขนาดใหญ่กว่า เช่น ฝุ่นหรือแอโรซอล และมีทั้งฟังก์ชันความหนาแน่นกับ phase function ที่แสดงการกระจายซ้ำตามทิศทาง
    • การดูดกลืนของโอโซน จะตัดความยาวคลื่นบางส่วนของแสงที่ผ่านชั้นบรรยากาศชั้นบนออกจากเส้นทาง โดยไม่ได้เกิดจากการกระเจิง
    • การดูดกลืนของโอโซนช่วยเพิ่มความลึกของสีท้องฟ้าและเลื่อนโทนสี โดยเฉพาะบริเวณขอบฟ้า ช่วงอาทิตย์ตก อาทิตย์ขึ้น และแสงสนธยาก่อนหลังช่วงนั้น
  • การสะสมของ Mie และโอโซน

    • อิมพลีเมนต์ที่ใช้ Rayleigh, Mie และโอโซนร่วมกันจะสะสม optical depth ของแต่ละชนิดไว้ใน viewODR, viewODM, viewODO
    • ในแต่ละ sample จะคำนวณ dR = rayleighDensity(h), dM = mieDensity(h), dO = ozoneDensity(h) และประกอบ tau จากผลรวมของ BETA_R * viewODR, BETA_M_EXT * viewODM, BETA_OZONE_ABS * viewODO
    • ค่า transmittance คำนวณด้วย exp(-tau) และสะสมค่าความหนาแน่นกับ transmittance รวมถึง stepSize ลงใน sumR, sumM, sumO
    • การกระเจิงสุดท้ายคำนวณในรูป SUN_INTENSITY * (phaseR * BETA_R * sumR + phaseM * BETA_M_SCATTER * sumM + BETA_OZONE_SCATTER * sumO)
  • ค่าคงที่สำคัญและเอฟเฟกต์

    • MIE_SCALE_HEIGHT เป็นค่าที่สอดคล้องกับ RAYLEIGH_SCALE_HEIGHT สำหรับแอโรซอล และตั้งให้เล็กกว่าที่ 1.2km เพราะอนุภาคมักกระจุกตัวใกล้ขอบฟ้า
    • MIE_BETA_SCATTER ควบคุมว่าอนุภาคจะกระเจิงแสงเข้าหากล้องมากเพียงใด โดยส่วนใหญ่ไม่ขึ้นกับความยาวคลื่น จึงตั้งเป็น vec3(0.003)
    • MIE_BETA_EXT คือค่าสัมประสิทธิ์การสูญดับแบบ Mie ที่บอกว่ามีแสงถูกตัดออกจากเส้นทางมากเพียงใด และทำให้บรรยากาศระยะไกลดูขุ่นมัวมากขึ้น
    • MIE_G ควบคุม anisotropy โดย 0.0 หมายถึงการกระเจิงสม่ำเสมอ และ 1.0 หมายถึงมีแนวโน้มกระเจิงไปข้างหน้ามากขึ้นอย่างชัดเจน
    • OZONE_BETA_ABS มีค่าเป็น vec3(0.00065, 0.00188, 0.00008) ซึ่งดูดกลืนสีเขียวและช่วงเหลือง-ส้มมากกว่า ทำให้สีท้องฟ้าเลื่อนไปทางน้ำเงิน·แดง·ม่วง
    • เมื่อรวม Mie และโอโซนเข้าด้วยกัน จะได้สี “sky blue” ที่เป็นธรรมชาติมากขึ้นและแสงฟุ้งมัวรอบดวงอาทิตย์ โดยเอฟเฟกต์การกระเจิงแบบ Mie จะเด่นชัดยิ่งขึ้นเมื่อดวงอาทิตย์อยู่ใกล้ขอบฟ้า

เส้นทางแสงและช่วงพระอาทิตย์ตก·พระอาทิตย์ขึ้น

  • ข้อจำกัดของการใช้งานเดิม

    • sky fragment shader สามารถเรนเดอร์สีที่ดูเป็นธรรมชาติได้ในระดับความสูงที่หลากหลาย และรองรับโมเดลการส่งผ่านของ Mie, Rayleigh และโอโซน
    • แต่เมื่อย้ายดวงอาทิตย์เข้าใกล้เส้นขอบฟ้า ก็จะเห็นเพียง กลุ่มแสงสีขาวฟุ้งมัว โดยไม่มีทั้งการลดทอนของแสงหรือเอฟเฟกต์พระอาทิตย์ตก·พระอาทิตย์ขึ้น
    • เพราะในลูป raymarching เดิมมีการคำนวณการลดทอนของแสงเฉพาะบนลำแสงมองเห็นจากกล้องไปยังแต่ละจุดตัวอย่างเท่านั้น
    • จึงต้องคำนวณด้วยว่า ก่อนแสงอาทิตย์จะมาถึงจุดตัวอย่างนั้น แสงสูญเสียไปมากแค่ไหนขณะผ่านชั้นบรรยากาศ
  • ลูปซ้อน light-march

    • ที่แต่ละจุดตัวอย่าง จะรันลูปซ้อนแยกต่างหากไปตามทิศทางของแหล่งกำเนิดแสง เพื่อสุ่มตัวอย่าง การส่งผ่าน ของเส้นทางนั้น
    • แนวทางนี้ยังถูกใช้ใน real-time cloudscapes และ volumetric lighting เช่นกัน
    • lightMarch(float start, float sunY) จะวนซ้ำตามจำนวน LIGHTMARCH_STEPS พร้อมสะสม odR, odM, odO
    • เพิ่ม optical depth ตามทิศทางดวงอาทิตย์ sunOD เข้าไปใน optical depth ของการมองเห็นเดิม viewODR, viewODM, viewODO
    • tau ขั้นสุดท้ายประกอบจากผลรวมของ BETA_R * (viewODR + sunOD.x), BETA_M_EXT * (viewODM + sunOD.y), BETA_OZONE_ABS * (viewODO + sunOD.z)
    • การใช้งานนี้ทำให้สามารถเรนเดอร์ท้องฟ้าในสภาพแสงของพระอาทิตย์ตก พระอาทิตย์ขึ้น ดวงอาทิตย์เหนือศีรษะ และช่วงระหว่างนั้นได้
    • ใช้ uniform sun angle เพื่อสร้างการเปลี่ยนแปลงของสีน้ำเงินบนท้องฟ้าตลอดทั้งวัน และการกระเจิงแบบ Mie จะช่วยผสมแสงเข้ากับเส้นขอบฟ้าอย่างเป็นธรรมชาติในช่วงพระอาทิตย์ตกและพระอาทิตย์ขึ้น
    • เมื่อดวงอาทิตย์อยู่ต่ำ โอโซนจะเพิ่ม โทนม่วง ให้กับท้องฟ้า

ขยายไปสู่บรรยากาศของดาวเคราะห์

  • จากฉากหลังแบบระนาบสู่เอฟเฟกต์หลังการประมวลผล

    • เชดเดอร์ที่สร้างไว้ก่อนหน้านี้ให้ฉากหลังท้องฟ้าที่ดี แต่ยังใกล้เคียงกับฉากหลังแบบระนาบในฉาก React Three Fiber
    • ขั้นต่อไปคือแปลงมันให้เป็น เอฟเฟกต์หลังการประมวลผล (post-processing effect) เพื่อเรนเดอร์เป็นปริมาตรที่คำนึงถึงความลึกของฉาก และเป็นเปลือกบรรยากาศที่ล้อมรอบ planetary mesh
    • เพื่อทำเช่นนี้ จะต้องสร้างพิกัด world space ขึ้นใหม่จากพิกัด screenUV และนำ depth buffer ของฉากมาใช้กับ raymarching
  • การสร้าง world space ใหม่และลำแสง 3D

    • หากต้องการใช้ atmospheric scattering กับฉาก ไม่ใช่แค่วาดท้องฟ้า แต่ต้องเติมพื้นที่ระหว่างกล้องกับออบเจ็กต์ที่ถูกเรนเดอร์บนหน้าจอด้วย
    • ข้อมูลที่ต้องใช้คือ depth buffer ของฉาก, projectionMatrixInverse, matrixWorld, position ของกล้อง และส่งค่าเหล่านี้เป็น uniform ให้กับเอฟเฟกต์หลังการประมวลผล
    • getWorldPosition(vec2 uv, float depth) จะสร้าง clipZ ด้วย depth * 2.0 - 1.0 และสร้างพิกัด NDC ด้วย uv * 2.0 - 1.0 จากนั้นจึงใช้ projectionMatrixInverse และ viewMatrixInverse
    • กระบวนการเดียวกันนี้ยังถูกใช้ในเอฟเฟกต์หลังการประมวลผลของ volumetric lighting ใน On Shaping Light ด้วย
    • หลังจากได้ worldPosition ของพิกเซลปัจจุบันแล้ว จะตั้ง rayOrigin เป็นตำแหน่งกล้อง และคำนวณ rayDir ด้วย normalize(worldPosition - rayOrigin) เพื่อเดินหน้าไปตาม ลำแสง 3D ของแต่ละพิกเซลบนหน้าจอ
  • ปรับช่วง raymarch ด้วย depth buffer

    • เพื่อคำนึงถึงเรขาคณิตของฉาก ต้องกำหนดช่วง raymarch ของลำแสงปัจจุบันด้วย depth buffer แทน stepSize แบบคงที่
    • ใช้ sceneDepth = depthToRayDistance(uv, depth) เพื่อหาความลึกของฉากบนลำแสง
    • พิกเซลฉากหลังตรวจจับได้ด้วย depth >= 1.0 - 1e-7 และสำหรับ “sky pixels” จะใช้ sceneDepth = atmosphereHeight * SKY_MARCH_DISTANCE_MULTIPLIER
    • หากลำแสงชี้ลงด้านล่าง จะคำนวณจุดตัดกับพื้นด้วย tGround = observerAltitude / max(-rayDir.y, 1e-4) และจำกัดด้วย rayEnd = min(rayEnd, tGround)
    • stepSize ขั้นสุดท้ายคำนวณเป็น (rayEnd - rayStart) / float(PRIMARY_STEPS)
    • ลำแสงที่ชนออบเจ็กต์ใกล้ตัวหรือพื้นจะถูกสุ่มตัวอย่างอย่างแม่นยำขึ้นด้วย stepSize ที่เล็กกว่า ส่วนลำแสงที่ไปไกลจะกระจายจำนวนตัวอย่างเท่าเดิมออกไปบนระยะที่ยาวกว่า
  • หมอกบรรยากาศภายในฉาก

    • เชดเดอร์ที่ทำเป็นเอฟเฟกต์หลังการประมวลผลนี้จะใช้ atmospheric scattering กับทั้งปริมาตรของฉาก และยังสามารถใช้ sky shader เป็นฉากหลังโดยคำนึงถึงเรขาคณิตของฉากได้
    • ออบเจ็กต์ที่อยู่ใกล้กล้องจะมองเห็นได้คมชัดกว่า ส่วนออบเจ็กต์ที่อยู่ไกลจะยิ่งพร่ามากขึ้น
    • ตัวอย่างอินเทอร์แอ็กชันที่ใส่วัตถุท้องฟ้าซึ่งลากได้ด้วย Raycaster ดูได้ใน ทวีตของ MaximeHeckel

การเรนเดอร์ดาวเคราะห์

  • สองขั้นตอนที่จำเป็น

    • หากต้องการเรนเดอร์บรรยากาศรอบดาวเคราะห์ให้สมจริง จำเป็นต้องมี logarithmic depth buffer สำหรับจัดการสเกลขนาดใหญ่ และเปลือกบรรยากาศทรงกลมที่ใช้กำหนดว่ารังสีเริ่มต้นและสิ้นสุดที่จุดใดในบรรยากาศ
  • logarithmic depth buffer

    • ในระดับขนาดของดาวเคราะห์ เมื่อมองจากระยะไกล เชดเดอร์อาจแยกความแตกต่างของความลึกระหว่างบรรยากาศกับเปลือกดาวเคราะห์ได้ยาก ทำให้เกิด depth fighting
    • เนื่องจากความสูงของบรรยากาศมีเพียงไม่กี่กิโลเมตร จึงต้องปรับทั้งการกำหนด depth buffer ของฉากและวิธีการอ่านมันในเอฟเฟ็กต์ post-processing
    • ตั้งค่า logarithmicDepthBuffer: true ใน prop gl ที่ครอบ Canvas ของ React Three Fiber
    • ตัวอย่างการตั้งค่าอยู่ในรูปแบบ <Canvas shadows gl={{ alpha: true, logarithmicDepthBuffer: true }}>
    • ในเชดเดอร์ มีการนิยามการคำนวณ sceneDepth ใหม่เพื่อแปลง logarithmic depth buffer กลับเป็นระยะบนแนวรังสี
    • logDepthToViewZ(depth) ใช้ pow(2.0, depth * log2(cameraFar + 1.0)) - 1.0 และคืนค่า -d
  • หาช่วงบรรยากาศด้วย ray-sphere intersection

    • ใช้ ray-sphere intersection test เพื่อหาจุดที่รังสีสายตาเข้าสู่และออกจาก ทรงกลมบรรยากาศ (atmospheric sphere)
    • เมื่อได้จุดตัดสองจุดแล้ว ก็สามารถจำกัดลูป raymarching ให้ทำงานเฉพาะในช่วงนั้นได้ โดยไม่สิ้นเปลืองการสุ่มตัวอย่างนอกบรรยากาศ
    • เนื่องจากดาวเคราะห์เป็นเมชทรงกลม และมีทรงกลมบรรยากาศที่ใหญ่กว่าเล็กน้อยห่อหุ้มอยู่ จึงใช้การทดสอบจุดตัดแบบเดียวกันกับตัวดาวเคราะห์ด้วย
    • หากรังสีชนพื้นดินก่อนจะออกจากบรรยากาศ ก็ใช้จุดตัดกับพื้นเป็นจุดสิ้นสุดของช่วง raymarching
    • การใช้งาน raySphereIntersect ที่ใช้ อ้างอิงจาก Ray-Surface intersection functions ของ Inigo Quilez
  • ออบเจ็กต์ในฉากและเงื่อนไขสิ้นสุดของบรรยากาศ

    • บรรยากาศควรสิ้นสุดเมื่อสัมผัสพื้นผิวดาวเคราะห์ หรือเมื่อเจอออบเจ็กต์อื่นในฉากก่อนจะชนพื้น
    • กรณีชนดาวเคราะห์ จะหยุดที่พื้นโดยพื้นฐานด้วย atmosphereFar = min(atmosphereFar, planetHit.x)
    • หากมีเมชอื่นถูกเรนเดอร์อยู่ด้านหน้าพื้น จะตรวจด้วยเงื่อนไข sceneDepth < planetHit.x - 2.0 แล้วใช้ atmosphereFar = min(atmosphereFar, sceneDepth)
    • หากไม่มีตรรกะนี้ จะเกิดปัญหาที่พื้นผิวดาวเคราะห์แสดงอยู่ด้านหน้าออบเจ็กต์
  • เดโม React Three Fiber และกลิตช์ที่ยังเหลืออยู่

    • เมื่อนำการปรับสองส่วนนี้ไปใช้ในโค้ด ก็สามารถทำ atmospheric scattering เป็นเอฟเฟ็กต์ post-processing และเรนเดอร์บรรยากาศรอบดาวเคราะห์ได้
    • ฉากเดโมเรนเดอร์ “Sun - Earth system” แบบง่ายใน React Three Fiber แล้วใช้เอฟเฟ็กต์แบบกำหนดเอง
    • เมื่อปรับตำแหน่งดวงอาทิตย์และซูมออก ก็จะเห็นสีท้องฟ้าที่เชดเดอร์สร้างขึ้นจากหลายมุมมอง ตั้งแต่บนพื้นดินไปจนถึงวงโคจร
    • เอฟเฟ็กต์เดียวกันนี้ถูกใช้กับภาพโปสเตอร์สำหรับโพสต์ตัวอย่างบทความช่วงต้นเดือนเมษายน และมีการแชร์ภาพเรนเดอร์ทาง ทวีต
    • torus ในฉากอาจยังดูเหมือนอยู่ในสถานะ “lit-up” แม้หลังพระอาทิตย์ตกแล้ว
    • สาเหตุมาจาก shadow-map หรือ shadow-camera ของ directional light หลักมีสเกลเล็กเกินไป จึงครอบคลุม torus ที่อยู่ไกลมากไม่ถึง
    • วิธีเลี่ยงคืออาจนำแนวทาง shadow-mapping จาก บทความ volumetric lighting กลับมาใช้ แต่ยังไม่ได้ลองทำจริง

การจัดการสุริยุปราคา

  • กรณีที่วัตถุท้องฟ้าขนาดใหญ่บังดวงอาทิตย์ สามารถเพิ่มได้โดยเรียกฟังก์ชัน sunVisibility หลัง lightMarch แล้วนำค่าที่คืนกลับมา [0, 1] ไปคูณกับค่าการส่งผ่านแสง
  • แนวคิดพื้นฐานคือเปรียบเทียบดอตโปรดักต์ของ ทิศทางไปยังดวงจันทร์ และ ทิศทางไปยังดวงอาทิตย์ จากตำแหน่งตัวอย่างปัจจุบัน
  • หากสองทิศทางเกือบตรงกันจนดอตโปรดักต์เข้าใกล้ 1.0 แสดงว่าดวงจันทร์กำลังบังดวงอาทิตย์ และหากตั้งฉากกันจนเข้าใกล้ 0.0 แสดงว่าไม่มีการบัง
  • แต่ดอตโปรดักต์เพียงอย่างเดียวไม่สามารถสะท้อน ขนาดและสเกล ของวัตถุในฉากได้ ดังนั้นการใช้งานจริงจึงเปรียบเทียบระยะเชิงมุมระหว่างดวงอาทิตย์กับดวงจันทร์ รวมถึงรัศมีเชิงมุมของแต่ละดวง
  • sunVisibility ครอบคลุมกรณีที่ดวงจันทร์ไม่ได้บังดวงอาทิตย์, กรณีที่บังในสถานะที่เมื่อมองจากกล้องดวงจันทร์ดูใหญ่กว่าหรือมีขนาดใกล้เคียงดวงอาทิตย์, และกรณีที่บังในสถานะที่เมื่อมองจากกล้องดวงจันทร์เข้าไปอยู่ภายในรัศมีของดวงอาทิตย์
  • เดโมเพิ่ม sunVisibility และ เมชดวงจันทร์ ลงบนตัวอย่าง atmospheric scattering เดิม เพื่อให้ Atmospheric Scattering shader จัดการสถานการณ์ที่แสงไม่เพียงพอเมื่อจัดดวงจันทร์ให้อยู่แนวเดียวกับดวงอาทิตย์
  • การจำลองสุริยุปราคาและโคโรนาที่ละเอียดกว่านี้มีอยู่ในงานวิจัย Physically Based Real-Time Rendering of Eclipses และยังไม่มีการพอร์ตอิมพลีเมนเทชันของงานนั้นไปเป็น WebGL

บรรยากาศของดาวเคราะห์ดวงอื่น

  • แบบจำลองความหนาแน่นและการกระเจิงของบรรยากาศที่ใช้ ถูกกำหนดเป็นหลักด้วยค่าคงที่ไม่กี่ตัว เช่น รัศมีของดาวเคราะห์และบรรยากาศ, RayleighScaleHeight, RayleighBeta, MieScaleHeight, MieBeta, mieBetaExt, mieG, OzoneHeight, OzoneWidth
  • เมื่อปรับค่าเหล่านี้ ก็สามารถสร้างผลลัพธ์ที่ใกล้เคียงกับ บรรยากาศของดาวอังคาร หรือบรรยากาศของดาวเคราะห์ดวงอื่นได้
  • ค่าที่ใช้สำหรับดาวอังคารเป็นค่าประมาณ
    • planetRadius: 3390
    • atmosphereRadius: 3500, ความหนาประมาณ 110 km
    • rayleighScaleHeight: 11.1
    • rayleighBeta: new THREE.Vector3(0.019, 0.013, 0.0057)
    • mieScaleHeight: 1.5
    • mieBeta: 0.04
    • mieBetaExt: 0.044
    • mieG: 0.65
    • ozoneCenterHeight: 0.0
    • ozoneWidth: 1.0
    • ozoneBetaAbs: new THREE.Vector3(0.0, 0.0, 0.0)
    • sunIntensity: 15.0
    • planetSurfaceColor: '#8B4513'
  • หากแทนที่ค่าคงที่เดิมด้วยค่าเหล่านี้ ก็จะได้บรรยากาศที่ มีฝุ่นมากขึ้นและออกโทนส้ม มากขึ้น รวมถึงได้ โทนสีน้ำเงินยามอาทิตย์ตก อันเป็นเอกลักษณ์ของดาวอังคารด้วย
  • มีงานวิจัยที่เกี่ยวข้องคือ Physically Based Rendering of the Martian Atmosphere

การกระเจิงของบรรยากาศแบบอิง LUT

  • แนวทางและส่วนที่ย่อให้สั้นลง

    • เชดเดอร์แบบเดิมสามารถเรนเดอร์บรรยากาศทั้งในสเกลเล็กและสเกลใหญ่ได้อย่างตรงไปตรงมา แต่มีต้นทุนการประมวลผลสูง เพราะมีลูป raymarching ที่ PRIMARY_STEPS จำนวนมาก, ลูปซ้อน lightmarching และการคำนวณที่ความละเอียดเต็มหน้าจอ
    • A Scalable and Production Ready Sky and Atmosphere Rendering Technique ของ Sebastian Hillaire เสนอแนวทางแบบอิง Look Up Tables(LUTs) โดยเก็บการคำนวณการกระเจิงที่มีต้นทุนสูงไว้ในเท็กซ์เจอร์ แล้วค่อยสุ่มตัวอย่างและคอมโพสจากเท็กซ์เจอร์ที่คำนวณไว้ล่วงหน้าในขั้นตอนเรนเดอร์สุดท้าย
    • LUT ที่กล่าวถึงมี Transmittance LUT สำหรับเก็บปริมาณแสงที่ยังคงเหลือหลังผ่านบรรยากาศ, Sky-view LUT สำหรับเก็บสีท้องฟ้าที่ตำแหน่งกล้องหนึ่ง ๆ มองเห็น, และ Aerial Perspective LUT สำหรับเก็บหมอกบรรยากาศและแสงกระเจิงระหว่างกล้องกับเรขาคณิตของฉากที่มองเห็น
    • ไม่ได้นำการอิมพลีเมนต์จากทั้งงานวิจัยมาใช้ตรง ๆ ทั้งหมด และแม้ LUT จะเหมาะกับ compute shader ของ WebGPU แต่เพราะเวลาจำกัดและเพื่อให้บทความต่อเนื่อง จึงยังคงใช้ WebGL
    • ในงานวิจัย Aerial Perspective LUT เป็น 3D texture แต่ในการอิมพลีเมนต์นี้ใช้ 2D render target
    • วิธีนี้จำเป็นต้องสร้างเท็กซ์เจอร์ใหม่ทุกครั้งเมื่อกล้องเคลื่อนที่ เพื่อให้ได้ค่าพิกเซลที่ถูกต้อง จึงยากต่อการพรีคอมพิวต์ล่วงหน้า
    • ละ Multi-Scattering ออกไปเพราะเวลาจำกัด
  • Transmittance LUT

    • ในเชดเดอร์แบบเดิม ทุกจุดตัวอย่างจะเรียก lightmarch เพื่อคำนวณว่าแสงอาทิตย์เดินทางมาถึงได้มากแค่ไหน ซึ่งมีค่าใช้จ่ายสูง
    • Transmittance LUT จะเก็บข้อมูลนี้ไว้ล่วงหน้าที่ความละเอียดต่ำ เพื่อให้ LUT อื่น ๆ อ่านไปใช้ได้เมื่อจำเป็นต้องใช้ข้อมูลแสง
    • การอิมพลีเมนต์กำหนด Frame Buffer Object เฉพาะที่ความละเอียด 250 x 64 แล้วใช้ material เชดเดอร์แบบคัสตอมกับ full-screen quad ของซีนเฉพาะ transmittanceLUTScene จากนั้นส่งเท็กซ์เจอร์ผลลัพธ์จากการเรนเดอร์เป็น uniform ให้กับ LUT ปลายน้ำ
    • ที่แต่ละพิกเซล จะทำ raymarching จาก vec3(0.0, radius, 0.0) โดย radius จะเพิ่มจาก planetRadius ไปถึง atmosphereRadius ตามพิกัด vUv.y
    • แกน x ของ LUT แทนมุมของแสง ส่วน แกน y แทนระดับความสูง โดยสีขาวล้วนหมายถึงอัตราการส่งผ่าน 100% ขณะที่บริเวณสีดำหรือมีสีแสดงถึงพื้นดินหรือส่วนที่อากาศหนาแน่นที่สุด
    • จากนั้น LUT อื่น ๆ ก็สามารถดึง “ปริมาณแสงที่ยังเหลือหลังผ่านบรรยากาศที่มุมและความสูงที่กำหนด” ได้ด้วยการ lookup เท็กซ์เจอร์เพียงอย่างเดียว
  • Sky-view LUT

    • Sky-view LUT คำนวณว่าสีของท้องฟ้าจะเป็นอย่างไรเมื่อมองขึ้นไปจากพื้นดินในทิศทางหนึ่ง ๆ
    • getSkyViewRayDir จะแมป vUv.x ไปเป็น azimuth [-PI, PI] และ vUv.y ไปเป็น elevation [-PI/2, PI/2] เพื่อกำหนดทิศทาง raymarching
    • สำหรับ elevation ใช้ quadratic mapping คือ (vUv.y * vUv.y - 0.5) * PI ซึ่งเป็นวิธีแก้เฉพาะหน้าเพื่อหลีกเลี่ยงไม่ให้ Sky View กะพริบมากเกินไปในระยะไกล
    • ถ้าเรย์ไม่เข้าสู่บรรยากาศจะคืนค่าสีดำ และเรย์ที่ชนดาวเคราะห์จะทำ raymarching เฉพาะช่วงของบรรยากาศที่มองเห็นได้ พร้อมหยุดเร็วขึ้นเมื่อชนดาวเคราะห์
    • ลูปการกระเจิงยังคงเหมือนเดิม แต่เดินหน้าไปตามทิศทาง Sky View และใช้ Transmittance LUT สำหรับแสงอาทิตย์
  • Aerial Perspective LUT

    • ต่างจากงานวิจัยของ Hillaire ผลลัพธ์ของการอิมพลีเมนต์นี้เป็น 2D texture และแต่ละพิกเซลจะสอดคล้องกับพิกเซลหนึ่งจุดบนหน้าจอที่มองเห็น
    • ใช้ depth buffer ของฉากเพื่อตัดสินว่าควรเดินตามเรย์ไปไกลแค่ไหนและสะสมการกระเจิงมากเพียงใด
    • นำโค้ดการกระเจิงเดิมกลับมาใช้เกือบทั้งหมด แต่ให้แต่ละตัวอย่างดึงค่าการมองเห็นแสงอาทิตย์จาก Transmittance LUT
    • เอาต์พุตจะเก็บค่าการกระเจิงบรรยากาศสะสมไว้ใน RGB และเก็บค่า packed view transmittance ที่ใช้ตอนคอมโพสไว้ในอัลฟา
    • ลำดับการอิมพลีเมนต์คืออ่านค่าความลึกจาก depthBuffer แล้วกู้คืนตำแหน่งใน world space ของพิกเซลบนหน้าจอด้วย getWorldPosition(vUv, depth) ก่อนคำนวณ rayDir จากตำแหน่งกล้องไปยังตำแหน่งใน world space
    • จากนั้นใช้ logDepthToRayDistance(vUv, depth) เพื่อแปลงความลึกของฉากเป็นระยะตามเรย์ แล้วคำนวณจุดตัดกับบรรยากาศและดาวเคราะห์ ก่อนจะ march เฉพาะช่วงบรรยากาศที่มองเห็นได้
  • การคอมโพส

    • หลังจากสร้าง Sky-view LUT และ Aerial Perspective LUT แล้ว จะรวมทั้งสองเข้าด้วยกันใน post-processing pass สุดท้าย
    • งานหลักคือแปลง rayDir ปัจจุบันให้เป็น พิกัด UV ของ Sky View
    • สำหรับเรขาคณิตของฉาก จะใช้ Aerial Perspective LUT โดยใช้อัลฟาแชนเนลเป็น view transmittance และใช้แชนเนล RGB เป็นแสงกระเจิง เพื่อคำนวณ color = color * aerialPerspective.a + aerialPerspective.rgb
    • สำหรับพิกเซลพื้นหลัง จะสุ่มตัวอย่างจาก Sky View LUT และหาก depth >= 1.0 - 1e-7 จะถือเป็นพื้นหลัง แล้วใช้ color = inputColor.rgb + sampleSkyViewLUT(rayDir, planetCenter)
    • สุดท้ายจึงใช้ ACESFilm(color) และ pow(color, vec3(1.0 / 2.2))
    • ดูโค้ดการอิมพลีเมนต์บรรยากาศแบบอิง LUT ทั้งหมดได้ที่ Github link

สรุป

  • ผลลัพธ์ของการกระเจิงบรรยากาศแบบอิง LUT อาจดูแทบไม่ต่างจากเวอร์ชัน raymarching เต็มรูปแบบก่อนหน้านี้ แต่กระบวนการภายในต่างกัน
  • มันแบ่งงานออกเป็น LUT ขนาดเล็กหลายตัว แล้วคอมโพสในเอฟเฟ็กต์ขั้นสุดท้าย โดยไม่ต้อง raymarching ซ้ำไปทางดวงอาทิตย์ในทุกตัวอย่างเพื่อคำนวณแสงที่มาถึง
  • เพราะดึงข้อมูลแสงมาโดยตรงจาก Transmittance LUT จึงแทนที่ลูปซ้อนที่มีต้นทุนสูงด้วยการ lookup เท็กซ์เจอร์แบบง่าย และได้ประสิทธิภาพที่ดีขึ้นอย่างมีนัยสำคัญในฉากสุดท้าย
  • การอิมพลีเมนต์นี้ยังด้อยกว่าเมื่อเทียบกับ Sébastian Hillaire และงานอิมพลีเมนต์ในด้านอื่น ๆ โดยเฉพาะ Sky View ที่ยังมี banding และ flickering และยังไม่เหมาะที่สุดเพราะมีส่วนที่ย่อให้สั้นลง
  • เป็นไปได้ว่าควรใช้ WebGPU ตั้งแต่แรก
  • หากต้องการการอิมพลีเมนต์ระดับ production-grade จริง ๆ แนะนำ three-geospatial ของ Shoda Matsuda(@shotamatsuda)
  • นอกจากนี้ยังได้ลองใส่ volumetric clouds เพิ่มเข้าไปด้วย แต่ผลลัพธ์ยังคละกันและยังไม่น่าพอใจพอจะนำมาแสดงในบทความ จึงยังต้องทำต่อ

1 ความคิดเห็น

 
GN⁺ 2 시간 전
ความคิดเห็นจาก Hacker News
  • เคยเห็นมาก่อน เลยอาจไม่ได้เกี่ยวตรง ๆ ทั้งหมด แต่วิดีโอที่ Sebastian Lague พูดถึง การเรนเดอร์บรรยากาศ ในการทดลองสร้างดาวเคราะห์ก็น่าสนใจมากจริง ๆ https://www.youtube.com/watch?v=DxfEbulyFcY
    มันมีความสนุกเป็นพิเศษในการพัฒนาเอฟเฟ็กต์ภาพและได้เห็นมันค่อย ๆ ถูกทำให้สมจริงขึ้นเรื่อย ๆ และสักวันก็อยากลองทำอะไรในสายนี้ด้วยตัวเอง
    • สิ่งที่น่าทึ่งที่สุดเกี่ยวกับ Sebastian Lague คือ อัลกอริทึมของ YouTube สามารถทำร้ายคนได้มากแค่ไหน
      เมื่อก่อนยอดวิววิดีโออยู่ระดับหลายล้าน แต่ตอนนี้แค่ 5 แสนยังแทบจะไปไม่ถึง อาจเป็นผลจากช่วงโควิดที่ทุกคนอยู่บ้านและหันไปสนใจอะไรสุ่ม ๆ มากขึ้นก็ได้
    • ข้อบ่นเดียวที่มีต่อ Sebastian Lague คือวิดีโอมีไม่มากพอ
      ปกติผมมักเปิดฟังก่อนนอน แล้วก็เคยคิดเหมือนกันว่าอยากลองทำเอง เพราะอยากมีคอนเทนต์แบบนี้ที่สงบแต่ลงลึกกับหัวข้อเทคนิคมากกว่านี้
  • ไม่แน่ใจว่าตั้งใจละไว้หรือเปล่า แต่ประเด็นที่ควรพูดถึงคือใน โมเดลพระอาทิตย์ตก ท้องฟ้าไม่ควรกลายเป็นสีดำทันทีที่ดวงอาทิตย์ต่ำกว่าขอบฟ้า
    หลังพระอาทิตย์ตกไปสักพัก บรรยากาศเหนือศีรษะและบริเวณเหนือขอบฟ้ายังคงได้รับแสงอาทิตย์อยู่ และในบรรยากาศของโลกยังคงมีแสงสนธยาที่สังเกตได้จนกว่าดวงอาทิตย์จะต่ำกว่าขอบฟ้า 18 องศา แม้อาจไม่ค่อยเหมาะจะทำด้วย ray tracing แต่ก็มีอัลกอริทึมทั่วไปสำหรับโมเดลลักษณะนี้อยู่
  • บทความกราฟิกดี ๆ ยินดีต้อนรับเสมอ ผมเองก็เคยทำอะไรคล้ายกันใน procedural universe/planet generator และข้อดีของ การกระเจิงในบรรยากาศ คือเมื่อรวมกับการเรนเดอร์เมฆเชิงปริมาตรแล้ว จะสร้างฉากพระอาทิตย์ตกและท้องฟ้าที่สวยมากได้
    https://www.threads.com/@mrsharpoblunto/post/DVS4wfYiG8f?xmt...
    https://www.threads.com/@mrsharpoblunto/post/C6Vc-S1O9mX?xmt...
    https://www.threads.com/@mrsharpoblunto/post/C6apksDRa8q?xmt...
  • ทุกวันนี้สิ่งที่ โทรศัพท์มือถือและเบราว์เซอร์ ทำได้นี่น่าทึ่งจริง ๆ
    ยังจำได้ว่าเคยทำ implementation ของ “Display of The Earth Taking into Account Atmospheric Scattering” ของ Nishita และคณะ ซึ่งเป็นงานปี 1993 และแทบจะเป็นงานต้นแบบของหัวข้อนี้ แถมอ่านง่ายมาก: https://www.researchgate.net/publication/2933032_Display_of_...
    • ผมนึกถึงงานวิจัยที่เคยอ่านตอนทำ Rayleigh scattering กับ Mie scattering จากคอมเมนต์อีกอันก่อนหน้านี้ และอันนี้แหละใช่เลย
      ตอนที่ทำให้มันใช้งานได้ มีจังหวะหนึ่งที่รู้สึกว่า “ปรากฏการณ์ซับซ้อนในโลกจริงแบบนี้ กลับจำลองได้ค่อนข้างดีด้วยการคำนวณที่ค่อนข้างเรียบง่ายไม่กี่อย่าง” จากเดิมที่มีแค่กล่องฟ้าสีน้ำเงินนิ่ง ๆ ก็กลายเป็นวัฏจักรกลางวันกลางคืนแบบสมบูรณ์ได้ในพริบตา
  • ยอดเยี่ยมจริง ๆ
    เมื่อก่อนเคยคิดว่า ถ้าลองเรนเดอร์ท้องฟ้าบนเว็บด้วยการซ้อน gradient หลายชั้นจะเป็นอย่างไร น่าจะพอสำเร็จระดับหนึ่งและได้ผลลัพธ์ที่ใช้ได้อยู่บ้าง แต่ก็เทียบกับสิ่งที่ทำไว้ที่นี่ไม่ได้เลย ผลลัพธ์น่าประทับใจและให้แรงบันดาลใจมาก
    • ผมเคยทำ Rayleigh scattering กับ Mie scattering ในเกมเอนจินที่ทำเป็นงานอดิเรกอยู่ช่วงหนึ่ง
      แค่นั้นก็ทำให้ได้วัฏจักรพระอาทิตย์ตก/พระอาทิตย์ขึ้นที่ดูน่าเชื่อถือพอสมควรแล้ว ซึ่งทำให้ผมประหลาดใจ และถ้าจำไม่ผิด ตัวดวงอาทิตย์เองก็ดูเหมือนจะโผล่ออกมาอย่างเป็นธรรมชาติจากตรงนั้นด้วย ตอนนั้นใช้ XNA ซึ่งเป็นแพลตฟอร์มพัฒนาเกม C# ของ Microsoft และทำตามชุดบทสอนอันยอดเยี่ยมของ Riemer โดยมีฉบับเก็บถาวรอยู่ที่นี่ https://github.com/SimonDarksideJ/XNAGameStudio/wiki/Riemers...
      แต่ดูเหมือนจะไม่มีส่วนเรื่องการกระเจิงอยู่ตรงนั้น ดังนั้นส่วนนั้นผมอาจเอามาจากที่อื่น ยังจำได้ว่าเคยอ่านงานวิจัยที่มีสมการอยู่ด้วย
  • ขอแนะนำ SpaceEngine อย่างมาก เพราะขึ้นชื่อว่าใส่ใจงานด้านนี้มากพอตัว: https://www.youtube.com/watch?v=_4TjdVAbXks
    https://spaceengine.org/
    • FAQ ของอะไรแบบนี้ดีตรงที่มันแสดงให้เห็นทั้ง สเกล และความหลากหลายของคำถาม
      คำตอบของ “SpaceEngine มีวัตถุกี่ชิ้น?” คือรวมทั้งแค็ตตาล็อกดาว Hipparcos ทั้งหมด ดาวเคราะห์นอกระบบที่รู้จักทั้งหมด กาแล็กซีมากกว่าหนึ่งหมื่นแห่ง และวัตถุส่วนใหญ่ในระบบสุริยะ รวมแล้ว 130,000 ชิ้น และยังมีการเพิ่มกาแล็กซีและระบบดาวจำนวนมากยิ่งกว่าที่มีอยู่จริงในเอกภพที่สังเกตได้ด้วย ส่วน “ทำไมดาวเคราะห์น้ำถึงร้อนได้?” ก็อธิบายว่าน้ำในชั้นบรรยากาศตอนบนเป็นไอน้ำร้อน แต่เมื่ออยู่ลึกลงไปจะค่อย ๆ เปลี่ยนเป็นของเหลวภายใต้ความดันสูง และลึกลงไปกว่านั้นจะกลายเป็นสถานะของแข็งที่เรียกว่า ice VII ส่วนคำตอบของ “เคลื่อนที่อย่างไร?” คือปุ่ม WASD
    • การเปิด Wikipedia ไว้ในแท็บหนึ่ง แล้วเปิด SpaceEngine ไว้อีกแท็บ เป็นหนึ่งในประสบการณ์แบบเกมกึ่งการศึกษาที่ผมชอบที่สุด
      เป็นเกมที่ยอดเยี่ยม และถึงจะเก่าพอสมควรแล้วก็ยังไม่ค่อยเห็นอะไรที่ทำได้ดีระดับนี้
    • เป็นซอฟต์แวร์ที่ยอดเยี่ยมและมีมาหลายปีแล้ว และไม่ใช่แค่หัวข้อนี้เท่านั้น แต่หลายส่วนล้วนแสดงถึง ความหมกมุ่นกับรายละเอียด อย่างน่าทึ่ง
      พออ่านโพสต์นี้แล้วผมก็นึกถึง SpaceEngine เหมือนกัน
  • การกระเจิง เป็นหัวใจสำคัญของการสร้างภาพเรนเดอร์ที่สมจริงมานานมากแล้ว
    หนึ่งในงานวิจัยที่ผมชอบ: http://www.graphics.stanford.edu/papers/bssrdf/bssrdf.pdf
    คิดว่านี่น่าจะเป็นครั้งแรกที่ผมได้รู้ว่าการเรนเดอร์นมนั้นเป็นปัญหาที่ยุ่งยาก
  • ว้าว เป็นการเดินทางที่หนักแน่นมากจริง ๆ
    ผมน่าจะเข้าใจแค่ราว 5% เท่านั้น แต่ก็ทึ่งมากจริง ๆ
    • ผมก็เหมือนกัน แค่ ภาพประกอบ ก็คุ้มค่าแก่การอ่านแล้ว
  • โอ้ นี่เป็นบทความที่สวยมากและอ่านเพลินจริง ๆ
    แถมถ้าเป็น MIT License ด้วย ก็เท่ากับว่าปัญหา skybox ในเกมของผมได้รับการแก้แล้ว เพราะมุมมองจะถูกตรึงไว้ ดังนั้นขอแค่มีการเรนเดอร์ดวงอาทิตย์เคลื่อนข้ามท้องฟ้า แล้วก็ขยายต่อด้วยคาบแบบ sine wave เพื่อให้มุมดวงอาทิตย์เปลี่ยนไปตามฤดูกาลตลอดทั้งปีได้