pbr image là gì

Giới thiệu

Physically-Based Rendering hoặc PBR là 1 trong những tụ hội những phương thức/thủ thuật nhằm những vật thể 3 chiều render kể từ PC thiệt rộng lớn nhờ việc vận dụng những quy tắc cơ vật lý tương tự động như ở toàn cầu thực. Nhờ PBR tuy nhiên thời buổi này tất cả chúng ta rất có thể thấy được những kĩ xảo nhập game nhìn ko không giống là bao đối với thực tiễn. Bài trước của tôi sở hữu một quãng đối chiếu thân mật PBR với thuật toán tô màu sắc cổ xưa (Blinn-Phong) về những ưu điểm yếu, giờ thì hợp tác nhập mò mẫm hiểu cơ hội PBR hoạt động và sinh hoạt thôi!

Bạn đang xem: pbr image là gì

Các vật liệu + bề mặt không giống nhau được vận dụng lên và một khối cầu

Ai chỉ quan hoài cho tới code shader và mã mối cung cấp project rất có thể hiểu cho tới phần tiếp cuối bản thân tiếp tục tổ hợp lại toàn cỗ.

Lý thuyết

Toàn cỗ lý thuyết về PBR một cơ hội dễ nắm bắt nhất (bằng giờ Anh) rất có thể được liếc qua bài xích thuyết trình này: https://www.youtube.com/watch?v=j-A0mwsJRmk của Naty Hoffman. Kèm theo đòi slide và note chi tiết: Slide Note

Bạn này mong muốn coi lý giải của tôi thì nối tiếp hiểu nhé. Warning: Physics & Math incoming. Các hình hình ảnh sau đây đa số đều lấy mối cung cấp kể từ slide của Hoffman.

Microfacet

Tất cả những thiết lập và đo lường của PBR được dựa vào lý thuyết về những mặt mày siêu nhỏ (microfacet) bên trên mặt phẳng của một vật bất kì. Hãy đánh giá mặt phẳng một vật ở Lever siêu nhỏ, tất cả chúng ta coi mặt phẳng này "nhẵn" hoặc "nhám" dựa vào việc những microfacet được bố trí ra sao. Các microfacet này tương tự như là 1 trong những mặt phẳng con cái của mặt phẳng lúc đầu, và xử xử "gần như" một tấm gương. Chúng tớ đều biết rõ gương tiếp tục phản chiếu độ sáng nó có được trở nên một tia hành động tự nhiên với góc hành động tự nhiên vị góc cho tới (Vật lý 7). Khi mặt phẳng nhám, những mặt mày gương phía theo đòi những góc lếu láo loàn tiếp tục khiến cho độ sáng bị khuếch giã cực mạnh, nếu như sở hữu phản chiếu thì hình hình ảnh tạo ra vị mặt phẳng này cực kỳ lù mù. trái lại với mặt phẳng nhẵn, độ sáng gần như là được hành động tự nhiên theo đòi và một phía vì thế tớ thấy nó "bóng" rộng lớn (do độ sáng triệu tập "bay" nhập đôi mắt được không ít hơn).

Kim loại và non-kim loại

Tất nhiên ko nên từng mặt phẳng đều hành động tự nhiên toàn bộ độ sáng phản vào nó. Chúng tớ ko thể nhìn đợi một miếng vật liệu bằng nhựa phản chiếu độ sáng vị với cùng 1 tấm kính được. Và dùng quyết định lý bảo toàn năng lượng, chắc chắn phần tích điện thất lạc chuồn bại liệt nên trở nên một dạng gì bại liệt. Đó đó là phần độ sáng ko được hành động tự nhiên và được mặt phẳng vật hít vào. Phần này sẽ thay cho thay đổi tuỳ theo đòi đặc thù bề mặt!

Bởi độ sáng trông thấy thực tế là 1 trong những loại sóng năng lượng điện kể từ, vì thế so với sắt kẽm kim loại, cấu tạo phân tử của bọn chúng khiến cho bọn chúng hít vào toàn cỗ phần độ sáng này. Khác với sắt kẽm kim loại, những mặt phẳng ko nên sắt kẽm kim loại tiếp tục hít vào một trong những phần của độ sáng bại liệt, tích tích điện và trả nó rời khỏi (Vì sao? Vì nó ko nên kim loại những phân tử của bọn chúng thông thường tiếp tục bão hoà năng lượng điện, vì thế nó không thể mong muốn lưu giữ thêm thắt nhiều tích điện nữa). Đó là nguyên nhân Lúc tớ chiếu độ sáng vào trong 1 miếng vật liệu bằng nhựa, phần ko được phát sáng thẳng vẫn sáng sủa nhẹ nhõm lên.

Dựa nhập bại liệt, tớ rất có thể thấy phần độ sáng diffuse (khuếch tán), hỗ trợ chúng ta thấy được sắc tố mặt phẳng và phần specular (bóng), hỗ trợ chúng ta thấy sắc tố mối cung cấp sáng sủa là nhị phần hỗ trợ lẫn nhau. Nghĩa là tổng tích điện độ sáng lúc đầu sẽ tiến hành phân tích trở nên 2 bộ phận chủ yếu này.

kS = calculateSpecularFraction(...); // Tính tỉ trọng độ sáng phản xạ
kD = 1 - kS; // Tính tỉ trọng phần độ sáng bị hấp thụ

Halfway vector + Thành phần sáng sủa phản xạ

Lại một định nghĩa nữa bản thân ko biết dịch trở nên giờ Việt ra sao. Chúng tớ hãy nhìn lại hình mặt phẳng tại đây, chỉ xét với vector chỉ khoảng cách nhìn của tớ (camera) là vv và vector độ sáng kể từ mối cung cấp sáng sủa là ll, nhập số cực kỳ thật nhiều microfacet khuynh hướng về đầy đủ góc, chỉ những microfacet sở hữu vector pháp tuyến là hh (màu đỏ) mới mẻ góp sức độ sáng hành động tự nhiên nhập vật tớ thấy. Nghĩa là độ sáng sở hữu phản chiếu ở những microfacet không giống, song tớ chỉ xét cho tới những microfacet hỗ trợ chúng ta trông thấy độ sáng bên trên mặt phẳng tuy nhiên thôi.

Vector h này gọi là halfway vector. Nó được xem vị (l+v)/length(l+v). Kết hợp ý cùng theo với thông số roughness, tớ rất có thể tuỳ ý thay đổi thiên mặt phẳng vật thể Theo phong cách mình đang có nhu cầu muốn. phẳng cơ hội dùng công thức BRDF (bidirectional reflective distribution function - một hàm tính rời khỏi cường độ phản chiếu độ sáng bên trên bề mặt), tất cả chúng ta rất có thể ước tính ngay sát đúng mực độ sáng hành động tự nhiên kể từ vật thể Lúc nó nhận độ sáng phản vào. Hàm này được khái niệm như sau:

f(l,v)=F(l,h)G(l,v,h)D(h)4(nl)(nv)f(l,v) = \frac{F(l,h)G(l,v,h)D(h)}{4(n \cdot l)(n \cdot v)}

Hơi kinh sợ nhỉ? Chúng tớ hãy phẫu thuật nó rời khỏi từng phần nhằm nắm rõ nó rộng lớn.

Phản xạ Fresnel

f(l,v)=F(l,h)G(l,v,h)D(h)4(nl)(nv)f(l,v) = \frac{\boxed {F(l,h)} G(l,v,h)D(h)}{4(n \cdot l)(n \cdot v)}

Lượng độ sáng phản chiếu vị mặt phẳng vật thể tiếp tục thay cho thay đổi Lúc góc tạo ra vị tia nhìn và vector pháp tuyến bên trên mặt phẳng thay cho thay đổi. Tại 90°90\degree, mặt phẳng vật thể tiếp tục phản chiếu 100% độ sáng phản vào nó. Chúng tớ rất có thể trông thấy phần hành động tự nhiên này qua quýt hình minh hoạ mặt mày dưới:

Chúng tớ thường thấy phần rìa của ngược cầu (dù bóng hoặc nhám) sáng sủa nhất tự ở bại liệt góc tạo ra vị tia nhìn và pháp tuyến mặt phẳng là tối nhiều. Tuỳ nhập vật liệu tuy nhiên lượng độ sáng được phản chiếu cũng không giống nhau, tuy nhiên nó sẽ bị đạt cực kỳ tè Lúc góc nhỏ và đạt tối nhiều Lúc góc = 90°90\degree. Có nhiều phương pháp để ước tính bộ phận hành động tự nhiên này, tuy nhiên công thức của Schlick thịnh hành chính vì tính đúng mực và tiết kiệm ngân sách khoáng sản (tính toán của GPU) của chính nó. Theo bại liệt bộ phận này được xem như sau

F(l,n)=Fo+(1Fo)(1(ln))5F(l, n) = {F}_{o} + (1 - {F}_{o} )(1 - (l \cdot n))^{5}

nn ở phía trên đó là vector halfway tuy nhiên tất cả chúng ta kể phía trên. Fo{F}_{o} là đại lượng đặc thù mang đến vật liệu mặt phẳng. Với sắt kẽm kim loại thì độ quý hiếm thực của đại lượng này tương đối cao, đại lượng này hạn chế Lúc tính phi-kim loại tăng dần dần.

Bề mặt mày vật thể

f(l,v)=F(l,h)G(l,v,h)D(h)4(nl)(nv)f(l,v) = \frac{F(l,h) \boxed {G(l,v,h)} D(h)}{4(n \cdot l)(n \cdot v)}

Đây là bộ phận GG nhập công thức BRDF. Cho tớ biết phần trăm bên trên điểm tớ đang được xét liệu mặt phẳng microfacet sở hữu bị một khối này bại liệt lép vế hay là không. Hàm này nhận thông số là roughness của vật liệu. Kết hợp ý nhị ước tính GGX và Schlick-Beckman, tớ sở hữu một hàm ước tính bộ phận này như sau:

G(n,v,k)=nv(nv)(1k)+kG(n, v, k) = \frac{n \cdot v}{(n \cdot v)(1 - k) + k}

Với k là 1 trong những đại lượng tính kể từ roughness của vật tư. Tuỳ nhập khi tớ tính phản chiếu độ sáng thẳng (từ mối cung cấp sáng) hoặc kể từ môi trường xung quanh (IBL - image based lightning - đo lường độ sáng từ là một "ảnh 360 độ" kể từ môi trường xung quanh mang đến trước) tuy nhiên k rất có thể tính không giống nhau

kdirect=(α+1)28{k}_{direct} = \frac{{(\alpha + 1)}^{2}}{8}

kIBL=α22{k}_{IBL} = \frac{{\alpha}^{2}}{2}

Trong bại liệt α\alpha sẽ tiến hành quy thay đổi thẳng kể từ roughness.

Hàm phân phối

f(l,v)=F(l,h)G(l,v,h)D(h)4(nl)(nv)f(l,v) = \frac{F(l,h)G(l,v,h) \boxed {D(h)}}{4(n \cdot l)(n \cdot v)}

Thành phần DD (distribution) của BRDF. Chúng tớ tiếp tục mô phỏng một mặt phẳng không phẳng hoặc nhẵn bóng vị hàm này. Với độ quý hiếm roughness cao, mặt phẳng sẽ sở hữu được thật nhiều microfacet phía chuồn tình cờ, khiến cho độ sáng bị phân giã và "tối hơn". trái lại, so với mặt phẳng nhẵn, những microfacet ngay sát nhau tiếp tục triệu tập khuynh hướng về và một phía vì thế Lúc đặt tại chính khía cạnh, tớ sẽ sở hữu được một hình hình ảnh phản chiếu sắc đường nét (và nhỏ) của mối cung cấp sáng sủa.

Do đó là một hàm phân phối được đưa đến nhằm quy mô hoá thực tiễn, nên tớ rất có thể tuỳ ý chọn 1 hàm để thay thế thay đổi đặc điểm của "đốm sáng sủa phản xạ". Tại phía trên bản thân tiếp tục sử dụng hàm phân phối Trowbridge-Reitz GGX. Hàm này được thiết lập như sau:

float DistributionGGX(vec3 N, vec3 H, float a)
{
    float a2     = a*a;
    float NdotH  = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;
	
    float nom    = a2;
    float denom  = (NdotH2 * (a2 - 1.0) + 1.0);
    denom        = PI * denom * denom;
	
    return nom / denom;
}

Mẫu số

Bản hóa học của độ quý hiếm (nl)(nv)(n \cdot l)(n \cdot v) ở kiểu mẫu số của hàm BRDF đó là Geometry (tính hóa học bề mặt) của vật thể. Tuy nhiên nó ko nên của những mặt mày microfacet tuy nhiên là của tất cả khối vật thể. Để dễ dàng tưởng tượng, hãy suy nghĩ cho tới một ngược bóng bịa đặt trước một mối cung cấp sáng sủa. Phần ngược bóng khuynh hướng về phía mối cung cấp sáng sủa (có pháp tuyến nằm trong phương trái hướng với phía tia sáng) tiếp tục sáng sủa nhất, những địa điểm khuynh hướng về phía không giống mối cung cấp sáng sủa tiếp tục nhận không nhiều độ sáng rộng lớn, tự này sẽ tối rộng lớn.

Xem thêm: độc quyền yêu em

Khi chuẩn chỉnh hoá vector nnll, nln \cdot l đó là cos góc cho tới độ sáng, góc cho tới càng nhỏ thì độ sáng càng "trực diện".

Thành phần sáng sủa khuếch tán

Phía bên trên tiếp tục trình diễn phương pháp tính bộ phận hành động tự nhiên (specular). Phần độ sáng khuếch giã - đã cho chúng ta biết sắc tố thực của vật thể - đơn giản và giản dị rộng lớn. Quay lại một chút ít, tất cả chúng ta tiếp tục phân tách độ sáng Lúc xúc tiếp với mặt phẳng của vật thể sẽ tiến hành tạo thành 2 bộ phận, Lúc dùng quyết định luật bảo toàn tích điện, tất cả chúng ta hiểu được tổng tích điện 2 bộ phận này sẽ không được vượt lên vượt tích điện của mối cung cấp sáng sủa lúc đầu.

kS = calculateSpecularFraction(...); // Tính tỉ trọng độ sáng phản xạ
kD = 1 - kS; // Tính tỉ trọng phần độ sáng bị hấp thụ

Thành phần diffuse sẽ tiến hành tính theo đòi công thức

Ld=kdcπL_d = k_d * \frac{c}{\pi}

Trong bại liệt c là màu sắc của vật thể và kD là tỉ trọng của bộ phận diffuse.

Khi có mức giá trị của cả hai bộ phận, việc tất cả chúng ta cần thiết thực hiện là nằm trong bọn chúng lại cùng nhau để sở hữu được sắc tố ở đầu cuối.

Cài đặt

Chuẩn bị

  • Model 3D: Khẩu súng sáu được kiến thiết kèm cặp với material map (bao bao gồm những thông số tớ tiếp tục thưa phía bên trên - roughness, metallic...) cùng theo với normal map và ambient occlusion map (cho biết vùng này bên trên khẩu pháo nhận được không ít độ sáng nhất)

  • Môi trường: Một cubemap cụ thể môi trường xung quanh xung xung quanh và một cubemap nhằm đo lường độ sáng khuếch giã kể từ môi trường xung quanh. quý khách hàng rất có thể mò mẫm những hình ảnh HDR rất chất lượng đơn giản bên trên mạng internet nhằm sử dụng demo, và sử dụng một tool có trước như sIBLEdit muốn tạo rời khỏi cubemap thứ hai.

  • Một LUT (Look-up texture) tiếp tục đo lường sẵn mang đến hàm BRDF nhằm cắt giảm đo lường mang đến GPU.

  • Lòng kiên trì debug 😃

  • Các chúng ta cũng có thể lấy resource bản thân gói gọn sẵn ở links github: https://github.com/Colb98/Test3D/tree/master/res

Vertex shader

Cái này gần như là tương tự động nhau mang đến từng shader. Tại vertex shader tất cả chúng ta tiếp tục hiểu những vấn đề về đỉnh của model. Bao gồm:

  • Vị trí nhập không khí (Model Space)
  • Toạ chừng màu sắc lấy kể từ texture (Texture Coordinate)
  • 3 vector Normal, Tangent và Bitangent nhằm tính được vector pháp tuyến
// File pbr.vert
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec3 a_tangent;
attribute vec3 a_binormal;
attribute vec2 a_texCoord;

varying vec3 v_normal;
varying vec2 v_uv;
varying vec4 v_world_position;
varying vec3 v_tangent;
varying vec3 v_binormal;

void main(){
    gl_Position = CC_MVPMatrix * a_position;

    v_world_position = CC_MVMatrix * a_position;
    v_normal = normalize(CC_NormalMatrix * a_normal);
    v_uv = a_texCoord;
    v_uv.y = (1.0 - v_uv.y);

    v_tangent = normalize(CC_NormalMatrix * a_tangent);
    v_binormal = normalize(CC_NormalMatrix * a_binormal);
}
  • gl_Position là địa điểm tuy nhiên cocos tiếp tục vẽ đỉnh bại liệt lên màn hình hiển thị, vì thế tất cả chúng ta cần thiết gửi kể từ ModelSpace -> WorldSpace (View Space) -> PresentationSpace bằng phương pháp nhân với ma mãnh trận MVP Matrix (được cocos cung ứng sẵn)
  • Còn lại những varying tất cả chúng ta tiếp tục truyền bọn chúng quý phái fragment shader nhằm đo lường.

Fragment shader

Phần đo lường tối đa. Trước tiên tất cả chúng ta cần thiết khai báo một trong những thay đổi nguồn vào nhằm sẵn sàng mang đến quy trình đo lường.

// File pbr.frag
// Khai báo chừng đúng mực của loại float. Nếu cần dùng mang đến mobile thì khu vực này cần phải sở hữu. Do GL_ES bên trên mobile chỉ xử lý cho tới chừng đúng là mediump mang đến loại float
// Nếu dùng mang đến PC thì sử dụng highp nhằm quality hình hình ảnh cao hơn nữa (do dùng số đúng mực hơn)
#ifdef GL_ES
precision mediump float;
#endif

// Các varying truyền kể từ vertex shader
varying vec3 v_normal;
varying vec2 v_uv;
varying vec4 v_world_position;
varying vec3 v_tangent;
varying vec3 v_binormal;

// u_color là uniform của cocos
uniform vec4 u_color;
// Vị trí camera
uniform vec3 u_cam_pos;

// 5 point lights
// Vị trí những mối cung cấp sáng
uniform vec3 u_point_light[6];

// Các texture tế bào mô tả mặt phẳng của vật thể
uniform sampler2D u_albedo_map;
uniform sampler2D u_ao_map; 
uniform sampler2D u_metallic_map; 
uniform sampler2D u_roughness_map; 
uniform sampler2D u_normal_map; // Tuỳ nằm trong nhập material
uniform sampler2D u_opacity_map; // Tuỳ nằm trong nhập material. Thông thường sở hữu một trong những material cần thiết đặc điểm này, rất có thể vứt đi
uniform sampler2D u_brdf; 

// cubemap nhằm tính hành động tự nhiên môi trường xung quanh cho những vật thể bóng bẩy
uniform samplerCube u_hdr_map_0;
// cubemap nhằm tính độ sáng khuếch giã kể từ môi trường
uniform samplerCube u_env_map;

const float PI = 3.14159265359;

Bắt tay nhập sẵn sàng những độ quý hiếm được cung ứng sẵn nhằm đo lường, tất cả chúng ta dùng hàm texture2D nhằm hiểu tài liệu kể từ texture và chuẩn chỉnh hoá vài ba độ quý hiếm varying:

void main(){
    float metallic = texture2D(u_metallic_map, v_uv).r;
    vec3 normal = calcNormal();
    float roughness = texture2D(u_roughness_map, v_uv).r;
    float ao = texture2D(u_ao_map, v_uv).r;
    vec3 albedo = texture2D(u_albedo_map, v_uv).rgb;
    albedo = pow(albedo, vec3(2.2));
    //...
}

// Tính vector normal (nếu model sở hữu hỗ trợ)
vec3 calcNormal(){
    vec3 normal = texture2D(u_normal_map, v_uv).rgb;
    normal = normal * 2.0 - 1.0;
    normal = normal.r * v_tangent + normal.g * v_binormal + normal.b * v_normal;
    return normal;
}

Chúng tớ sử dụng câu mệnh lệnh pow(albedo, vec3(2.2)); nhằm tăng lên mức màu sắc của albedo lên. Thao tác này thường hay gọi là gamma expansion, ở bước cuối tất cả chúng ta tiếp tục tiến hành thêm 1 thao tác gamma compression nhằm nén màu sắc quay về khoảng tầm (0.0, 1.0).

Tiếp tục đo lường những độ quý hiếm bên trên điểm:

    vec3 world_pos = v_world_position.xyz;
    vec3 N = normal;
    vec3 V = normalize(u_cam_pos - world_pos);
    vec3 r = reflect(-V, N);

    float NdotV = max(dot(N, V), 0.0001);

    vec3 F0 = vec3(0.04);
    F0 = mix(F0, albedo, metallic);
    vec3 Lo = vec3(0.0);

Ở phía trên tất cả chúng ta đo lường sẵn những vector địa điểm, vector tia nhìn V, vector hành động tự nhiên r. F0 là vector nhằm tính bộ phận fresnel tiếp tục kể phía bên trên, nếu như trọn vẹn là phi-kim loại tiếp tục đem độ quý hiếm 0.04, ngược lại tiếp tục đem sắc tố của vật thể (albedo). Lo là tổng mức độ sáng bên trên điểm đang được xét, khởi tạo ra vị 0.

Tiếp theo đòi tất cả chúng ta tiếp tục tính độ quý hiếm Lo này so với từng mối cung cấp sáng

    // POINT LIGHTS
    // Loop từ một - 2
    for(int i=1;i<3;i++){
        // Vị trí của mối cung cấp sáng sủa nhập WorldSpace
        vec3 light = u_point_light[i];
        
        // Vector L = (nguồn sáng) - (vị trí điểm)
        vec3 L = normalize(light - world_pos);
        
        // Vector Halfway = V + L (V và L đều tiếp tục chuẩn chỉnh hoá)
        vec3 H = normalize(V + L);
        
        // Khoảng cơ hội cho tới mối cung cấp sáng sủa, phân chia 100.0 là nhằm scale đơn vị chức năng tính nhập shader với thực tiễn. Ví dụ so với bản thân 1 unit = 1cm => 100 unit = 1m vì thế cần thiết phân chia 100.0
        // cũng có thể thay cho thay đổi nhằm tăng tác động của mối cung cấp sáng sủa với vật
        float dist = length(light - world_pos)/100.0; 
        float attenuation = 1.0/(dist*dist); // Sự hao hụt tích điện độ sáng được xấp xỉ bằng phương pháp lấy nghịch tặc hòn đảo bình phương khoảng cách = Càng xa xôi càng mờ/tối
        vec3 radiance = vec3(1.0,1.0,1.0) * attenuation;

        float NdotL = max(dot(N, L), 0.0); // Cosine góc cho tới của độ sáng với pháp tuyến bên trên điểm

        float NDF  = DistributionGGX(N, H, roughness); // Hàm phân phối
        float G = GeometrySmith(NdotV, NdotL, roughness); // Thành phần geometry mặt phẳng của vật
        vec3 F  = FresnelSchlick(max(dot(H, V), 0.0), F0); // Phản xạ Fresnel

        // Tỉ lệ thân mật Diffuse và Specular (kS + kD = 1.0)
        vec3 kS = F;
        vec3 kD = vec3(1.0) - kS;
        kD *= 1.0 - metallic;

        // Phản xạ Specular = (NDF * G * F) / (4.0 * NdotV * NdotL)
        vec3 num = NDF * G * F;
        float denom = 4.0 * NdotV * NdotL;
        vec3 specular = num / max(denom, 0.0001);
       
        // Ánh sáng sủa ở đầu cuối = Specular + Diffuse 
        // Diffuse = Tỉ lệ diffuse * màu sắc vật thể / PI
        Lo += (kD * albedo / PI + specular) * radiance * NdotL;
    }

Mình dùng mối cung cấp sáng sủa kể từ 1-2 vì thế lặp vòng for gấp đôi. Giá trị độ sáng sẽ tiến hành nằm trong vào sinh sống cuối vòng lặp cho từng mối cung cấp, nghĩa là vấn đề càng có được độ sáng từ rất nhiều mối cung cấp thì sẽ càng sáng sủa rộng lớn (hợp lý phết).

Các hàm được dùng ở đoạn code trên:

vec3 FresnelSchlick(float cosTheta, vec3 F0){
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

float DistributionGGX(vec3 N, vec3 H, float roughness){
    float a = roughness * roughness;
    float a2 = a*a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;

    float num = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return num/denom;
}

float GeometrySchlickGGX(float NdotV, float roughness){
    float r = (roughness + 1.0);
    float k = (r*r)/8.0;
    float num = NdotV;
    float denom = NdotV * (1.0-k) + k;
    return num/denom;
}

float GeometrySmith(float NdotV, float NdotL, float roughness){
    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}

Các đo lường này và đã được kể phía bên trên nên bản thân sẽ không còn lý giải lại nữa nhá. Giờ tất cả chúng ta tiếp tục xử phần hành động tự nhiên môi trường xung quanh xung xung quanh.

    vec3 F = FresnelSchlickRoughness(NdotV, F0, roughness);
    vec3 kS = F;
    vec3 kD = 1.0 - kS;
    kD *= 1.0 - metallic;

    vec3 irradiance = textureCube(u_env_map, N).rgb; // Ánh sáng sủa khuếch giã kể từ môi trường
    vec3 diffuse =  irradiance * albedo; // bộ phận Diffuse 

Chúng tớ cũng tính kS và KD như phía trên. Nhưng tính F tất cả chúng ta cần thiết xét cho tới tính "nhám" của vật thể, tinh ma chỉnh hàm FresnelSchlick 1 tí.

vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness){
    // return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
    return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}

Thành phần Specular sẽ tiến hành tính như sau.

    const float MAX_LOD = 3.0;
    vec3 prefilteredColor = textureCubeLod(r, roughness * MAX_LOD).rgb;
    vec2 brdf = texture2D(u_brdf, vec2(NdotV, roughness)).rg;
    vec3 specular = prefilteredColor * (F * brdf.x + brdf.y);

Với LOD là chừng cụ thể của texture Lúc tất cả chúng ta "phản chiếu" nó lên mặt phẳng. Chúng tớ tiếp tục tận dụng tối đa cubemap "mờ" muốn tạo rời khỏi những nấc cụ thể không giống nhau. Đoạn này chính rời khỏi tất cả chúng ta nên dùng những mipmap level không giống nhau của texture cubemap. Tuy nhiên phiên bạn dạng OpenGL được dùng vị cocos2dx ko tương hỗ hàm texture(sampler, vector, mipLevel) nên bản thân nên dùng một cơ hội kềnh càng rộng lớn. Kết ngược đạt được với những scene bản thân test demo tương đối tốt, và performance không thật tệ.

vec4 textureCubeLod(vec3 uvw, float lod){
    if(lod < 1.0){
        return textureCube(u_hdr_map_0, uvw);
    }
    else if(lod < 2.0){
        return mix(textureCube(u_hdr_map_0, uvw), textureCube(u_env_map, uvw), vec4(fract(lod)));
    }
    else{
        return textureCube(u_env_map, uvw);
    }
}

Hàm tiếp tục nội suy độ quý hiếm thân mật 2 cubemap muốn tạo rời khỏi những hình hình ảnh với chừng lù mù không giống nhau tuỳ theo đòi lod.

Sau Lúc đo lường hoàn thành bộ phận diffuse và specular, việc ở đầu cuối cần thiết thực hiện là nằm trong bọn chúng lại và kết phù hợp với độ sáng kể từ mối cung cấp sáng sủa (Lo) tớ tiếp tục sở hữu.

    // ...
    vec3 ambient = (kD * diffuse + specular)* ao;
    vec3 color = ambient + Lo;
   
   // Gamma Compression nhằm nén màu sắc hình ảnh về khoảng tầm (0,1);
    color = color/(color + vec3(1.0));
    color = pow(color, vec3(1.0/2.2));

    gl_FragColor = vec4(color, 1.0) * u_color;

Cocos2dx

Vì mục tiêu đó là thiết lập shader nhằm tô màu sắc, phần này bản thân tiếp tục chỉ thiết lập công việc ít nhất nhằm thêm 1 đối tượng người sử dụng 3 chiều và dùng shader lên nó. Các bước râu rìa (cài bịa đặt camera, điều khiển) sẽ không còn được kể, song rất có thể đơn giản nhìn thấy thật nhiều chỉ dẫn bên trên mạng mang đến cocos. quý khách hàng này gặp gỡ trở ngại gì khi thiết lập rất có thể tương tác với bản thân.

initSphere: function(){
        // Đặt thương hiệu khá ngu tự khi đầu tôi chỉ quyết định vẽ 1 ngược cầu thôi :)
        let sphere = new jsb.Sprite3D(res.gun);
        sphere.setRotation3D(cc.math.vec3(-90, 0, 0));
        sphere.setPosition3D(cc.math.vec3(14, 125, 0));
        this.addChild(sphere);
        this._sphere = sphere;
},

Khởi tạo ra một đối tượng người sử dụng Sprite3D và mang đến nhập vào scene. Không sở hữu gì đặc trưng. Vị trí và góc xoay là tuỳ ý, à và lưu giữ setup camera để xem thấy được nhé. Setup camera sở hữu ở trong phần cuối nội dung bài viết trước của tôi.

Sau bại liệt tất cả chúng ta đưa đến một đối tượng người sử dụng lưu state của shader và áp nó lên Sprite3D.

    initShader: function(){
        // Đường dẫn cho tới shader
        const shader = new cc.GLProgram("res/shader/pbr.vert", "res/shader/pbr.frag");
        shader.retain();

        const state = cc.GLProgramState.create(shader);
        this._sphere.setGLProgramState(state);
        this._state = state;
        this._program = shader;
        
        // Các texture truyền vào
        state.setUniformTexture("u_brdf", cc.textureCache.addImage(res.brdf_lut));
        state.setUniformTexture("u_albedo_map", cc.textureCache.addImage(res.gun_albedo));
        state.setUniformTexture("u_metallic_map", cc.textureCache.addImage(res.gun_metallic));
        state.setUniformTexture("u_ao_map", cc.textureCache.addImage(res.gun_ao));
        state.setUniformTexture("u_roughness_map", cc.textureCache.addImage(res.gun_roughness));
        state.setUniformTexture("u_normal_map", cc.textureCache.addImage(res.gun_normal));


        let pPointLights = [0,0,0, 0,0,-50, 0,100,50];
        pPointLights = new Float32Array(pPointLights);

        var program = shader.getProgram();
        var loc = gl.getUniformLocation(program, "u_point_light");
        gl.uniform3fv(loc, 3, pPointLights);
        
        // Cubemap độ sáng khuếch tán
        const path = "res/cubemaps/icelake_env/";
        const env_map = jsb.TextureCube.create(path + "px.png", path + "nx.png", path + "py.png", path + "ny.png", path + "pz.png", path + "nz.png");
        env_map.setTexParameters(gl.LINEAR, gl.LINEAR, gl.MIRRORED_REPEAT, gl.MIRRORED_REPEAT);
        state.setUniformTexture("u_env_map", env_map);

        // Các texture cube ko được retain Lúc truyền nhập shader, nên cần phải có một skybox ảo để giữ lại nó. Chúng tớ sẽ không còn mang đến hiển thị skybox này (vì nó mờ)
        let dummyskybox = jsb.Skybox.create();
        dummyskybox.setTexture(env_map);
        this.addChild(dummyskybox);
        dummyskybox.setVisible(false);
        
        // Cubemap chi tiết
       const path_hdr = "res/cubemaps/icelake_hdr_1/";
       const hdr_map = jsb.TextureCube.create(path_hdr + "px.png", path_hdr + "nx.png", path_hdr + "py.png", path_hdr + "ny.png", path_hdr + "pz.png", path_hdr + "nz.png");
       hdr_map.setTexParameters(gl.LINEAR, gl.LINEAR, gl.MIRRORED_REPEAT, gl.MIRRORED_REPEAT);
       state.setUniformTexture("u_hdr_map_0", hdr_map);

      // Hiển thị để xem tất cả sống động rộng lớn (là một chiếc nền đen kịt thui) 
       let dummyskybox = jsb.Skybox.create();
       dummyskybox.setTexture(hdr_map);
       this.addChild(dummyskybox);
    },

Sau bại liệt lưu giữ setup thêm thắt cả hàm truyền địa điểm camera Lúc thay cho thay đổi góc xoay nữa đấy. Các các bạn hãy mò mẫm phương pháp để gọi hàm này mỗi lúc camera thay cho thay đổi góc máy, địa điểm.

Xem thêm: giá net là gì

    onCameraChange: function(position){
        if(this._state)
            this._state.setUniformVec3("u_cam_pos", position);
    },

Thành phẩm

Các chúng ta cũng có thể chuyên chở phác thảo bên trên về nhằm build chạy demo bên trên github của mình: https://github.com/Colb98/Test3D

Nếu chúng ta tiếp tục hiểu cho tới phần này của nội dung bài viết, cảm ơn thật nhiều 😄

Nguồn tham lam khảo

  • Bài ghi chép cụ thể dễ nắm bắt vị giờ Anh về những phương trình toán học: https://learnopengl.com/PBR/Theory
  • Slide tầm cỡ về PBR của Hoffman: https://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
  • Slide và Paper của EpicGame về PBR: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
  • Blog về một Engine tự động build theo đòi nguyên tắc PBR của Alex Tardif: http://alextardif.com/eden.html. Blog này còn có cụ thể vài ba cơ hội implement về mối cung cấp sáng sủa sở hữu diện tích S (Bài bên trên chỉ nói tới mối cung cấp sáng sủa điểm), cực kỳ hữu ích và dễ dàng tuân theo.
  • Nguồn lấy resource môi trường: http://www.hdrlabs.com/
  • Model PBR: https://www.cgtrader.com/free-3d-models/military/gun/schofield-3-co2-bb