Friday, November 22, 2013

Shader програм, түүнийг бэлтгэх

shader бол GPU (график карт) дээр байрлан 3-н хэмжээст геометр, пиксел мэдээллүүдийг боловсруулах үүрэгтэй жижигхэн хэмжээний програм юм.



Graphic Pipeline-д хоёр төрлийн shader програм голчлон хэрэглэгддэг. Эдгээр нь Vertex Shader болон Fragment Shader юм. Сүүлд Vertex Shader дээр нэмэлтээр Tessellation Shader(энэ дотроо хоёр сална), Geometry Shader гээд хоёр төрлийн shader орж ирсэн ч үндсэн хоёр shader-ээ судлах нь эхний үед чухал.

Shader Програм бичихийн тулд Shader code-оо файл аль эсвэл санах ой дээр бэлтгэн хадгалах хэрэгтэй. Дараагаар нь тэдгээрийг уншин авч shader compiler-аар компайлдаж машины код бүхий blob болгож хөрвүүлнэ. Хөрвүүлэлт амжилттай болвол blob-ууд маань үүрэг гүйцэтгэхэд бэлэн болно. Үүний дараа shader програмуудаа шаардлагатай график өгөгдлүүдээр хангаж өгөх хэрэгтэй. Тэдгээрт объектийн оройн координатууд, UV координатууд, матриц, uniform өгөгдлүүд гэх мэт болно.

Ажиллах дарааллын хувьд эхлээд Vertex Shader ажиллаад дараа нь rasterizer-аар дамжаад Fragment Shader ажилладаг.

Shader програмыг файлаас уншиж компайлдах
shader програмыг C хэлтэй төстэй өгүүлбэр зүйтэй хэлээр бичдэг, эдгээр хэлүүдээс дурдвал HLSL, GLSL, Cg, PSSL, OSL, RSL гээд олон янз бий. Ямар график API хэрэглэж байгаагаас shader хэл хамаарна. OpenGL-ийн хувьд GLSL юм уу Cg хэлүүдийн аль нэгийг хэрэглэж болно голчлон GLSL хэл хэрэглэгддэг. Эхлээд Shader програмын кодыг текст файл хэлбэрээр хадгалж бэлтгэх хэрэгтэй. Файлын өргөтгөлийн хувьд myshader.vert.glsl болон myshader.frag.glsl гэх мэт байдлаар хадгалж хэрэглэвэл ялган танихад амар байдаг.

Vertex Shader болон Fragment Shader хоёрын алийг нь ч байсан уншиж аваад GPU дээр компайлдаж blob болгон хадгалах функц бичье. Функцийн оролтонд shader програмын хадгалагдсан байгаа файлын зам болон shader програмын төрөл гэсэн хоёр утга авна. Хэрэв vertex shader ачаалж авахыг хүсвэл shaderType дээр GL_VERTEX_SHADER тогтмол утгыг тавьж өгнө. анхнаасаа shader-уудын төрлийг enum байдлаар зарлаж цаанаа хэрэглэдэг учир нэрээр нь хандаж ашиглах хэрэгтэй. GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER гэх мэтээр тавьж өгнө. Функц заагдсан файлыг нээгээд хэрэв файл нээхэд хэвийн байвал шинээр shaderID дээр shaderType-ийг заан shader object үүсгэнэ(glCreateShader функцээр). shaderID нь цаашид төлөөлөгдөн хэрэглэгдэнэ. stream-ээсээ shader програмын эх кодыг агуулах текстийн заагчийг аваад хаягаар нь хандан агуулгыг нь компайлдахад бэлэн болгоно. Үүний дараа glCompileShader функцийг ашиглан програм компайлдаж хөрвүүлнэ. Хөрвүүлэх явцад ямар нэгэн алдаа гаргавал энэ талаар мэдээлээд үүсгэсэн shaderID буюу shader  object-ийг устгана.

GLuint loadShader(string filePath, GLenum shaderType) {
    GLuint shaderID = 0;
    string shaderString;
    ifstream sourceFile(filePath.c_str());
    if (sourceFile) {

        shaderString.assign(
                (istreambuf_iterator(sourceFile)),
                (istreambuf_iterator()));

        shaderID = glCreateShader(shaderType);

        const GLchar* shaderSource = shaderString.c_str();

        glShaderSource(shaderID, 1, (const GLchar**) &shaderSource, NULL);
        glCompileShader(shaderID);

        GLint shaderCompiled = GL_FALSE;
        glGetShaderiv(shaderID, GL_COMPILE_STATUS, &shaderCompiled);
        if (shaderCompiled != GL_TRUE) {
            cout << "cannot compiled" << endl;
            glDeleteShader(shaderID);
            shaderID = 0;
        }

        sourceFile.close();
    } else {
        cout << "cannot open file" << endl;
    }
    return shaderID;
}

Vertex Shader үүсгэх

shader уншиж авч хөрвүүлдэг функц бэлэн болсон бол одоо тэр функцээ ашиглан shader-үүдээ уншиж авч нэгтгэн нэг програм болгох хэрэгтэй. Graphics Pipeline-ийн дарааллын дагуу эхний ээлжинд vertex shader програмыг ачаалая. Хэрэв амжилтгүй болвол програмыг зогсооно. Програмыг кодыг simple.vert.glsl файлд хадгалсан байгаа

GLuint vertexShader = loadShader("simple.vert.glsl", GL_VERTEX_SHADER);
if (vertexShader == 0) {
    glDeleteProgram(programID);
    programID = 0;
    return EXIT_FAILURE;
}

Fragment Shader үүсгэх

Дараалал ёсоор дараагийн програм болох fragment shader-ийг үүсгэе. Програмын кодыг simple.frag.glsl файл хадгаллаа.

GLuint fragmentShader = loadShader("simple.frag.glsl", GL_FRAGMENT_SHADER);
if (fragmentShader == 0) {
    glDeleteProgram(programID);
    programID = 0;
    return EXIT_FAILURE;
}

Shader програмуудаа амжилттай хөрвүүлсэн бол одоо эдгээрийг нэгтгээд GPU дээр ажиллах нэг програм болгон холбох хэрэгтэй. Үүний тулд GPU програмыг төлөөл program object үүсгэх хэрэгтэй.

GLuint programID = glCreateProgram();

иймэрхүү. Энэ програм объектдоо түрүүний vertex болон fragment shader-үүдээ зааж өгөх хэрэгтэй.

glAttachShader(programID, vertexShader);
glAttachShader(programID, fragmentShader);

Ингээд болсон бол сүүлийн ажиллагаа болох object linking ийг ажиллуулъя. Энийг ажиллуулснаар vertex болон fragment shader хоёул нэгдэн нэг програм объектэд харъяалагдаж цаашид тэр програмаар дамжин програмчлагдах үйл ажиллагаа явагдана.

glLinkProgram(programID);

GLint programSuccess = GL_TRUE;
glGetProgramiv(programID, GL_LINK_STATUS, &programSuccess);
if (programSuccess != GL_TRUE) {
    cout << "error in linkin program " << programID << endl;
    glDeleteProgram(programID);
    programID = 0;
    return EXIT_FAILURE;
}


Цаашид GPU програмтай харьцахаар болвол programID хувьсагчаар дамжин ажиллана. Жишээ нь програмыг дуудаж ажиллуулах болвол

glUseProgram(programID);

Хэрэв програмаа ашиглаж дуусаад хуучин төлөвт нь ашиглахыг хүсвэл
glUseProgram(0);

гэх мэтээр ажиллаад явна