Төрийн далбаагаа 3D дээр намируулая!

Энэ удаагийн постоор бид Монгол улсынхаа далбайг намируулах болно.

Ач холбогдол нь гэвэл хэрхэн 3D дүрс бүтддэг, текстурыг хаана хэрэглэдэг, GPU дээр анимацийг хэрхэн хийдэг талаар ойлголттой болох юм.

3D объект бүтээхэд тэр объектүүдийг дүрслэх vertex-үүд болон гадаргууг илтгэх texture хэрэг болдог.

Эхлээд туг далбаагаа бүтээцгээе.
Туг далбаа бол тэгш өнцөгт хэлбэртэй дүрс байдаг бөгөөд бид тэр тэгш өнцөгтийн дөрвөн булангийнх байрлалыг доторхойлоход хангалттай юм.

цэгэн оройнууд

өргөн өндөрийг нь 3:2 ийн харьцаагаар бодож далбааныхаа координатыг тодорхойлъё. Тэгхээр өндөрийг нь 1 хэмжээтэй гэвэл урт нь 3/2 буюу 1.5 болно.

V1 = ( 0, 1, 0)
V2 = (1.5, 1, 0)
V3 = (1.5, 0, 0)
V4 = ( 0, 0, 0)

Ингээд далбайныхаа координатуудыг гаргаж авлаа. Эдгээр координатуудаа хост програмын санах ой буюу RAM дээр массив үүсгэн хадгалъя. Эдгээрийг GPU-рүү upload хийхэд бэлтгэж байна гэж ойлгож болох юм.

1
2
3
4
5
6
    var vertices = [
         0.0,  1.0,  0.0, // зүүн дээд оройн байрлал
         1.5,  1.0,  0.0, // баруун дээд оройн байрлал
         1.5,  0.0,  0.0, // баруун доод оройн байрлал
         0.0,  0.0,  0.0  // зүүн доод оройн байрлал
    ];

Одоо бүгдээрээ GPU буюу график карт дээрээ буффер үүсгэе. Үүний тулд GPU дээрхи буфферийг байнга төлөөлөн хаягийг нь хадгалж байдаг хувьсагч зарлая

1
    var triangleVertexPositionBuffer;

Энэ хувьсагчаа ашиглан vertices доторхи утгуудыг хадгалах зориулалттай буфферийг GPU дээр үүсгэе.

1
    triangleVertexPositionBuffer = gl.createBuffer();

Ингээд GPU дээр өгөгдөл хадгалах зориулалттай буффер үүсэх бөгөөд энэ буфертэй би тулж ажиллах юм шүү гэдэг командыг GPU-д мэдэгдэцгээе. ARRAY_BUFFER гэдэг нь энэ бол өгөгдлүүдийг массив байдлаар хадгалах буффер юм шүү гэдгийг илтгэнэ.

1
    gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);

Яг одоо яах вэ? мэдээж GPU-дээр үүсгэсэн байгаа буферлүү хост програмын санах ой дээр байгаа өгөгдлүүдээ хуулж хийнэ. Өөрөөр хэлбэл RAM дээр vertices гэсэн массивт тэгш өнцөгтийг дүрслэх оройнуудын координатууд хадгалагдсан байгаа бөгөөд эдгээр өгөгдлүүдийг GPU дээр үүсгэсэн байгаа буфферлүү хуулж хийнэ. Энэ хуулах үйл явц нь RAM-аас компютерийн төв bus-аар дамжаад GPU дээр очиж хуулагдана. Ингээд хуулж хийцгээе.

1
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

GPU-рүү өгөгдөл хуулах процесс бол ердөө л ийм юм.
Үүнтэй нэгэн адилаар мөн орой болгонд харгалзсан өнгөний утгуудыг буфферлээд хуулж хийсэн байгаа.

Одоо төсөөлцгөөе GPU дээр баахан өгөгдлүүд оччихсон байгаа тэгэхээр эдгээр өгөгдлүүдийн аль алийг нь нэг гурвалжин болгож зурах юм бэ гэдгийг хэрхэн тодорхойлох вэ? 3D-д гадаргууд дүрсэлдэг хамгийн бага дүрс бол гурвалжин юм. 3D объектүүд бүгд маш олон тооны гурвалжингуудаас бүрдсэн байдаг гэсэн үг. Бидний далбааг дүрслэх гэж байгаа тэгш өнцөгт маань хүртэл хоёр ширхэг гурвалжинг нийлүүлж үүснэ.
index гэсэн ойлголт байдаг бөгөөд энэ нь оройнуудыг ямар дарааллаар зурах вэ гэдэг асуудлыг шийдэж өгдөг. Ажиллах үүсгэх зарчим нь түрүүний буффер үүсгэх зарчимтай адилхан хийгдэнэ. Харин үүсгэх flag буюу төлөв нь ялгаатай байгааг анзаараарай. Ингээд үүсгэцгээе.

Үүний тулд GPU дээр index буфферийг төлөөлүүлэх хувьсагчийг хост програм дээр зарлая.

1
    var indexBuffer;

index буфферээ GPU дээр үүсгэе

1
    indexBuffer = gl.createBuffer();

GPU дээр үүсгэсэн буфферээ index ийн зорилгоор буюу өгөгдлүүдийн зурагдах дарааллыг тодорхойлох зорилготой ашиглах юм шүү гэдгээ GPU-д хэлж ойлгуулцгаая. Үүний тулд буффер ашиглах горимдоо ELEMENT_ARRAY_BUFFER гэсэн төлөвийг тодорхойлж өгнө.

1
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

Ямар дарааллаар зурах юм бэ гэдгээ тодорхойлцгооё. Далбааг тодорхойлсон оройн байрлалуудаа санаарай. Дөрвөн ширхэг оройнуудыг тодорхойлсон байгаа. Аль гуравийг нь гурвалжин болгож тодорхойлох вэ гэдгийг нь индекс буюу байрлалаар тодорхойлж өгье.

1
2
3
4
    var indices = [
        0, 1, 2, // дээд гурвалжин
        2, 3, 0  // доод гурвалжин
    ];

Одоо мэдээж өгөгдлөө GPU -рүү хуулах хэрэгтэй

1
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

Харин одоо хэрхэн индексэд тодорхойлсон дарааллын дагуу гурвалжингуудаа зурах вэ?

index буфферээ ашиглахаар тохируулна.

1
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

Гурвалжин зурах горимоор shader програмаа 6-н удаа буюу index-д доторхойлсон тооны удаа дуудан ажиллуулна. GPU нь эдгээр зургаан дуудалтыг нүд ирмэхийн зуур параллелиар ажиллуулж дуусгах болно.

1
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

За одоо үр дэнгээ харцгаая.

За одоо текстуртэй танилцая.
Текстур гэдэг бол ердөө л зураг юм. Зургийг 3D объектийн гадаргууг дүрслэхэд ашиглаж болно. Англиар энэ үйл явцыг texture mapping гэдэг. Энэ үгээр дэлгэрүүлж гүүглэдэх боломжтой шүү.

Далбааг тодорхойлсон дүрсдээ хэрхэн зураг оноох вэ? Үүний тулд эхлээд төрийнхөө далбайны зургийг гүүгэлдэн хайцгаая. Mongolian flag гэдэг үгээр хайлаа. Эх орны минь далбаа үнэхээр гоё юм тэ.

Монгол улсын далбаа

Энэ олсон зургаа бид текстур болгон ашиглана. Гэхдээ арай болоогүй байна. Тэгш өнцөгтийн оройнуудыг зургийн яг хаана байрлуулах вэ гэдгийг эхлээд GPU-д хэлж өгөх хэрэгтэй. Үүний тулд текстур дээр буулгах хоёр хэмжээст координатуудыг тодорхойлж өгөх хэрэгтэй.
Өмнөх буффер үүсгэж ашиглаж байсантай л ижилхэн хийгдэх юм.

буфферээ төлөөлөх хувьсагчаа зарлая

1
    var textureCoordBuffer;

буфферээ үүсгэе

1
    textureCoordBuffer = gl.createBuffer();

ашиглахаар идэвхижүүлэх

1
    gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);

хост програм дээрээ буулгалтын координатуудыг тодорхойлох

1
2
3
4
5
6
    var texCoords = [
        0.0, 1.0,
        1.0, 1.0,
        1.0, 0.0,
        0.0, 0.0
    ];

тодорхойлсон массиваа GPU дээрхи буфферлүү upload хийх

1
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

нэг элемэнт нь хэр хэмжээтэй, хэдэн ширхэг элемэнт буфферт байгааг тодорхойлж өгөх

1
2
    textureCoordBuffer.itemSize = 2;
    textureCoordBuffer.numItems = 4;

Текстур үүсгэх
үүний тулд текстурыг төлөөлөх хувьсагч зарлах хэрэгтэй

1
    var texture;

хувьсагч дээрээ текстур объектийг үүсгэх

1
    texture = gl.createTexture();

түүний зурагны өгөгдөл хариуцсан талбарт нь Image объектийг үүсгэх

1
    texture.image = new Image();

зураг дуудан ачааллах функцийг override хийгээд OpenGL ийн тохиргоо хийх

1
2
3
4
5
6
7
8
    texture.image.onload = function() {
      gl.bindTexture(gl.TEXTURE_2D, texture); // ашиглахаар идэвхижүүлэх
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); // GPU-рүү upload хийх
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.bindTexture(gl.TEXTURE_2D, null);
    }

Ямар зураг уншиж ачааллах вэ гэдгийгээ зааж өгөх

1
    texture.image.src = "MongolianFlag.png";

текстур буулгалтын координатуудыг shader-лүү attribute болгож нэмсэн тул shader програмд маань ч гэсэн харгалзах attribute-ийг нэмж өөрчлөлт оруулна.
shader програм дотор юу болж байна вэ гэхээр vertex үе шатанд aTexCoord аттрибутад shader програмын дуудалт бүрд өмнө тодорхойлсон текстур буулгалтын координат орж ирнэ. Түүнийг varying хувьсагч болох vTexCoord-д оноож өгсөнөөр fragment үе шатруу дөхөлт хийгдэж орж ирнэ. Өөрөөр хэлбэл fragment shader-ийн vTexCoord-ийг текстурээс пиксэлийн өнгөний мэдээллийг авах координатын утга болгож ашиглана гэсэн үг. Өнгө авах үйл ажиллагааг texture2D(texture, vTexCoord) функцийг дуудан биелүүлнэ. gl_FragColor-т өнгө оноосноор эцсийн дэлгэцэнд харагдах пикселийн өнгө бие болно.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- vertex shader програмын код -->
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;
    attribute vec2 aTexCoord;
   
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;
   
    varying vec4 vColor;
    varying vec2 vTexCoord;
 
    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vColor = aVertexColor;
        vTexCoord = aTexCoord;
    }
</script>

<!-- fragment shader програмын код -->
<script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;
   
    uniform sampler2D texture;
 
    varying vec4 vColor;
    varying vec2 vTexCoord;
 
    void main(void) {
        gl_FragColor = texture2D(texture, vTexCoord);
    }
</script>

За ингээд текстур буулгалт хийгдсэн үр дүнг харцгаая.

Харин одоо бичлэгийн үндсэн зорилго болох тугаа хэрхэн намируулах вэ гэдэг дээр анхаарлаа хандуулъя.
Ямар нэгэн анимэшн хийхэд тэр анимацийг хөдөлгөгч хүч болсон хугацааны өөрчлөлтийн утга хэрэг болдог. Тэр утгыг фрэйм бүрт зарцуулсан делта хугацааг цуглуулан нэмэх замаар гаргаж авъя.
Үүний тулд хамгийн эхлээд програм ажиллаж эхэлснээс хойшхи deltaTime-уудыг цуглуулах animationTime хувьсагчийг зарлая.

1
    var animationTime = 0.0;

Харин одоо deltaTime хугацааг тооцон олоод animationTime дээр нэмэе

1
2
3
4
5
6
7
    var deltaTime;
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
       deltaTime = (timeNow - lastTime)/1000.0;
       animationTime += deltaTime;
    }
    lastTime = timeNow;

Ингээд animationTime утга байнга өөрчлөгдөх тул энэ утгаа GPU дээрхи shader програмдаа дамжуулж өгнө. Үүний тулд shader програмаа үүсгэх хэсэгт uniform-доо зориулсан хувсагч тохируулж өгнө

1
    shaderProgram.timeUniform = gl.getUniformLocation(shaderProgram, "time");

animationTime хувьсагчийн утгыг GPU shader програм дэх “time” гэсэн uniform хувьсагчид онооё. “time” хувьсагчийг GPU дотор ашиглах боломжтой боллоо.

1
    gl.uniform1f(shaderProgram.timeUniform, animationTime);

Туг намируулах үүрэг бүхий vertex shader програмын коод

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;
    attribute vec2 aTexCoord;
   
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;
    uniform float time;
   
    varying vec4 vColor;
    varying vec2 vTexCoord;
 
    void main(void) {
        vec3 tempPos = aVertexPosition;
        float waveWidth  = 0.8;
        float waveHeight = 0.9;
        tempPos.z = sin(waveWidth * tempPos.x + time) * cos(waveWidth * tempPos.y + time) * waveHeight;
        gl_Position = uPMatrix * uMVMatrix * vec4(tempPos, 1.0);
        vColor = aVertexColor;
        vTexCoord = aTexCoord;
    }
</script>

Энэ кодоо дэлгэрүүлбэл shader-т орж ирж байгаа vertex-ийн байрлалыг илтгэх aVertexPosition хувьсагчид байгаа утгыг эхлээд хадгалж авна. Учир нь attribute-ийн талбарыг өөрчилж болдоггүй юмаа.

1
    vec3 tempPos = aVertexPosition;

Мөн туг намируулах алгоритм хэрэг болох долгиолуулалтын өргөн өндөр утгуудыг тодорхойлоё

1
2
    float waveWidth  = 0.8;
    float waveHeight = 0.9;

Харин одоо тугныхаа Z тэнхлэгийн дагуу vertex-ийн байрлалыг өөрчилөх кодоо тодорхойлъё, наашаа цаашаа долгиолуулахын тулд vertex-ийн байрлалд хугацааны өөрчлөлтийг тригонометрийн radian ий өөрчлөлт мэт бодон sin болон cos функцуудыг ашиглая. Тригонометрээ санаж байгаа гэдэгт найдаж байна.

1
    tempPos.z = sin(waveWidth * tempPos.x + time) * cos(waveWidth * tempPos.y + time) * waveHeight;

Ингээд намирч байгаа тугны анимацийн үр дүнг харцгаая.

Програмын бүрэн кодыг харахыг хүсвэл
https://dl.dropbox.com/u/3482121/script/hicheelinfotutorial/MongolianFlag/flagwave.html

Сурталчилгаан дээр дapж биднийг дэмжээрэй. ↓ ↓ ↓

3 сэтгэгдэлтэй

  1. Noname says:

    Uneheer sain hicheel bolson bna andaa . Tnks

  2. tsoomoo says:

    Vrgeljleliig ni hvleej blaa. Sonirholtoi hicheel bolson bna :)

Сэтгэгдэл бичих

*

Таныг илтгэгч аватар зураг GrAvatar.com (Хэрхэн ашиглах вэ?)

© 2010-2012 Hicheel.inFo .
Wordpress дээр суурьлаж Theme Junkie ашиглан SaKu бүтээв. HOSTED BY DUSAL HOSTING
Ангилал 3D график програмчлал (2 дахь хичээл 7 хичээлээс)