- Mặt trăng và hành tinh khí dùng làm nền trời có thể biểu diễn bề mặt, chuyển động quay và khí quyển chỉ bằng một đĩa tròn và pixel shader, không cần mesh hình cầu thật
- UV sphere truyền thống dễ ánh xạ texture 2×1, nhưng kéo theo đường viền góc cạnh, mất mẫu mipmap, mẫu lặp, seam, méo ở cực và giới hạn của halo khí quyển
- Cách dùng đĩa khôi phục vị trí bề mặt cầu từ UV tâm, rồi dùng UV cầu dựa trên
asin, ma trận quay và spin bằng cách dịch tọa độ u để tạo bề mặt trông như hình cầu ở cấp pixel
- Phần shading bắt đầu từ chiếu sáng Lambertian dùng vị trí bề mặt làm normal; với bề mặt đá áp dụng ma trận TBN và normal map, còn khí quyển thì xấp xỉ độ sáng halo và hấp thụ theo từng kênh
- Có thể xử lý texture tĩnh từ Substance Designer và texture render động trong Unreal theo cùng một cách, giảm gánh nặng hiệu năng bằng texture động nhỏ hơn và điều chỉnh tiling
Mục tiêu thị giác của hành tinh trong skybox
- Skybox của dự án tập trung tạo bố cục giống bầu trời ngoài hành tinh với mặt trăng có hoạt ảnh và hành tinh khí
- Hai hành tinh quay, và hành tinh khí còn có dòng khí quyển chuyển động
- Chuyển động thực tế khá tinh tế, nhưng được tăng tốc để tạo hiệu ứng thị giác
- Hướng mỹ thuật là phong cách bán hiện thực (semi-realistic) lấy cảm hứng từ tranh SF thập niên 1970~80
Đầu vào texture bề mặt và ràng buộc
- Bề mặt hành tinh khí được tạo thời gian thực bằng pixel shader và cơ chế render-to-texture
- Texture mặt trăng được tạo trong Substance Designer, có thể thay đổi bảng màu và material seed để thử nhiều look khác nhau
- Hành tinh khí động phải render texture mỗi frame nên chi phí hiệu năng lớn
- Tăng độ phân giải texture lên 2 lần thì chi phí render tăng 4 lần
- Cần một giải pháp hoạt động nhất quán cho cả texture tĩnh từ Substance Designer lẫn texture render động trong Unreal
- Cubemap ban đầu đã được cân nhắc, nhưng có thể dùng tài nguyên gấp 6 lần so với texture vuông, và Substance Designer không hỗ trợ tạo cubemap nên không thực tế
Giới hạn bộc lộ ở UV sphere
- UV sphere trông như lựa chọn mặc định vì dễ khớp tọa độ với texture chữ nhật 2×1
- Vì mỗi lần chỉ thấy một phía của hình cầu, cũng có thể dùng texture vuông dạng tile thay vì một texture chữ nhật độc nhất cho toàn bộ bề mặt
- Khi áp dụng thực tế, nhiều vấn đề chồng lên nhau
- Đường viền trông góc cạnh rõ rệt, cần tăng subdivision hoặc che bằng mask trong pixel shader
- Phần lớn bề mặt cầu được nhìn xiên nên bị lấy mẫu từ mipmap thấp, và với texture động, nhiều pixel tạo ra mỗi frame được dùng cho vùng gần như không thấy được
- Tiling phải tăng theo bội số nguyên thì toàn bộ pattern mới nối liền, và chỉ cần tăng 2 lần là đã thấy lặp
- Fractional tiling tạo ra seam chạy từ cực Bắc đến cực Nam
- Ở các cực xuất hiện texture pinching và méo ánh xạ tam giác, trông tương tự méo texture rung lắc của PSX
- Chỉ có thể vẽ bề mặt, nên atmospheric halo cần một model riêng
- UV sphere vẫn dùng được cho mô hình hóa hành tinh, nhưng với mục đích skybox thì phải sửa và hack nhiều
Cách tiếp cận bằng đĩa tròn và pixel shader
- Hành tinh trong skybox được quan sát từ một vị trí ở xa, nên không nhất thiết cần mesh cầu 3D phức tạp
- Dùng một đĩa polygon tròn được tô kín đơn giản, rồi xử lý ánh xạ texture trong pixel shader
- Có thể vẽ cả atmospheric halo trong cùng một mesh
- Gốc UV của đĩa phải nằm chính xác ở tâm
- Nếu xem bán kính hành tinh là 1, tọa độ UV phải mở rộng ra ngoài mức đó để có không gian vẽ khí quyển
Khôi phục bề mặt cầu
- Hệ tọa độ sử dụng hệ left-handed, Y-up nhất quán với DirectX
- Unreal Engine cũng là left-handed nhưng hướng up là Z, nên cần kiểm tra hướng và định dạng normal map
- Phương trình bề mặt ánh xạ vị trí 2D trên mặt phẳng đĩa sang vị trí 3D trên bề mặt cầu
- Trước tiên, để xác định pixel có nằm trong bề mặt cầu hay không, kiểm tra độ dài vector UV có nhỏ hơn bán kính hay không
float CircleMask( float2 uv, float radius)
{
return length(uv) < radius? 1.0: 0.0;
}
- Cách này chỉ hoạt động đúng khi tọa độ UV được căn thẳng với tâm mesh
- local position của bề mặt cầu dùng nguyên x, y của UV và chỉ khôi phục z
float3 ReconstructSurface(float2 uv)
{
float zSquared = 1.0 - dot(uv, uv);
float z = sqrt(zSquared);
return float3(uv, z);
}
Tạo UV cầu
- Quá trình ánh xạ texture vuông lên bề mặt cầu được chia thành ba bước
- Quấn quanh hình trụ
- Lặp lại xử lý tương tự trên trục y
- Biến đổi kết quả thành dạng tròn
- Tọa độ texture x tỉ lệ với góc quấn quanh hình trụ; tính
arcsine(x) rồi ánh xạ lại [-π, π] sang [0, 1]
- Dùng generatrix, tức bề rộng local của hình cầu, để pinching tọa độ ở các cực
float2 GenerateSphericalUV(float3 position)
{
float width = sqrt(1.0 - position.y * position.y);
float generatrixX = position.x / width * sign(position.z);
float2 generatrix = float2(generatrixX, position.y);
float2 uv = asin(generatrix) / 3.14159 + float2(0.5, 0.5);
return float2(uv);
}
Độ nghiêng trục và xoay
- Để bố cục hành tinh tự nhiên hơn, nghiêng trục hành tinh bằng pitch và roll
- Hành tinh trong tranh SF thường bị nghiêng, giúp bố cục trông năng động và có thể cho thấy ice cap vùng cực
- Yaw tương ứng với chuyển động quay, và được xử lý ở bước riêng để tránh vấn đề seam
- Với mesh cầu, thường biến đổi hành tinh bằng ma trận; nhưng ở cách dùng đĩa, mỗi pixel định nghĩa vị trí bề mặt nên có thể dùng một ma trận 3×3 đơn giản chỉ chứa phép quay
float3x3 CreateRotationMatrix(float pitch, float roll) {
float cosPitch = cos(pitch);
float sinPitch = sin(pitch);
float cosRoll = cos(roll);
float sinRoll = sin(roll);
return (float3x3)(
cosRoll, -sinRoll * cosPitch, sinRoll * sinPitch,
sinRoll, cosRoll * cosPitch, -cosRoll * sinPitch,
0.0, sinPitch, cosPitch
);
}
- Material editor của Unreal không hỗ trợ ma trận như một kiểu dữ liệu, nhưng có cách lách
Xử lý scale, seam, spin
- Khi thay đổi scale của texture, UV seam vốn được giấu ở rìa texture ban đầu sẽ lộ ra
- Seam không thể loại bỏ hoàn toàn một cách dễ dàng, nhưng nếu chuyển nó ra phía sau thì sẽ ít thấy hơn
- Chia hình cầu thành các góc phần tư theo sign của position, rồi áp dụng offset theo hướng u cho các góc phần tư back-left và back-right
float2 GenerateSphericalUV(float3 position, float spin, float scale)
{
float leftRightSign = sign(position.x);
float frontBackSign = sign(position.z);
float width = sqrt(1.0 - position.y * position.y);
float generatrixX = position.x / width * frontBackSign;
float2 generatrix = float2 (generatrixX, position.y);
float2 uv = asin(generatrix) / 3.14159 + float2 (spin, 0.5);
if(frontBackSign < 0.0)
{
uv = float2 (uv.x + 1.0 * leftRightSign, uv.y);
}
return float2 (uv / scale);
}
- Seam vẫn nối cực Bắc và cực Nam, nhưng đã được chuyển ra phía sau nên ít lộ hơn nhiều
- Chuyển động quay được xử lý bằng cách dịch texture bề mặt theo hướng tọa độ u, chứ không xoay chính hình cầu
float2 GenerateSphericalUV(float3 position, float scale, float spin)
{
float width = sqrt(1.0 - position.y * position.y);
float generatrixX = position.x / width * sign(position.z);
float2 generatrix = float2(generatrixX, position.y);
float2 uv = asin(generatrix) / 3.14159 * scale + float2(0.5 + spin, 0.5);
return float2(uv);
}
float2 sphericalVU = GenerateSphericalUV(position, scale, time*speed)
Shading bề mặt và normal map
- Để hành tinh trông tròn, shading rất quan trọng
- Khác với shading trong engine thông thường, hành tinh có hai layer là bề mặt và khí quyển, nên phải tự định nghĩa ánh sáng và blending cho hai layer
- Khi bán kính là 1, vị trí bề mặt trước khi quay có thể dùng ngay làm surface normal
- Ánh sáng dùng mô hình Lambertian đơn giản
- Mục tiêu không phải hiện thực tuyệt đối, mà là thẩm mỹ bìa SF cách điệu lấy cảm hứng từ ảnh NASA
float LambertianLight(float3 normal, float3 lightDirection) {
float NdotL = max(dot(normal, lightDirection), 0.0);
return NdotL;
}
- Bề mặt mịn như gas giant có thể chỉ cần normal đơn giản là đủ
- Với hành tinh đá, nên mô phỏng địa hình như núi, sống núi, hố va chạm bằng normal map
- Để dùng normal map cần ma trận TBN gồm Tangent, Bitangent, Normal
- Với hình cầu, phép tính này phải thực hiện theo từng pixel
- Ma trận TBN được dùng để chuyển normal từ normal map sang World Space Coordinates
Bất liên tục UV và artifact mipmap
- UV tạo trong pixel shader có thể gặp vấn đề bất liên tục (discontinuity)
- Back seam cần thành phần UV ngang nối hoàn toàn từ 0.0 sang 1.0 ở biên, nhưng thực tế sẽ xuất hiện artifact dạng block dọc theo seam
- Đường này tương ứng với DDX của UV
- DDX và DDY đo tốc độ thay đổi UV theo các trục trong screen space
- Texture sampler dùng các giá trị này để quyết định mipmap sẽ dùng
- UV derivative thấp tương ứng với mipmap độ phân giải cao, còn derivative cao tương ứng với mipmap độ phân giải thấp
- Khi giá trị hai bên seam nhảy đột ngột, DDX tăng lớn và texture bị lấy mẫu từ mipmap thấp nhất, có thể trông như một đường xám
- Trên hành tinh, vùng này sẽ được che bằng polar patch nên không quá nghiêm trọng; nhưng nếu thấy rõ, cần tự hiệu chỉnh DDX và DDY của seam rồi truyền cho sampler
- Ví dụ fix chỉ nhằm minh họa; giá trị thực tế phải tự suy ra công thức cho phù hợp với mapping
Polar patch và hiệu chỉnh pinching
- Méo ở cực được che bằng polar patch theo cách đã được kiểm chứng
- Trên hành tinh hoặc mặt trăng, vùng cực thường phủ băng và khác biệt thị giác so với các vùng khác, nên một patch texture riêng vừa che được vấn đề vừa tăng điểm nhấn thị giác
- Có thể thêm polar patch bằng cách ánh xạ lên một mặt phẳng vuông góc với trục quay rồi xoay tọa độ kết quả
float2 PolarPatchMapping(float3 position, float scale, float spin)
{
float cosSpin = cos(spin);
float sinSpin = sin(spin);
float scale = 0.4;
float2 uv = float2(position.x, position.z) * scale;
float2 spinningUV = float2(uv.x * cosSpin - uv.y * sinSpin,
uv.x * sinSpin + uv.y * cosSpin);
return spinningUV;
}
- Vì UV map không được lưu ở vertex mà được tạo cho từng pixel, việc hiệu chỉnh pinching ở cực linh hoạt hơn
- Có thể hiệu chỉnh aspect ratio của texture vùng cực bằng một phép chia đơn giản
uv = float2(uv.x, uv.y/pow(width, 1/3));
- Kết quả hiệu chỉnh có thể trông như một artifact khác, nhưng trong cảnh thực tế, nó nối với polar patch mượt hơn và giảm bất nhất thị giác
Xấp xỉ halo khí quyển
- Vì bề mặt hành tinh không chiếm toàn bộ vùng đĩa, có thể vẽ atmospheric halo ở phần không gian còn lại
- Nếu vẽ halo trong cùng shader, nó có thể hòa tự nhiên với khí quyển trên hành tinh
- Render khí quyển dựa trên vật lý thường dùng Rayleigh scattering và raymarching, nhưng ở đây không dùng
- Sử dụng một xấp xỉ tương tự trick chiếu sáng đối tượng động trong Quake 1·2
- Static lighting của Quake được tính trước bằng lightmap
- Quái vật động lấy mẫu lightmap bên dưới để điều chỉnh màu; dù không chính xác về vật lý nhưng giúp chúng hòa với môi trường
- Theo cách tương tự, dùng bề mặt hành tinh để xấp xỉ ánh sáng của atmospheric halo
- Mở rộng surface normal trước bump mapping để tính độ sáng halo
- Remap độ sáng theo từng kênh khác nhau có thể mô phỏng đơn giản sự hấp thụ các bước sóng ánh sáng khác nhau
- Khí quyển dễ thấy hơn ở góc xiên, nên remap thành phần Z của surface normal
- Cũng tính khoảng cách từ bề mặt cầu để tạo hiệu ứng halo fade
- Cuối cùng, gộp bề mặt và khí quyển bằng alpha blending
Kết quả cuối cùng và ứng dụng
- Tuy cần nhiều bước hơn dự kiến, kết quả cuối cùng trông như một hình cầu hoàn chỉnh và hỗ trợ các thao tác texture·shader mong muốn
- Có thể tạo texture bề mặt bằng một graph Substance Designer duy nhất, và kết quả không cần hậu xử lý bổ sung
- Texture có hoạt ảnh cũng có thể xử lý theo cùng cách
- Có thể dùng kích thước texture nhỏ hơn để tiết kiệm hiệu năng
- Có thể điều chỉnh tiling theo mức cần thiết
- Có thể xem video kết quả tại Video 3
1 bình luận
Các ý kiến trên Hacker News
Có vẻ tác giả đã loại trừ cubemap quá nhanh; khi dùng nó để render một hành tinh khí động trong dự án cá nhân, tôi thấy đây là giải pháp đơn giản nhất.
Dùng cubemap không làm bộ nhớ tăng gấp 6 lần; về bản chất là chia một texture hình chữ nhật lớn thành 6 mặt chữ nhật nhỏ, nên tổng mức chi tiết của texture vẫn như nhau.
Ưu điểm là không phải bận tâm đến hiện tượng nén ở hai cực, và có thể dễ dàng tạo trường dòng chảy liền mạch để hoạt ảnh/biến dạng texture bằng hàm nhiễu 3D hoặc 4D.
https://www.junkship.net/News/2016/06/09/jupiter-jazz
Mỗi bán cầu được chiếu thành một đĩa tròn, nhưng có thể lấp texture đến tận biên hình vuông và chồng lặp một phần của mỗi bán cầu lên các góc của texture bên kia.
Vì có chênh lệch tỉ lệ 1:2 giữa tâm và rìa đĩa, có thể xem là lãng phí pixel nếu tính theo mức chi tiết tối thiểu; nhưng vì đây là phép chiếu đồng góc, nó khiến cách lấy mẫu màu pixel đích ở các góc nhìn xiên gắt bớt khó hơn nhiều, và tính xuôi/ngược cũng chỉ cần 1 phép chia cùng vài phép cộng, nhân cho mỗi điểm chiếu, rẻ hơn cả phép chiếu xuyên tâm của cubemap.
Nếu cần một phương pháp đồng góc không quá khó về mặt khái niệm nhưng giảm thêm biến thiên tỉ lệ và lãng phí pixel ở góc, có thể dùng 2 phép chiếu Mercator hơi chồng lên nhau theo các hướng vuông góc để phủ hình cầu như hai mảnh da của quả bóng chày.
Mỗi mảnh có thể là một texture hình chữ nhật, và trong các bài báo của NOAA cũng có bài đề xuất cách tiếp cận này cho lưới phương trình vi phân trong mô phỏng thời tiết Trái Đất.
Phép chiếu tiết kiệm pixel nhất mà tôi biết là chia hình cầu thành một bát diện, rồi phủ từng octant bằng lưới pixel lục giác dựa trên “tọa độ diện tích cầu”.
Mỗi octant có thể được biểu diễn trong ảnh pixel vuông thông thường như nửa hình vuông, tức tam giác vuông 45-45-90, cho ra kết quả như https://observablehq.com/@jrus/sac-quincuncial, và cũng có thể dùng lưới lục giác như https://observablehq.com/@jrus/sphere-resample.
Tuy nhiên, xử lý chi tiết khi phải lấy mẫu vượt qua đường biên rắc rối hơn nhiều so với cách dùng 2 phép chiếu lập thể, và vẫn có thể sinh artifact ở đường nối.
Cần nhiều toán hơn, nhưng nếu không định chia nhỏ bề mặt thêm vì lý do khác thì thường không đáng.
Hiện tượng nén texture ở cực thực ra là dạng biểu hiện cực đoan của méo tồn tại trên toàn bộ bề mặt.
Thường nó chỉ rõ ở các cực, nhưng nếu chia nhỏ thích nghi hình cầu thành các tam giác, mức méo cũng thay đổi khi cấp độ chia nhỏ thay đổi, nên có thể nhìn thấy ở những nơi khác.
Vấn đề là hình cầu được chia thành các tứ giác, và mỗi tứ giác trong không gian UV được biểu diễn bằng 2 tam giác có cùng diện tích, nhưng trong không gian 3D thì một tam giác—đặc biệt là tam giác có cạnh ngang gần cực hơn—lại nhỏ hơn.
Dù vậy, UV vẫn được nội suy tuyến tính bên trong tam giác, nên một nửa texture bị thu nhỏ còn nửa kia bị kéo giãn.
Ở cực, diện tích 3D của một tam giác thực sự trở thành 0, khiến chỉ một nửa texture được render và đường nối giữa các tam giác trở nên rõ rệt.
Cách giải đúng là tính tọa độ UV cho từng pixel trong pixel shader thay vì nội suy tuyến tính theo đỉnh; nếu làm đúng, ngay cả cực cũng được xử lý liền mạch.
Ví dụ khi render một hình cầu có cực “chính bắc”, nếu nhìn từ bên cạnh thì dùng cực render phía chính bắc, còn nếu nhìn từ gần chính bắc thì dùng render theo xích đạo ở 0',0’.
Tôi thấy muốn xem lại displacement mapping.
Có lẽ nó không thay thế được vấn đề tác giả đang cố giải, nhưng đơn giản hơn và khá thú vị.
Khoảng 25 năm trước, tôi đã làm một visualizer âm nhạc tên là “Eclipse” cho SoundJam; đầu vào là mảng mức tín hiệu trong dải tần nghe được và hai kênh trái/phải.
Mục tiêu là tạo hình ảnh giống một mặt trời bị nhật thực với các luồng phát xạ corona, và dữ liệu nhạc trở thành các “luồng phát xạ” đó.
Tần số của dữ liệu quyết định nó xuất hiện ở đâu quanh vành đĩa mặt trời.
Theo thời gian, các luồng phát xạ đi xa khỏi mặt trời và “nguội đi” rồi biến mất thành màu đen; tín hiệu mạnh bắt đầu bằng màu trắng, rồi khi yếu dần chuyển sang vàng, cam, đỏ và nâu.
Tôi phải giữ một mảng giá trị dữ liệu âm thanh trong một bộ đệm vòng đủ lớn để chứa khoảng thời gian trước khi luồng phát xạ biến mất thành màu đen.
Toàn bộ phần hiển thị “Eclipse” và các luồng phát xạ cuối cùng là một displacement map, và tôi đã tính sẵn một bitmap trong đó giá trị của từng pixel là offset vào bộ đệm mức âm thanh.
Pixel gần bề mặt mặt trời có offset tới dữ liệu mới đi vào, còn pixel ở phía ngoài có offset tới phần đuôi bộ đệm sắp hết hạn.
Vì phải ánh xạ phát xạ tròn/xuyên tâm sang tọa độ Descartes, việc tạo giá trị dịch chuyển cần một chút toán.
Vòng lặp chính nhận giá trị âm thanh mới, ghi đè giá trị cũ nhất, rồi duyệt displacement map theo hàng và cột để lấy dữ liệu âm thanh tương ứng, ánh xạ nó sang màu trong bảng màu cố định và đưa vào bộ đệm hiển thị.
Nó không hào nhoáng như các visualizer khác, nhưng có vẻ đẹp trầm tĩnh và phản ánh dữ liệu nhạc khá tốt.
Khác với các visualizer về sau, nó không có cảm giác như vẫn không thể đứng yên ngay cả khi đưa vào khoảng lặng.
Bài viết rất hay, nhưng càng cuộn xuống dưới càng tải nhiều shader, nên nếu không phải máy tính rất mạnh thì trình duyệt có thể gần như treo.
Thật thú vị khi thấy mọi người diễn giải “tính chân thực” khác nhau đến mức nào
Trong các game thời tài nguyên còn hạn chế hơn nhiều, sự khác biệt này đặc biệt rõ; có game chỉ với đồ họa vuông vức, pixelated và bảng màu thấp mà vẫn tạo cảm giác nhập vai hơn cả các game kinh phí lớn ngày nay
Vì vậy có lẽ người chơi đã nhập tâm chủ động hơn
Bài viết này về một cách tiếp cận tương tự làm tôi nhớ đến: https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92...
GPU hay thư viện 3D không có hàm nào quét ngang từng dòng của hình tròn rồi ánh xạ X,Y của texture vào vị trí “3D” trên hình cầu sao?
Hình cầu là hình tròn, thuật toán vẽ hình tròn thì đơn giản, còn việc xoay và chiếu cả triệu đỉnh cho tam giác trông như một sự lãng phí tài nguyên khổng lồ
Chỉ cần quét ngang, xuống một dòng rồi tiếp tục vẽ là có thể vẽ hết mọi dòng
Tôi nhớ cuối thập niên 80 từng làm việc kiểu này để tính trước những thứ như hiệu ứng thấu kính phóng đại trong Second Reality
Toàn bộ pipeline render vốn đã được thiết kế để xử lý nội suy tuyến tính
Chỉ với một chương trình shader và 1 tam giác cũng có thể vẽ được một hình cầu khử răng cưa hoàn hảo
Nếu render trên GPU thì bất chấp độ phức tạp bề ngoài, cách tiếp cận trong bài trông khá ổn
Có một loạt bài rất hay về kiến trúc render dựa trên GPU: https://fgiesen.wordpress.com/2011/07/09/a-trip-through-the-...
Phần 6 nói về rasterization
Hành tinh imposter bằng pixel shader 2D trên nền thật sự giúp ích rất nhiều cho các tác vụ có nhiều hành tinh, như vũ trụ procedural
Do bộ nhớ và băng thông GPU, hành tinh dựa trên spherical cube chỉ giới hạn ở 1 instance, còn các thiên thể ở gần được vẽ bằng kỹ thuật nền
Trong không gian có một khoảng cách nằm đâu đó giữa hai thiên thể mà cả hai đều được render dưới dạng imposter; sau đó thiên thể gần nhất được xử lý bằng phương pháp quadtree cầu
Không hoàn hảo, nhưng ảo giác này gần như khó bắt bẻ
Hơn nữa vì nằm trên mặt phẳng, việc thêm các hiệu ứng vật lý nhẹ của ống kính camera cho các thiên hà hay thiên thể ở xa cũng dễ hơn
Ví dụ như hiệu ứng mặt trăng khi mọc trông lớn hơn thực tế, hoặc thiên hà xa trông bị bẻ cong bởi hấp dẫn
Tôi thích trang liên quan đến hành tinh khí này: https://emildziewanowski.com/flowfields/
Icosphere có thể được trải phẳng mượt hơn nhiều, vị trí đỉnh và kích thước phần tử cũng đều đặn
Việc trải phẳng không hẳn tầm thường, nhưng vẫn làm được