Trong bài này tôi hướng dẫn dựa trên bài trước (Bài 2, bài 1). Vì vậy nếu muốn hiểu được tốt về bài này thì hãy tham khảo qua 2 bài trước.
- Ánh sáng cho mỗi điểm ảnh là gì?
Ánh sáng trên mỗi điểm ảnh (Per
pixel) là một trường hợp tương đối mới trong các trò chơi Game với sự ra
đời của việc sử dụng Shader. Có rất nhiều trò chơi cũ rất phổ biến như
Haft Life thì được phát triển trước khi cho ra đời Shader.Và các đặc
trưng chính ở đây là ánh sáng tĩnh, với một số thủ thuật để mô phỏng ánh
sáng động như ánh sáng trên từng đỉnh (Hay còn gọi là Gouraud shading)hoặc kỹ thuật khác giống như là lightmaps.
lightmaps đưa
ra một kết quả rất đẹp và nhiều lúc đưa ra kết quả thật là tuyệt vời
hơn cả Shader.Việc tính toán rất nhiều và có thể tính toán trước. Nhưng
nhược điểm của chúng là rất tốn bộ nhớ máy tính. Và ánh sáng động được
biểu diễn trong một giới hạn hẹp.
Với
Shader, rất nhiều những tính toán này có thể được chuyển qua cho GPU,
cho phép hiệu ứng cho nhiều hơn nữa để được thực hiện trong thời gian
thực.
- Từ per vertex Lighting sang fragment Lighting
Điện
thoại di động GPU đang nhận được nhanh hơn và nhanh hơn, nhưng hiệu quả
vẫn còn là một mối quan tâm. Cho chiếu sáng "soft" như địa hình, ánh
sáng mỗi đỉnh có thể đủ tốt.Đảm bảo bạn có một sự cân bằng thích hợp
giữa chất lượng và tốc độ.
Một
sự khác biệt đáng kể giữa hai loại ánh sáng có thể được nhìn thấy trong
các tình huống nhất định. Hãy nhìn vào các ảnh chụp màn hình sau đây:
Trong
ánh sáng Per vertex trong hình ảnh bên trái, mặt trước của khối lập
phương xuất hiện như là tô bóng phẳng, và không có bằng chứng về ánh
sáng gần đó. Điều này là bởi vì một trong bốn điểm của mặt trước là
nhiều hơn hoặc ít hơn khoảng cách bằng nhau từ ánh sáng, và ánh sáng
cường độ thấp tại một trong bốn điểm chỉ đơn giản là nội suy qua hai
hình tam giác tạo nên mặt trước.
Phiên bản cho mỗi mảnh cho thấy một điểm nhấn đẹp trong so sánh.
Hình ảnh bên trái cho thấy một hình lập phương. Giống như Gouraud Shading
di chuyển nguồn ánh sáng gần góc của mặt trước của khối lập phương, một
hiệu ứng giống như hình tam giác có thể được nhìn thấy. Điều này là do
mặt trước là thực sự bao gồm của hai tam giác, và như các giá trị nội
suy theo các hướng khác nhau trên mỗi tam giác, chúng ta có thể nhìn
thấy hình học ở dưới.
Phiên bản Per- fragments cho thấy không có lỗi nội suy như ở Per - vertex, và cho thấy một điểm nhấn đẹp tròn gần mép.
- Tổng quan của Per - vertex Lighting
Chúng ta hãy nhìn vào shaders của
chúng ta từ bài học thứ hai, một lời giải thích chi tiết hơn về những gì
các Shader có thể được tìm thấy trong bài học số 2 này.
Vertex shader
uniform mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix. uniform mat4 u_MVMatrix; // A constant representing the combined model/view matrix. uniform vec3 u_LightPos; // The position of the light in eye space. attribute vec4 a_Position; // Per-vertex position information we will pass in. attribute vec4 a_Color; // Per-vertex color information we will pass in. attribute vec3 a_Normal; // Per-vertex normal information we will pass in. varying vec4 v_Color; // This will be passed into the fragment shader. // The entry point for our vertex shader. void main() { // Transform the vertex into eye space. vec3 modelViewVertex = vec3(u_MVMatrix * a_Position); // Transform the normal's orientation into eye space. vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0)); // Will be used for attenuation. float distance = length(u_LightPos - modelViewVertex); // Get a lighting direction vector from the light to the vertex. vec3 lightVector = normalize(u_LightPos - modelViewVertex); // Calculate the dot product of the light vector and vertex normal. If the normal and light vector are // pointing in the same direction then it will get max illumination. float diffuse = max(dot(modelViewNormal, lightVector), 0.1); // Attenuate the light based on distance. diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance))); // Multiply the color by the illumination level. It will be interpolated across the triangle. v_Color = a_Color * diffuse; // gl_Position is a special variable used to store the final position. // Multiply the vertex by the matrix to get the final point in normalized screen coordinates. gl_Position = u_MVPMatrix * a_Position; } |
Fragment shader
precision mediump float ; // Set the default precision to medium. We don't need as high of a // precision in the fragment shader. varying vec4 v_Color; // This is the color from the vertex shader interpolated across the // triangle per fragment. // The entry point for our fragment shader. void main() { gl_FragColor = v_Color; // Pass the color directly through the pipeline. }
|
Như
bạn đã nhìn thấy, hầu hết các công việc được thực hiện trên vertex
shader. Dịch chuyển từ per-fragment lighting nghĩa rằng fragment shader
sẽ có nhiều việc mà chúng ta phải làm.
Implementing per-fragment lighting
Here is what the code looks like after moving to per-fragment lighting.
Implementing per-fragment lighting
Here is what the code looks like after moving to per-fragment lighting.
Vertex shader
uniform mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix. uniform mat4 u_MVMatrix; // A constant representing the combined model/view matrix. attribute vec4 a_Position; // Per-vertex position information we will pass in. attribute vec4 a_Color; // Per-vertex color information we will pass in. attribute vec3 a_Normal; // Per-vertex normal information we will pass in. varying vec3 v_Position; // This will be passed into the fragment shader. varying vec4 v_Color; // This will be passed into the fragment shader. varying vec3 v_Normal; // This will be passed into the fragment shader. // The entry point for our vertex shader. void main() { // Transform the vertex into eye space. v_Position = vec3(u_MVMatrix * a_Position); // Pass through the color. v_Color = a_Color; // Transform the normal's orientation into eye space. v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0)); // gl_Position is a special variable used to store the final position. // Multiply the vertex by the matrix to get the final point in normalized screen coordinates. gl_Position = u_MVPMatrix * a_Position; } |
The
vertex shader thì đơn giản hơn bài trước. Chúng ta thêm 2 biến nội suy
tuyến tính và đưa vào fragment shader: the vertex position and the
vertex normal. Cả 2 biến này sẽ được sử dụng khi tính toán ánh sáng
trong fragment shader.
Fragment shader
precision mediump float ; // Set the default precision to medium. We don't need as high of a // precision in the fragment shader. uniform vec3 u_LightPos; // The position of the light in eye space. varying vec3 v_Position; // Interpolated position for this fragment. varying vec4 v_Color; // This is the color from the vertex shader interpolated across the // triangle per fragment. varying vec3 v_Normal; // Interpolated normal for this fragment. // The entry point for our fragment shader. void main() { // Will be used for attenuation. float distance = length(u_LightPos - v_Position); // Get a lighting direction vector from the light to the vertex. vec3 lightVector = normalize(u_LightPos - v_Position); // Calculate the dot product of the light vector and vertex normal. If the normal and light vector are // pointing in the same direction then it will get max illumination. float diffuse = max(dot(v_Normal, lightVector), 0.1); // Add attenuation. diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance))); // Multiply the color by the diffuse illumination level to get final output color. gl_FragColor = v_Color * diffuse; } |
Với ánh sáng trên fragment, our fragment shader có nhiều nhiệm vụ phải làm hơn. Chúng ta chuyển tính toán cơ bản the Lambertian calculation và attenuation to the per-pixel level, which gives us more realistic lighting without needing to add more vertices.
Không có nhận xét nào:
Đăng nhận xét