Bài đăng phổ biến

Thứ Năm, 11 tháng 10, 2012

OpenGLES2.0 - Android (Bài 3 - Per pixel Lighting)

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
Trong bài này chúng ta sẽ tìm hiểu cùng nội dung về ánh sáng tương ứng với 2 giải pháp per vertex và fragment vertex. Tôi gọi tắt là ánh sáng trên mỗi điểm ảnh. Trong opengles thực tế chúng ta làm việc với các fragments. Vài fragments có thể góp phần tạo ra giá trị cuối cùng cho Per pixel.

Đ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