- Shader trình duyệt kết hợp tán xạ Rayleigh, tán xạ Mie và hấp thụ ozone để kết xuất bầu trời xanh cùng hoàng hôn·bình minh theo thời gian thực
- Tích lũy độ sâu quang học của tia camera và độ truyền qua theo định luật Beer, rồi tính phân bố tán xạ theo hướng Mặt Trời bằng hàm pha
- Hiệu ứng hoàng hôn thực hiện thêm một light-march riêng theo hướng Mặt Trời tại mỗi mẫu để phản ánh lượng ánh sáng Mặt Trời bị mất khi đi qua khí quyển
- Shader bầu trời phẳng trở thành một hiệu ứng hậu xử lý nhờ depth buffer và khôi phục tọa độ thế giới, nên có thể xử lý cả sương mù khí quyển giữa các vật thể trong cảnh
- Ở quy mô hành tinh, kỹ thuật này được mở rộng với logarithmic depth buffer, ray-sphere intersection và LUT cho Transmittance·Sky-view·Aerial Perspective
Mục tiêu và tài liệu tham khảo của shader tán xạ khí quyển
Mô hình cơ bản của kết xuất bầu trời
-
Vì sao gradient đơn giản là không đủ
- Màu bầu trời không chỉ là một nền xanh đơn giản mà phải được xử lý như kết quả của việc ánh sáng tương tác với không khí và các thành phần của nó
- Cần xét đến các biến như độ cao của người quan sát, lượng bụi và thời điểm trong ngày, và phép tính diễn ra bên trong một thể tích (volume)
-
Lấy mẫu mật độ khí quyển
- Khí quyển được lấy mẫu bằng raymarching tương tự như volumetric clouds hay volumetric light
- Từ vị trí camera, bắn một tia và tiến dần qua môi trường trong suốt; trong quá trình đó tính độ truyền qua (transmittance) của phần ánh sáng còn sống sót sau khi đi qua khí quyển, cùng với tán xạ (scattering) được đổi hướng về phía camera tại mỗi mẫu
- Có thể tham khảo Painting with Math: A Gentle Study of Raymarching để ôn lại raymarching
-
Mật độ Rayleigh và độ sâu quang học
- Để tính độ truyền qua, cần tích lũy mật độ khí quyển mà tia gặp trên đường đi để tính độ sâu quang học (optical depth)
- Hàm mật độ Rayleigh biểu diễn lượng “không khí” có ở độ cao
h, phản ánh hiệu ứng khí quyển loãng dần khi độ cao tăng lên
- Ví dụ triển khai dùng
RAYLEIGH_SCALE_HEIGHT = 8.0km, ATMOSPHERE_HEIGHT = 100.0km, VIEW_DISTANCE = 200.0km, PRIMARY_STEPS = 24
rayleighDensity(h) là exp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT), và trong vòng lặp nó được tích lũy bằng viewOpticalDepth += dR * stepSize
-
Định luật Beer và màu xanh của bầu trời ban ngày
- Từ độ sâu quang học, tính độ truyền qua
T tại một điểm cụ thể; T=1.0 nghĩa là không mất ánh sáng, còn T=0.0 nghĩa là ánh sáng đã biến mất hoàn toàn
- Độ truyền qua được tính theo định luật Beer, ví dụ mã dùng
vec3 transmittance = exp(-rayleighBeta * viewOpticalDepth)
rayleighBeta là hệ số tán xạ Rayleigh, được lưu trong shader dưới dạng vec3(0.0058, 0.0135, 0.0331)
- Góc giữa hướng ánh sáng Mặt Trời và tia nhìn được mô hình hóa bằng hàm pha Rayleigh có dạng
3.0 / (16.0 * PI) * (1.0 + mu * mu)
- Do hệ số tán xạ Rayleigh, màu đỏ gần như không bị tán xạ, màu xanh lục bị tán xạ nhiều hơn một chút, còn màu xanh lam bị tán xạ mạnh nhất nên bầu trời ban ngày trông có màu xanh
- Khi mở rộng thành một tia cho mỗi pixel, phía đường chân trời sẽ đi qua nhiều khí quyển hơn nên trông như lớp sương trắng sáng, còn khi độ cao tăng lên thì màu chuyển thành xanh đậm và tối hơn
Tán xạ Mie và hấp thụ ozone
-
Những hiệu ứng mà Rayleigh thôi là chưa đủ
- Chỉ với tán xạ Rayleigh cũng có thể cho kết quả khá ổn, nhưng một bầu trời chân thực hơn cần thêm các hiệu ứng khí quyển khác
- Tán xạ Mie biểu diễn tương tác giữa ánh sáng với các hạt lớn hơn như bụi hay aerosol, và có hàm mật độ cùng hàm pha biểu diễn sự phân phối lại theo từng hướng
- Hấp thụ ozone loại bỏ một số bước sóng ánh sáng trên đường đi qua tầng khí quyển trên thay vì tán xạ chúng
- Hấp thụ ozone đặc biệt làm màu trời sâu hơn và dịch chuyển màu ở đường chân trời, lúc hoàng hôn, bình minh và thời kỳ chạng vạng quanh đó
-
Tích lũy Mie và ozone
- Triển khai dùng đồng thời Rayleigh, Mie và ozone sẽ tích lũy độ sâu quang học riêng của từng thành phần bằng
viewODR, viewODM, viewODO
- Ở mỗi mẫu, tính
dR = rayleighDensity(h), dM = mieDensity(h), dO = ozoneDensity(h), rồi tạo tau từ tổng của BETA_R * viewODR, BETA_M_EXT * viewODM, BETA_OZONE_ABS * viewODO
- Độ truyền qua được tính bằng
exp(-tau), còn sumR, sumM, sumO sẽ tích lũy tương ứng mật độ, độ truyền qua và stepSize
- Tán xạ cuối cùng được tính theo dạng
SUN_INTENSITY * (phaseR * BETA_R * sumR + phaseM * BETA_M_SCATTER * sumM + BETA_OZONE_SCATTER * sumO)
-
Các hằng số chính và hiệu ứng
MIE_SCALE_HEIGHT là biến tương ứng với RAYLEIGH_SCALE_HEIGHT dành cho aerosol; vì các hạt này thường tập trung gần đường chân trời nên nó được đặt nhỏ hơn, ở mức 1.2km
MIE_BETA_SCATTER kiểm soát mức độ các hạt tán xạ ánh sáng về phía camera; vì phần lớn gần như không phụ thuộc bước sóng nên nó được đặt là vec3(0.003)
MIE_BETA_EXT là hệ số suy hao Mie biểu diễn lượng ánh sáng bị loại khỏi đường đi, khiến khí quyển xa trông mờ đục hơn
MIE_G điều khiển tính bất đẳng hướng; 0.0 là tán xạ đồng đều, còn 1.0 nghĩa là thiên mạnh hơn về tán xạ hướng tới trước
OZONE_BETA_ABS có giá trị vec3(0.00065, 0.00188, 0.00008), hấp thụ nhiều hơn các dải xanh lục và vàng-cam, làm màu trời dịch về phía xanh lam·đỏ·tím
- Khi tích hợp Mie và ozone, màu “sky blue” trở nên tự nhiên hơn và xuất hiện quầng sáng mờ quanh Mặt Trời; hiệu ứng tán xạ Mie cũng rõ hơn khi Mặt Trời ở gần đường chân trời
Đường đi ánh sáng và hoàng hôn·bình minh
-
Giới hạn của cách triển khai hiện có
sky fragment shader có thể kết xuất màu sắc tự nhiên ở nhiều độ cao khác nhau và phản ánh các mô hình truyền qua của Mie, Rayleigh và ozone
- Tuy nhiên, ngay cả khi đưa mặt trời xuống gần đường chân trời, vẫn chỉ xuất hiện quầng sáng trắng mờ đục mà không có suy giảm ánh sáng hay hiệu ứng hoàng hôn·bình minh
- Nguyên nhân là vì vòng lặp
raymarching hiện tại chỉ tính suy giảm ánh sáng trên tia nhìn từ camera đến từng mẫu
- Cũng cần tính cả mức thất thoát của ánh sáng mặt trời khi đi qua khí quyển trước khi đến điểm mẫu
-
Vòng lặp lồng nhau light-march
- Tại mỗi điểm mẫu, chạy một vòng lặp lồng nhau riêng theo hướng nguồn sáng để lấy mẫu độ truyền qua của đường đi đó
- Cách tiếp cận liên quan cũng được dùng trong real-time cloudscapes và volumetric lighting
lightMarch(float start, float sunY) lặp LIGHTMARCH_STEPS lần và tích lũy odR, odM, odO
- Cộng thêm độ sâu quang học theo hướng mặt trời
sunOD vào các độ sâu quang học hiện có viewODR, viewODM, viewODO
tau cuối cùng được tạo bằng cách cộng BETA_R * (viewODR + sunOD.x), BETA_M_EXT * (viewODM + sunOD.y), BETA_OZONE_ABS * (viewODO + sunOD.z)
- Với cách triển khai này, có thể kết xuất bầu trời cho hoàng hôn, bình minh, mặt trời ở thiên đỉnh và các điều kiện chiếu sáng ở giữa
- Uniform
sun angle tạo ra sự thay đổi sắc xanh của bầu trời trong suốt cả ngày, còn tán xạ Mie giúp hòa ánh sáng vào đường chân trời một cách tự nhiên lúc hoàng hôn và bình minh
- Khi mặt trời ở thấp, ozone thêm tông tím cho bầu trời
Mở rộng sang khí quyển hành tinh
-
Từ nền phẳng sang hiệu ứng hậu xử lý
- Shader đã tạo ở trên cung cấp một nền trời tốt, nhưng vẫn gần với một nền phẳng trong cảnh React Three Fiber
- Bước tiếp theo là biến nó thành hiệu ứng hậu xử lý (post-processing effect) để kết xuất thể tích có xét đến độ sâu của cảnh và lớp vỏ khí quyển bao quanh mesh hành tinh
- Để làm vậy, tái tạo tọa độ không gian thế giới từ tọa độ
screenUV và phản ánh depth buffer của cảnh vào raymarching
-
Tái tạo không gian thế giới và tia 3D
- Để áp dụng tán xạ khí quyển vào cảnh, không chỉ vẽ bầu trời mà còn phải lấp đầy không gian giữa camera và các đối tượng đã được kết xuất trên màn hình
- Dữ liệu cần thiết là
depth buffer của cảnh, projectionMatrixInverse, matrixWorld, position của camera, và các giá trị này được truyền vào uniform của hiệu ứng hậu xử lý
getWorldPosition(vec2 uv, float depth) tạo clipZ bằng depth * 2.0 - 1.0, tạo tọa độ NDC bằng uv * 2.0 - 1.0, rồi áp dụng projectionMatrixInverse và viewMatrixInverse
- Quy trình tương tự cũng được dùng trong hiệu ứng hậu xử lý volumetric lighting của On Shaping Light
- Sau khi lấy được
worldPosition của pixel hiện tại, đặt rayOrigin là vị trí camera, còn rayDir được tính bằng normalize(worldPosition - rayOrigin) để tiến dọc theo tia 3D cho từng pixel trên màn hình
-
Điều chỉnh đoạn raymarch bằng depth buffer
- Để xét đến hình học của cảnh, cần dùng
depth buffer để xác định đoạn raymarch của tia hiện tại thay cho stepSize cố định
- Lấy độ sâu của cảnh trên tia bằng
sceneDepth = depthToRayDistance(uv, depth)
- Pixel nền được xác định bằng
depth >= 1.0 - 1e-7, và với “sky pixels” thì áp dụng sceneDepth = atmosphereHeight * SKY_MARCH_DISTANCE_MULTIPLIER
- Nếu tia hướng xuống dưới, tính giao cắt mặt đất bằng
tGround = observerAltitude / max(-rayDir.y, 1e-4) và giới hạn bằng rayEnd = min(rayEnd, tGround)
stepSize cuối cùng được tính bằng (rayEnd - rayStart) / float(PRIMARY_STEPS)
- Các tia chạm vào vật thể gần hoặc mặt đất sẽ được lấy mẫu chính xác hơn với
stepSize nhỏ, còn các tia đi xa thì phân bố cùng số lượng mẫu trên quãng đường dài hơn
-
Sương mù khí quyển trong cảnh
- Shader được triển khai dưới dạng hiệu ứng hậu xử lý sẽ áp dụng tán xạ khí quyển lên toàn bộ thể tích của cảnh, đồng thời có thể dùng
sky shader làm nền và vẫn xét đến hình học của cảnh
- Các vật thể gần camera sẽ hiện rõ hơn, còn các vật thể ở xa sẽ bị mờ nhiều hơn
- Có thể xem ví dụ tương tác thêm thiên thể có thể kéo bằng
Raycaster trong tweet của MaximeHeckel
Kết xuất hành tinh
-
Hai bước cần thiết
- Để kết xuất bầu khí quyển chân thực quanh hành tinh, cần một logarithmic depth buffer để xử lý quy mô lớn, cùng một lớp vỏ khí quyển hình cầu xác định nơi tia sáng bắt đầu và kết thúc trong khí quyển
-
logarithmic depth buffer
- Ở quy mô hành tinh, khi nhìn từ xa, shader có thể khó phân biệt chênh lệch độ sâu giữa khí quyển và vỏ hành tinh, dẫn đến depth fighting
- Vì độ cao khí quyển chỉ vài km, cần điều chỉnh cả cách định nghĩa depth buffer của cảnh lẫn cách đọc nó trong các hiệu ứng hậu xử lý
- Trong prop
gl bao quanh Canvas của React Three Fiber, đặt logarithmicDepthBuffer: true
- Cấu hình ví dụ có dạng
<Canvas shadows gl={{ alpha: true, logarithmicDepthBuffer: true }}>
- Trong shader, tính toán
sceneDepth được định nghĩa lại để chuyển logarithmic depth buffer về khoảng cách dọc theo tia
logDepthToViewZ(depth) dùng pow(2.0, depth * log2(cameraFar + 1.0)) - 1.0 và trả về -d
-
Tìm đoạn khí quyển bằng ray-sphere intersection
- Dùng ray-sphere intersection test để tìm điểm tia nhìn đi vào và đi ra khỏi khối cầu khí quyển (atmospheric sphere)
- Khi có hai giao điểm, có thể tránh lãng phí mẫu ở ngoài khí quyển và chỉ giới hạn vòng lặp raymarching trong đoạn đó
- Vì hành tinh là mesh hình cầu và được bao quanh bởi một khối cầu khí quyển lớn hơn một chút, cùng phép kiểm tra giao cắt đó cũng được áp dụng cho chính hành tinh
- Nếu tia chạm mặt đất trước khi thoát khỏi khí quyển, dùng giao điểm với mặt đất làm điểm kết thúc cho đoạn raymarching
- Phần cài đặt
raySphereIntersect được dùng tham khảo từ Ray-Surface intersection functions của Inigo Quilez
-
Điều kiện kết thúc khí quyển và object trong cảnh
- Khí quyển phải kết thúc khi chạm bề mặt hành tinh, hoặc khi gặp object khác trong cảnh trước khi chạm đất
- Với trường hợp chạm hành tinh, mặc định dừng ở mặt đất bằng
atmosphereFar = min(atmosphereFar, planetHit.x)
- Nếu mesh khác được render phía trước mặt đất, xác định bằng điều kiện
sceneDepth < planetHit.x - 2.0 rồi áp dụng atmosphereFar = min(atmosphereFar, sceneDepth)
- Nếu không có logic này, sẽ phát sinh vấn đề bề mặt hành tinh xuất hiện phía trước object
-
Demo React Three Fiber và các glitch còn lại
- Sau khi phản ánh hai điều chỉnh này vào mã, có thể triển khai tán xạ khí quyển như một hiệu ứng hậu xử lý và kết xuất khí quyển quanh hành tinh
- Cảnh demo trong React Three Fiber kết xuất một “Sun - Earth system” đơn giản và áp dụng hiệu ứng tùy chỉnh
- Khi điều chỉnh vị trí mặt trời và thu nhỏ góc nhìn, có thể thấy màu trời do shader tạo ra ở nhiều góc khác nhau, từ mặt đất đến quỹ đạo
- Hiệu ứng tương tự cũng được dùng cho ảnh poster nhá hàng bài viết đầu tháng 4, và ảnh render đã được chia sẻ qua tweet
- Torus trong cảnh có thể vẫn trông như ở trạng thái “lit-up” ngay cả sau khi mặt trời lặn
- Nguyên nhân là shadow-map hoặc shadow-camera của directional light chính có quy mô nhỏ, nên không bao phủ được torus ở quá xa
- Một cách vòng tránh là tái sử dụng cách tiếp cận shadow-mapping từ bài viết về volumetric lighting, nhưng thực tế chưa được thử
Xử lý nhật thực
- Khi thiên thể lớn che khuất mặt trời, có thể thêm xử lý bằng cách gọi hàm
sunVisibility sau lightMarch, rồi nhân giá trị trả về [0, 1] vào độ truyền qua
- Ý tưởng cơ bản là so sánh tích vô hướng giữa hướng mặt trăng và hướng mặt trời tại điểm mẫu hiện tại
- Nếu hai hướng gần như trùng nhau và tích vô hướng gần
1.0, mặt trăng đang che mặt trời; nếu vuông góc và gần 0.0, sẽ không có che khuất
- Chỉ dùng tích vô hướng đơn thuần sẽ không phản ánh được kích thước và tỷ lệ của object trong cảnh, nên phần cài đặt so sánh khoảng cách góc giữa mặt trời và mặt trăng cùng bán kính góc của từng bên
sunVisibility xử lý trường hợp mặt trăng không che mặt trời, trường hợp mặt trăng che khi nhìn từ camera thấy nó lớn hơn hoặc gần bằng mặt trời, và trường hợp mặt trăng che khi từ góc nhìn camera nó đi vào trong bán kính mặt trời
- Demo bổ sung
sunVisibility và mesh mặt trăng lên ví dụ tán xạ khí quyển hiện có, để khi căn mặt trăng thẳng hàng với mặt trời, Atmospheric Scattering shader sẽ xử lý tình huống thiếu sáng
- Mô phỏng nhật thực và vành nhật hoa tinh vi hơn được trình bày trong bài báo Physically Based Real-Time Rendering of Eclipses, nhưng phần triển khai của bài báo này không được port sang WebGL
Khí quyển của các hành tinh khác
- Mô hình mật độ khí quyển và tán xạ được dùng chủ yếu được quyết định bởi bán kính của hành tinh và khí quyển, cùng một số hằng số như
RayleighScaleHeight, RayleighBeta, MieScaleHeight, MieBeta, mieBetaExt, mieG, OzoneHeight, OzoneWidth
- Điều chỉnh các giá trị này có thể tạo ra kết quả gần với khí quyển Sao Hỏa hoặc khí quyển của các hành tinh khác
- Các giá trị dùng cho Sao Hỏa là xấp xỉ
planetRadius: 3390
atmosphereRadius: 3500, dày khoảng 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'
- Thay các hằng số hiện có bằng những giá trị này sẽ cho ra bầu khí quyển bụi hơn và ngả cam hơn, đồng thời cũng tạo được sắc xanh đặc trưng lúc hoàng hôn của Sao Hỏa
- Có thể tham khảo bài báo liên quan Physically Based Rendering of the Martian Atmosphere
Tán xạ khí quyển dựa trên LUT
-
Cách tiếp cận và những phần đã lược bớt
- Shader trước đây có thể kết xuất khí quyển ở cả quy mô nhỏ lẫn lớn một cách trực quan, nhưng chi phí chạy cao do vòng lặp raymarching với nhiều
PRIMARY_STEPS, vòng lặp lồng nhau lightmarching và việc tính toán ở độ phân giải toàn màn hình
- A Scalable and Production Ready Sky and Atmosphere Rendering Technique của Sebastian Hillaire đề xuất phương pháp dựa trên Look Up Tables (LUTs), trong đó các phép tính tán xạ tốn kém được lưu vào texture, rồi ở bước render cuối sẽ lấy mẫu và tổng hợp các texture đã tính trước
- Các LUT được đề cập gồm Transmittance LUT lưu lượng ánh sáng còn lại sau khi đi qua khí quyển, Sky-view LUT lưu màu bầu trời tại một vị trí camera cụ thể, và Aerial Perspective LUT lưu haze khí quyển cùng ánh sáng tán xạ giữa camera và hình học cảnh đang nhìn thấy
- Tôi không chuyển nguyên xi toàn bộ phần triển khai trong bài báo; LUT phù hợp với compute shader của WebGPU, nhưng do thiếu thời gian và để giữ mạch bài viết nên vẫn dùng WebGL
- Trong bài báo, Aerial Perspective LUT là 3D texture, nhưng trong phần triển khai này dùng render target 2D
- Cách này đòi hỏi phải tạo lại texture để có giá trị pixel chính xác mỗi khi camera di chuyển, nên khó có thể tính sẵn trước
- Multi-Scattering bị lược bỏ do thiếu thời gian
-
Transmittance LUT
- Trong shader cũ, mọi điểm mẫu đều gọi
lightmarch để tính xem ánh sáng mặt trời tới được bao nhiêu, và quá trình này rất tốn kém
- Transmittance LUT lưu sẵn dữ liệu này ở độ phân giải thấp để các LUT khác có thể đọc lại khi cần dữ liệu ánh sáng
- Phần triển khai định nghĩa một Frame Buffer Object riêng với độ phân giải
250 x 64, áp dụng material shader tùy chỉnh lên full-screen quad của scene chuyên dụng transmittanceLUTScene, rồi truyền texture kết quả render làm uniform cho các LUT downstream
- Tại mỗi pixel, quá trình raymarching bắt đầu từ
vec3(0.0, radius, 0.0), trong đó radius tăng từ planetRadius tới atmosphereRadius theo tọa độ vUv.y
- Trục x của LUT biểu diễn góc ánh sáng, trục y biểu diễn độ cao; màu trắng thuần là độ truyền qua
100%, còn vùng đen hoặc có màu biểu thị mặt đất hoặc phần không khí dày nhất
- Sau đó, các LUT khác có thể lấy được “lượng ánh sáng sống sót sau khi đi qua khí quyển ở một góc và độ cao cho trước” chỉ bằng một lần tra cứu texture
-
Sky-view LUT
- Sky-view LUT tính màu bầu trời khi nhìn lên từ mặt đất theo một hướng cụ thể
getSkyViewRayDir ánh xạ vUv.x sang azimuth [-PI, PI], và vUv.y sang elevation [-PI/2, PI/2] để xác định hướng raymarching
- Với elevation, nó dùng ánh xạ bậc hai
(vUv.y * vUv.y - 0.5) * PI như một cách lách để tránh việc Sky View bị nhấp nháy quá nhiều ở khoảng cách xa
- Nếu tia không đi vào khí quyển thì trả về màu đen; với tia chạm vào hành tinh, chỉ raymarch phần khí quyển nhìn thấy được và dừng sớm hơn khi chạm hành tinh
- Vòng lặp tán xạ giống như trước, nhưng tiến dọc theo hướng Sky View và dùng Transmittance LUT cho ánh sáng mặt trời
-
Aerial Perspective LUT
- Khác với bài báo của Hillaire, kết quả triển khai là texture 2D, và mỗi pixel tương ứng với đúng một pixel màn hình đang nhìn thấy
- Nó dùng depth buffer của cảnh để xác định cần march xa bao nhiêu dọc theo tia đó và tích lũy tán xạ đến đâu
- Gần như tái sử dụng nguyên mã tán xạ cũ, chỉ khác là mỗi mẫu sẽ lấy độ khả kiến của ánh sáng mặt trời từ Transmittance LUT
- Đầu ra lưu tán xạ khí quyển tích lũy vào RGB, còn alpha lưu giá trị view transmittance đã được packed để dùng khi compositing
- Luồng triển khai là đọc độ sâu từ
depthBuffer, khôi phục vị trí không gian thế giới của pixel màn hình bằng getWorldPosition(vUv, depth), rồi tính rayDir từ vị trí camera tới vị trí thế giới đó
- Sau đó dùng
logDepthToRayDistance(vUv, depth) để chuyển độ sâu cảnh thành khoảng cách tia, tính giao cắt với khí quyển và hành tinh, rồi chỉ march phần khí quyển nhìn thấy được
-
Tổng hợp
- Sau khi tạo Sky-view LUT và Aerial Perspective LUT, bước post-processing cuối sẽ kết hợp cả hai
- Công việc cốt lõi là chuyển
rayDir hiện tại sang tọa độ UV của Sky View
- Với hình học cảnh, áp dụng Aerial Perspective LUT; kênh alpha được dùng làm view transmittance, còn kênh RGB là ánh sáng tán xạ, để tính
color = color * aerialPerspective.a + aerialPerspective.rgb
- Với pixel nền, lấy mẫu từ Sky View LUT; nếu
depth >= 1.0 - 1e-7 thì xem là nền và áp dụng color = inputColor.rgb + sampleSkyViewLUT(rayDir, planetCenter)
- Cuối cùng áp dụng
ACESFilm(color) và pow(color, vec3(1.0 / 2.2))
- Có thể xem toàn bộ mã triển khai khí quyển dựa trên LUT tại Github link
Kết lại
- Kết quả tán xạ khí quyển dựa trên LUT có thể trông gần như giống phiên bản raymarching hoàn chỉnh trước đó, nhưng quy trình bên trong thì khác
- Công việc được chia thành các LUT nhỏ hơn rồi tổng hợp ở hiệu ứng cuối, thay vì ở mỗi mẫu lại raymarch lặp đi lặp lại về phía mặt trời để tính lượng ánh sáng đến được
- Nhờ lấy trực tiếp thông tin chiếu sáng từ Transmittance LUT, nó thay các vòng lặp lồng nhau tốn kém bằng các lần tra cứu texture đơn giản và mang lại cải thiện hiệu năng đáng kể ở cảnh cuối
- Phần triển khai này vẫn còn kém hơn so với Sébastian Hillaire và các triển khai trong lĩnh vực khác; đặc biệt Sky View còn bị banding và flickering, và do các phần bị lược bớt nên cũng chưa thật sự tối ưu
- Có lẽ ngay từ đầu nên dùng WebGPU
- Nếu cần một triển khai production-grade thực sự, tôi khuyên dùng three-geospatial của Shoda Matsuda(@shotamatsuda)
- Tôi cũng đã làm thêm phần chồng volumetric clouds lên trên, nhưng kết quả hiện vẫn còn lẫn lộn và chưa đủ hài lòng để đưa vào bài viết, nên cần làm thêm nữa
1 bình luận
Ý kiến trên Hacker News
Có một kiểu thú vị rất riêng khi phát triển hiệu ứng hình ảnh rồi nhìn nó dần dần trở nên giống thực tế hơn, và tôi cũng muốn tự mình thử nghiệm lĩnh vực này vào một ngày nào đó
Trước đây video của anh ấy đạt hàng triệu lượt xem, giờ thì ngay cả 500 nghìn cũng chỉ vừa vượt qua. Cũng có thể là do thời kỳ COVID mọi người đều ở nhà và chú ý đến những thứ ngẫu nhiên hơn
Tôi thường bật lên khi đi ngủ, và vì tôi rất muốn có thêm kiểu nội dung đào sâu các chủ đề kỹ thuật theo cách điềm tĩnh nhưng sâu sắc như vậy nên đã từng nghĩ đến chuyện tự làm
Sau khi mặt trời lặn, trong một khoảng thời gian, tầng khí quyển phía trên đầu và vùng phía trên đường chân trời vẫn còn nhận ánh sáng mặt trời, và trong khí quyển Trái Đất, ánh chạng vạng dễ nhận thấy vẫn còn cho đến khi mặt trời xuống tới 18 độ dưới đường chân trời. Có thể dùng dò tia thì không thực tế lắm, nhưng có những thuật toán phổ biến để mô hình hóa điều này
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...
Tôi còn nhớ mình đã từng triển khai bài “Display of The Earth Taking into Account Atmospheric Scattering” của Nishita và cộng sự, một bài báo từ năm 1993 gần như là tiền thân của chủ đề này và rất dễ đọc: https://www.researchgate.net/publication/2933032_Display_of_...
Khi làm cho nó chạy được, tôi đã có khoảnh khắc nhận ra rằng “à, hóa ra hiện tượng thế giới thực phức tạp này có thể được mô hình hóa khá tốt chỉ bằng vài phép tính tương đối đơn giản”. Từ một skybox xanh tĩnh, nó lập tức chuyển thành một chu kỳ ngày đêm hoàn chỉnh
Trước đây tôi từng nghĩ thử kết xuất bầu trời trên web bằng cách chồng nhiều gradient lên nhau xem sao. Có lẽ tôi đã thành công ở mức nào đó và cho ra kết quả tạm ổn, nhưng hoàn toàn không thể so với thứ được làm ở đây. Thành phẩm rất ấn tượng và truyền cảm hứng
Chỉ riêng điều đó thôi cũng đã cho ra chu kỳ hoàng hôn/bình minh khá thuyết phục, và nếu tôi nhớ không nhầm thì ngay cả mặt trời cũng như tự nhiên xuất hiện từ đó. Tôi đã dùng XNA, nền tảng phát triển game C# của Microsoft, và làm theo loạt hướng dẫn tuyệt vời của Riemer; bản lưu trữ ở đây https://github.com/SimonDarksideJ/XNAGameStudio/wiki/Riemers...
Tuy nhiên tôi không thấy phần về tán xạ ở đó, nên có thể tôi đã lấy phần đó từ chỗ khác. Tôi vẫn nhớ đã đọc các bài báo có công thức
https://spaceengine.org/
Câu trả lời cho “SpaceEngine có bao nhiêu vật thể?” là toàn bộ danh mục sao Hipparcos, toàn bộ ngoại hành tinh đã biết, hơn mười nghìn thiên hà, phần lớn các vật thể trong Hệ Mặt Trời, tổng cộng 130 nghìn, cộng thêm còn nhiều thiên hà và hệ sao hơn cả số thực sự tồn tại trong toàn bộ vũ trụ quan sát được. Với “Làm sao một hành tinh nước có thể nóng được?”, câu trả lời là nước ở tầng khí quyển trên là hơi nước nóng, nhưng khi xuống thấp hơn thì chuyển mượt sang dạng lỏng dưới áp suất cao, và sâu hơn nữa thì thành trạng thái rắn gọi là ice VII. Còn câu trả lời cho “Di chuyển thế nào?” là các phím WASD
Đây là một game tuyệt vời, và dù đã khá cũ, tôi vẫn chưa thấy thứ gì tốt đến vậy
Đọc bài này làm tôi cũng nghĩ ngay đến SpaceEngine
Một trong những bài báo tôi thích: http://www.graphics.stanford.edu/papers/bssrdf/bssrdf.pdf
Có lẽ đây là lần đầu tôi biết rằng kết xuất sữa là một bài toán khó nhằn
Có lẽ tôi chỉ hiểu được khoảng 5%, nhưng vẫn cực kỳ thán phục
Hơn nữa, nếu là giấy phép MIT thì coi như vấn đề skybox trong game của tôi đã được giải quyết. Vì phối cảnh sẽ cố định, tôi chỉ cần phần kết xuất mặt trời di chuyển qua bầu trời, rồi có thể mở rộng bằng chu kỳ sóng sin cho sự thay đổi góc mặt trời theo mùa trong năm