logo
Montag 6. Februar 2012
Zurück zur Kapitelübersicht

O3D Tutorial 2: Einen Würfel zeichnen

Beitrag geschrieben von King of Darkness am 2010-02-09

Kurzbeschreibung:
Im letzten Tutorial habe ich euch gezeigt wie das Grundgerüst einer O3D Anwendung aussehen kann. Jetzt ist an der Zeit etwas damit an zu fangen.

In diesem Tutorial werden wir einen sich drehenden Würfel erzeugen. Der Code den du hier findest kommt größten teils aus den O3D Samples, Ich habe lediglich ein paar kleinere Änderungen daran gemacht. Ich dachte mir das der Code aus dem Beispiel perfekt in dieses Tutorial passen würde.

Das Gründgerüst ist das gleiche wie aus dem ersten Tutorial, nur das hier der für den Würfel dazu gekommen ist.

Code:

<html>
   <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <title>Tutorial 2: Drawing a cube</title>
      <script type="text/javascript" src="o3djs/base.js"></script>
      <script type="text/javascript">
         o3djs.require('o3djs.util');
         o3djs.require('o3djs.math');
         o3djs.require('o3djs.rendergraph');

         // Events
         // Run the init() function once the page has finished loading.
         // Run the uninit() function when the page has is unloaded.
         window.onload = init;
         window.onunload = uninit;

         // global variables
         var g_o3d;
         var g_math;
         var g_client;
         var g_pack;
         var g_clock = 0;
         var g_timeMult = 1;
         var g_cubeTransform;

         function createCube(material) {
           var cubeShape = g_pack.createObject('Shape');
           var cubePrimitive = g_pack.createObject('Primitive');
           var streamBank = g_pack.createObject('StreamBank');

           cubePrimitive.material = material;
           cubePrimitive.owner = cubeShape;
           cubePrimitive.streamBank = streamBank;

           cubePrimitive.primitiveType = g_o3d.Primitive.TRIANGLELIST;
           cubePrimitive.numberPrimitives = 12; // 12 triangles
           cubePrimitive.numberVertices = 8;    // 8 vertices in total

           var positionArray = [
             -0.5, -0.5,  0.5,  // vertex 0
              0.5, -0.5,  0.5,  // vertex 1
             -0.5,  0.5,  0.5,  // vertex 2
              0.5,  0.5,  0.5,  // vertex 3
             -0.5,  0.5, -0.5,  // vertex 4
              0.5,  0.5, -0.5,  // vertex 5
             -0.5, -0.5, -0.5,  // vertex 6
              0.5, -0.5, -0.5   // vertex 7
           ];

           var indicesArray = [
               0, 1, 2,  // face 1
               2, 1, 3,
               2, 3, 4,  // face 2
               4, 3, 5,
               4, 5, 6,  // face 3
               6, 5, 7,
               6, 7, 0,  // face 4
               0, 7, 1,
               1, 7, 3,  // face 5
               3, 7, 5,
               6, 0, 4,  // face 6
               4, 0, 2
           ];

           var positionsBuffer = g_pack.createObject('VertexBuffer');
           var positionsField = positionsBuffer.createField('FloatField', 3);
           positionsBuffer.set(positionArray);

           var indexBuffer = g_pack.createObject('IndexBuffer');
           indexBuffer.set(indicesArray);

           streamBank.setVertexStream(
               g_o3d.Stream.POSITION, //  This stream stores vertex positions
               0,                     // First (and only) position stream
               positionsField,        // field: the field this stream uses.
               0);                    // start_index:
           // Associate the triangle indices Buffer with the primitive.
           cubePrimitive.indexBuffer = indexBuffer;

           return cubeShape;
         }

         /**
          * This method gets called every time O3D renders a frame.
          * Here's where we update the cube's transform to make it spin.
          * @param {o3d.RenderEvent} renderEvent The render event object
          * that gives us the elapsed time since last time a frame was rendered.
          */
         function renderCallback(renderEvent) {
           g_clock += renderEvent.elapsedTime * g_timeMult;
           // Rotate the cube around the Y axis.
           g_cubeTransform.identity();
           g_cubeTransform.rotateY(2.0 * g_clock);
         }

         /**
          * Creates the client area.
          */
         function init() {
           o3djs.util.makeClients(initStep2);
         }

         /**
          * Initializes O3D.
          * @param {Array} clientElements Array of o3d object elements.
          */
         function initStep2(clientElements) {
           // Initializes global variables and libraries.
           var o3dElement = clientElements[0];
           g_client = o3dElement.client;
           g_o3d = o3dElement.o3d;
           g_math = o3djs.math;

           // Initialize O3D sample libraries.
           o3djs.base.init(o3dElement);

           // Create a pack to manage the objects created.
           g_pack = g_client.createPack();

           // Create the render graph for a view.
           var viewInfo = o3djs.rendergraph.createBasicView(
               g_pack,
               g_client.root,
               g_client.renderGraphRoot);

            // Set up a simple orthographic view.

           viewInfo.drawContext.projection = g_math.matrix4.perspective(
               g_math.degToRad(30), // 30 degree fov.
               g_client.width / g_client.height,
               1,                  // Near plane.
               5000);              // Far plane.

           // Set up our view transformation to look towards the world origin
           // where the cube is located.
           viewInfo.drawContext.view = g_math.matrix4.lookAt([0, 1, 5], // eye
                                                     [0, 0, 0],  // target
                                                     [0, 1, 0]); // up

           // Create an Effect object and initialize it using the shaders
           // from the text area.
           var redEffect = g_pack.createObject('Effect');
           var shaderString = document.getElementById('effect').value;
           redEffect.loadFromFXString(shaderString);

           // Create a Material for the mesh.
           var redMaterial = g_pack.createObject('Material');

           // Set the material's drawList.
           redMaterial.drawList = viewInfo.performanceDrawList;

           // Apply our effect to this material. The effect tells the 3D
           // hardware which shaders to use.
           redMaterial.effect = redEffect;

           // Create the Shape for the cube mesh and assign its material.
           var cubeShape = createCube(redMaterial);

           // Create a new transform and parent the Shape under it.
           g_cubeTransform = g_pack.createObject('Transform');
           g_cubeTransform.addShape(cubeShape);

           // Parent the cube's transform to the client root.
           g_cubeTransform.parent = g_client.root;

           // Generate the draw elements for the cube shape.
           cubeShape.createDrawElements(g_pack, null);

           // Set our render callback for animation.
           // This sets a function to be executed every time frame is rendered.
           g_client.setRenderCallback(renderCallback);
         }

         /**
          * Removes any callbacks so they not called after the page has unloaded.
          */
         function uninit() {
           if (g_client) {
             g_client.cleanup();
           }
         }

      </script>
   </head>
   <body>
      <h1>Tutorial 2: Drawing a cube</h1>
      We now put our application to good use by drawing a spinning cube.
      <br/>

      <div id="o3d" style="width: 300px; height: 300px;"></div>
      <div style="display:none">
         <!-- Start of effect -->
         <textarea id="effect">
           // World View Projection matrix that will transform the input
           // vertices to screen space.
           float4x4 worldViewProjection : WorldViewProjection;

           // input parameters for our vertex shader
           struct VertexShaderInput {
             float4 position : POSITION;
           };

           // input parameters for our pixel shader
           struct PixelShaderInput {
             float4 position : POSITION;
           };

           /**
            * The vertex shader transforms the input vertices to screen space.
            */
           PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
             PixelShaderInput output;

             // Multiply the vertex positions by the worldViewProjection matrix
             // to transform them to screen space.
             output.position = mul(input.position, worldViewProjection);
             return output;
           }

           /**
            * This pixel shader just returns the color red.
            */
           float4 pixelShaderFunction(PixelShaderInput input): COLOR {
             return float4(1, 0, 0, 1);  // Red.
           }

           // Here we tell our effect file *which* functions are
           // our vertex and pixel shaders.

           // #o3d VertexShaderEntryPoint vertexShaderFunction
           // #o3d PixelShaderEntryPoint pixelShaderFunction
           // #o3d MatrixLoadOrder RowMajor
         </textarea>
         <!-- End of effect -->
    </div>
  </body>
</html>








Jetzt schauen wir uns den hinzugekommenen Code mal etwas genauer an:
Als erstes sagen wir O3D das wir einen Renderer brauchen:
Code:

o3djs.require('o3djs.rendergraph');

Als nächstes haben wir ein paar weitere Globale Variablen
Code:

         var g_pack;
         var g_clock = 0;
         var g_timeMult = 1;
         var g_cubeTransform;

Bevor wir zur Funktion kommen die den Würfel erstellt will ich noch auf die extra Zeilen in der init Funktion eingehen.

Hier wird eine art Container erstellt der sich um alle Objekte kümmert die wir erzeugen.
Code:

g_pack = g_client.createPack();

Die nächsten paar Zeilen erzeugen die View, in diesem Falle haben wir einen Sichtwinkel von 30 Grad mit Blickrichtung auf den Punkt [0,0,0] im Koordinatensystem.
Code:

           var viewInfo = o3djs.rendergraph.createBasicView(
               g_pack,
               g_client.root,
               g_client.renderGraphRoot);

           // Set up a simple orthographic view.
           viewInfo.drawContext.projection = g_math.matrix4.perspective(
               g_math.degToRad(30), // 30 degree fov.
               g_client.width / g_client.height,
               1,                  // Near plane.
               5000);              // Far plane.

           // Set up our view transformation to look towards the world origin
           // where the cube is located.
           viewInfo.drawContext.view = g_math.matrix4.lookAt([0, 1, 5], // eye
                                                     [0, 0, 0],  // target
                                                     [0, 1, 0]); // up

Die nächsten 3 Zeilen laden Effekt Konfiguration aus einem HTML textarea Formular und speichern es in dem Effekt Objekt. Dieses Objekt wird dann auf den Würfel angewandt, in unserem Fall ist das lediglich die Farbe rot.
Code:

           var redEffect = g_pack.createObject('Effect');
           var shaderString = document.getElementById('effect').value;
           redEffect.loadFromFXString(shaderString);

In den nächsten 3 Zeilen erstellen wir das Material, was unserem Würfel sagt wie er aussiehen soll.
Code:

           var redMaterial = g_pack.createObject('Material');
           redMaterial.drawList = viewInfo.performanceDrawList;
           redMaterial.effect = redEffect;

Als nächstes rufen wir die Funktion auf die alle nötigen Daten erzeugt die man zum Anzeigen eines Würfels braucht. Auf diese Funktion gehe ich in dem nächsten Abschnitt weiter ein.
Code:

          var cubeShape = createCube(redMaterial);

Jetzt müssen wir die Zeichenelemente welche den Würfel malen in den Container packen der dafür verwendet wird den Würfel anzuzeigen.
Code:

           cubeShape.createDrawElements(g_pack, null);

Hier wird ein Tranform-Objekt erzeugt und der Würfel zum Clientbereich hinzugefügt.
Code:

           g_cubeTransform = g_pack.createObject('Transform');
           g_cubeTransform.addShape(cubeShape);
           g_cubeTransform.parent = g_client.root;

Und als letztes setzen wir die Render Callback Funktion, welche das Bild bei jedem Frame Updatet
Code:

           g_client.setRenderCallback(renderCallback);



Jetzt definieren wir die oben angesprochene createCube(material) Funktion die unseren Würfel erstellen soll. Sie nimmt den Parameter material entgegen von dem etwas weiter oben schon gesprochen wurde.

Als erstes erstellen wir ein paar Objekte die uns helfen den Würfel zu erstellen. cubeShape ist das Objekt was wir dann zurück geben. cubePrimitive ist ein Objekt was in unserem Fall eine liste von Dreiecken sein wird. streamBank ist ein Container mit den ecken die wir erstellen.
Code:

           var cubeShape = g_pack.createObject('Shape');
           var cubePrimitive = g_pack.createObject('Primitive');
           var streamBank = g_pack.createObject('StreamBank');

Als nächstes fügen wir das material zu der Liste der Dreiecke hinzu und sagen der Liste zu welchem Objekt es gehört und fügen dann noch die Ecken hinzu. Das ganze fügt also die Einzelteile unseres Würfels zusammen.
Code:

           cubePrimitive.material = material;
           cubePrimitive.owner = cubeShape;
           cubePrimitive.streamBank = streamBank;

Jetzt sagen wir unserer Liste das sie eine Liste aus Dreiecken ist mit 12 Dreiecken (ein Würfel hat 6 Seiten mit je 2 Dreiecken) und 8 Ecken.
Code:

           cubePrimitive.primitiveType = g_o3d.Primitive.TRIANGLELIST;
           cubePrimitive.numberPrimitives = 12; // 12 triangles
           cubePrimitive.numberVertices = 8;    // 8 vertices in total


Dann müssen wir ein Array erstellen welches die Koordinaten jeder ecke unseres Würfels beinhaltet. Wenn man mit den Werten etwas rumspielt können wir die Flächen des Würfels, es würde ja immer wieder ein Objekt ergeben welches 8 Ecken hat.
Code:

          var positionArray = [
             -0.5, -0.5,  0.5,  // vertex 0
              0.5, -0.5,  0.5,  // vertex 1
             -0.5,  0.5,  0.5,  // vertex 2
              0.5,  0.5,  0.5,  // vertex 3
             -0.5,  0.5, -0.5,  // vertex 4
              0.5,  0.5, -0.5,  // vertex 5
             -0.5, -0.5, -0.5,  // vertex 6
              0.5, -0.5, -0.5   // vertex 7
           ];

Jetzt erstellen wir die Dreiecke die die Seiten des Würfels bilden. Jeweils 3 Nummern bilden die Ecken unseres Dreiecks. Wichtig dabei ist das die Reihenfolge in der die Nummern angegeben werden von Bedeutung ist. Die Ecken müssen immer im Uhrzeigersinn verlaufen, der Renderer würde sonst denke das die sichtbare Seite des Dreiecks von uns weg zeigt und würde es daher nicht zeichnen. Falls so etwas mal passiert, ist das nicht schlimm, das ist mir auch schon oft passiert.
Code:

          var indicesArray = [
               0, 1, 2,  // face 1
               2, 1, 3,
               2, 3, 4,  // face 2
               4, 3, 5,
               4, 5, 6,  // face 3
               6, 5, 7,
               6, 7, 0,  // face 4
               0, 7, 1,
               1, 7, 3,  // face 5
               3, 7, 5,
               6, 0, 4,  // face 6
               4, 0, 2
           ];

Jetzt fügen wir die Ecken die wir erstellt haben in einen Vertex Buffer hinzu.
Code:

           var positionsBuffer = g_pack.createObject('VertexBuffer');
           var positionsField = positionsBuffer.createField('FloatField', 3);
           positionsBuffer.set(positionArray);

Und erzeugen einen buffer für unsere Dreiecks Array
Code:

           var indexBuffer = g_pack.createObject('IndexBuffer');
           indexBuffer.set(indicesArray);

Jetzt fügen wir das ganze zu unserem Objekt hinzu was uns unseren Wüfel zusammen baut.
Code:

          streamBank.setVertexStream(
               g_o3d.Stream.POSITION, //  This stream stores vertex positions
               0,                     // First (and only) position stream
               positionsField,        // field: the field this stream uses.
               0);                    // start_index:
           // Associate the triangle indices Buffer with the primitive.
           cubePrimitive.indexBuffer = indexBuffer;

Und zu guter letzt geben wir unser Objekt was wir erstellt haben zurück
Code:

           return cubeShape;

Als nächstes schauen wir uns die Funktion an die bei jedem Durchlauf das Bild Updatet. Alles was diese Funktion macht ist das unser Würfel bei jedem Aufruf etwas um die Y-Achse gedreht wird.
Code:

         function renderCallback(renderEvent) {
           g_clock += renderEvent.elapsedTime * g_timeMult;
           // Rotate the cube around the Y axis.
           g_cubeTransform.identity();
           g_cubeTransform.rotateY(2.0 * g_clock);
         }


Im letzten Abschnitt werfen wir einen Blick auf die Effekte. In eine Textarea packen wir den Code der für den Effekt zuständig ist. Diesen Code laden wir dann mit JavaScript in unser Objekt. Wie genau die Effekte Funktionieren werde ich in einem Späteren Tutorial erklären, das hier reicht aber erstmal für den Anfang.

Als erstes definieren wir Projektion, welche aussagt wie das anzuzeigende berechnet wird.
Code:

float4x4 worldViewProjection : WorldViewProjection;

Dann erzeugen wir ein paar structs für die Parameter der vertex und pixel shaders.
Code:

           struct VertexShaderInput {
             float4 position : POSITION;
           };

           struct PixelShaderInput {
             float4 position : POSITION;
           };

           /**
            * The vertex shader transforms the input vertices to screen space.
            */

Jetzt definieren wir eine Funktion welche die mitgegebenen Daten auf unseren Projektor umformt bzw. berrechnet.
Code:

           PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
             PixelShaderInput output;
             output.position = mul(input.position, worldViewProjection);
             return output;
           }

Der Pixel shader sagt für jeden Bildpunkt (Pixel) welche Farbe gesetzt wird, in unserem Fall ist das einfach die Farbe rot.
Code:

           float4 pixelShaderFunction(PixelShaderInput input): COLOR {
             return float4(1, 0, 0, 1);  // Red.
           }

Die letzten Zuweisungen für die Funktionen die oben erstellt wurden die relevant sind für O3D, das diese auch korrekt angesprochen werden können. Es sieht zwar so aus als wenn diese Zeilen Auskommentiert sind, aber diese Zeilen sind sehr wichtig.
Code:

           // #o3d VertexShaderEntryPoint vertexShaderFunction
           // #o3d PixelShaderEntryPoint pixelShaderFunction
           // #o3d MatrixLoadOrder RowMajor

Wenn du jetzt den Code im Browser ausführst und alles korrekt ist wirst du einen rotierenden Würfel sehen.

Zurück zur Kapitelübersicht

Um eine Kapitel zu bewerten musst du eingeloggt sein.