Inicializar DelphiX
- Capitulo I de la serie "DelphiX 3D" -


Autor  

 Deimos

Ámbito  

 Iniciación a DelphiX

Lenguaje de Programación  

 Delphi 4

Fecha  

 2002/03/16

    Con este tutorial comenzamos una serie de tutoriales cuyo fin es desarrollar una escena 3D compleja usando los componentes de la suite DelphiX.

    En este primer tutorial nos familiarizaremos con la suite DelphiX y crearemos una sencilla aplicación que nos proporcionará una plantilla básica sobre la que se irán construyendo posteriores tutoriales.

    Comencemos.


    El primer paso es crear nuestra aplicación 3D, para ello procedemos como con cualquier otra aplicación Delphi (si no estás familiarizado con Delphi te sugiero que empieces con un tutorial más básico pues voy a dar por sabidas muchas cosas relacionadas con el IDE y el desarrollo de una aplicación con Delphi). Una vez tengamos la Ficha (Form) en pantalla, y antes de hacer nada, salvamos el proyecto. Para el proyecto sugiero el nombre D3D7Ini y para la unit principal UD3D7Ini, por otra parte son los nombres que encontrará en los fuentes.

    El segundo paso es preparar la ficha para trabajar con DirectX, para lo cual ponemos en ella el componente DXDraw, que encontraremos en la pestaña DelphiX de la paleta de componentes si hemos instalado correctamente la suite.

    Poner el componente no es suficiente, aún hemos de hacer un par de cosas.

    La primera es incluir en la cláusula uses de la sección interface las units: DXDraw, DXClass, y DirectX.

    La segunda es transformar la Ficha en una ficha DirectX con el siguiente cambio:

        TForm1 = class(TDXForm)

    Es decir hemos de cambiar el ascendiente de la Ficha TForm por TDXForm

    Por último, en el inspector de objetos, seleccionamos el componente DXDraw y en la propiedad Align seleccionamos alClient.

    Ya tenemos una ficha DirectX, sólo falta comprobar que las opciones activas del componente DXDraw son las correctas. Aprovechando que ya tenemos en el inspector de objetos el componente DXDraw, buscamos la propiedad options y nos aseguramos de tenerla igual que en la imagen1:

(Imagen 1)

    Generalmente tendremos la opción do3D a False.

    Sólo comentaré las opciones mas importantes:

doFullScreen := False; con este valor nos aseguramos de trabajar en modo ventana. Si ponemos el valor a True la aplicación cambiará a pantalla completa.
do3D := True; habilita las opciones de renderizado 3D.
doDirectX7Mode := True; selecciona el driver DirectX 7, necesario para usar las API DirectX7
doHardware := True; selecciona el renderizado por hardware.
doZBuffer := True; activa el ZBuffer. Si está seguro de que su tarjeta gráfica soporta el ZBuffer áctive la opción.

    Llegados a este punto, hemos de decidir cómo vamos a controlar el bucle de renderización, bien mediante un cronómetro (timer) o usando el evento OnIdle de la aplicación. Ambos métodos tienen sus ventajas y desventajas, como todo en la vida 8D, pero ya que estamos con Delphix vamos a usar el cronómetro que instala la suite el cual es considerablemente mas rápido que el que tenemos en la pestaña System de la paleta de componentes. Así pues ponemos el componente DXTimer en la ficha. Finalmente, en el inspector de objetos, cambiamos el valor de la propiedad interval y lo ponemos a 1 para obtener el máximo rendimiento. También conviene poner la propiedad Enabled a False. Una vez hecho esto la ficha debería tener el aspecto de la imagen 2:



(Imagen 2)

    Puesto que no vamos a usar ningún otro componente de la suite, ha llegado la hora de empezar a teclear código. 

    Aprovechando que estamos en el inspector de objetos seleccionamos el componente Form1 (si le hemos cambiado el nombre en el inspector de objetos buscamos dicho nombre) y activamos el evento OnCreate donde pondremos el siguiente código:

procedure TForm1.FormCreate(Sender: TObject);

begin
    DXTimer1.Enabled:=False;

    If Not(doFullScreen in DXDraw.Options) then
    DXDraw.Options:=DXDraw.Options-[doFullScreen];

    If Not(do3D in DXDraw.Options) then
    DXDraw.Options:=DXDraw.Options+[do3D];

    If Not(doDirectX7Mode in DXDraw.Options) then
    DXDraw.Options:=DXDraw.Options+[doDirectX7Mode];

    If Not(doHardware in DXDraw.Options) then
    DXDraw.Options:=DXDraw.Options+[doHardware];

    If Not(doZBuffer in DXDraw.Options) then
    DXDraw.Options:=DXDraw.Options+[doZBuffer];
end;

    Con este código nos aseguramos de que tenemos las opciones correctas en el componente DXDraw.

    En este evento conviene inicializar todas aquellas variables y estructuras que vayan a tener un papel crítico en la aplicación.

    Nótese que en la primera línea del evento desactivamos el cronómetro. Si no lo hacemos así, el proceso de inicialización puede verse interrumpido por una llamada al bucle de renderizado lo que, con toda seguridad, generará un error en tiempo de ejecución.

    A continuación activamos el evento OnInitialize del componente DXDraw donde pondremos el resto de código de inicialización de nuestra escena. A la salida del evento reactivamos el cronómetro.

procedure TForm1.DXDrawInitialize(Sender: TObject);

begin
    // Reactivamos el bucle de renderizado
    DXTimer1.Enabled:=True;
end;

    En nuestro caso sólo hemos de reactivar el cronómetro pues no necesitamos mas inicialización básica.

    Ya estamos listos para crear el área de visualización, vista, o viewport (en la medida de lo posible, castellanizaré todos aquellos términos que resulten confusos en su versión inglesa, así, si bien viewport puede hacer referencia a toda el área de la aplicación directx, ya sea en modo ventana, ya sea a pantalla completa, DirectX permite el uso de múltiples viewports, o vistas, por lo que en una misma ventana podríamos tener 2, 3, o mas viewports, por lo que la equivalencia viewport = ventana sólo es válida cuando tenemos sólo uno y sus dimensiones coinciden con las del área de la aplicación).

    El código de inicialización de la vista lo pondremos en el evento OnInitializeSurface del componente TDXDraw:

procedure TForm1.DXDrawInitializeSurface(Sender: TObject);

var
    Vista : TD3DViewport7;

begin
    // Inicializa la vista de la escena
    Fillchar(Vista, SizeOf(Vista),0);

    With Vista Do
    begin
        dwx:=DXDraw.Top;
        dwy:=DXDraw.Left;
        dwWidth:=DXDraw.SurfaceWidth;
        dwHeight:=DXDraw.SurfaceHeight;
        // para efectos especiales hacer MinZ=MaxZ
        // en cuanquier caso, MinZ y MaxZ sólo pueden tomar valores entre 0.0 y 1.0
        dvMinZ:=0.0;
        dvMaxZ:=1.0;
        end;

    DXDraw.D3DDevice7.SetViewport(Vista);
end;

    Como vemos, para inicializar la vista donde vamos a poner nuestra escena 3D hacemos una llamada a la función SetViewPort de la propiedad D3DDevice7 de nuestro componente DXDraw. Para ello hemos de pasar los datos de la vista en una estructura de tipo TD3DViewport7. Ya que nuestra vista va a ocupar toda el área de la aplicación, la posición de la esquina superior izquierda de la vista coincide con la esquina superior izquierda del componente DXDraw, así mismo hacemos que la altura y la anchura de la aplicación coincida con la del área del componente DXDraw. 

    No es obligatorio poner las dimensiones de DXDraw, sin embargo, hacer que la vista tenga dimensiones superiores a DXDraw tendrá como efecto dejar fuera del área visible aquellos elementos que no se rendericen dentro de los límites de DXDraw (lo que no significa que no se hayan rendirazado), en cambio una vista con menores dimensiones no usará toda el área disponible para el renderizado. Puesto que no espero que se fíe de mi palabra, experimente con los valores dwx, dwy, dwWidth, y dwHeight.

    Por último, y como se indica en los comentarios insertados en el código, los valores MinZ y MaxZ han de estar en el rango 0.0-1.0. Para renderizar completamente la escena los valores son los que se han puesto en el código. Sólo en caso de buscar efectos especiales es útil dar el mismo valor a MinZ y a MaxZ. Así, si sólo queremos que se rendericen los elementos en el primer plano igualaremos a 0.0 ambos valores; los igualaremos a 1.0 si queremos que sólo se rendericen los elementos del fondo (background).

    Con la vista inicializada ya podemos poner algo en nuestra escena. En nuestro caso sólo vamos a poner el fondo de color azul y mostrar un texto que nos dé los Fotogramas Por Segundo (Frames Per Second: FPS) y el nombre del ejemplo tutorial.

    Para ello vamos a crear nuestro bucle de renderización en el evento OnTimer del cronómetro:

procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);

var
    Rect3D : TD3DRect;

begin
    // Funcion de renderizado
    If not DXDraw.CanDraw Then
        Exit;

    // Borra el frame actual (pone la escena de color azul)
    Rect3D.x1:=DXDraw.Top;
    Rect3d.y1:=DXDraw.Left;
    Rect3D.x2:=DXDraw.SurfaceWidth;
    Rect3D.y2:=DXDraw.SurfaceHeight;

    // Borramos la escena vieja.
    If DXDraw.ZBuffer<>Nil Then
        DXDraw.D3DDevice7.Clear(1, Rect3D, D3DCLEAR_TARGET or 
                                                      D3DCLEAR_ZBUFFER,
                                                      RGB_MAKE(0,0,255), 1.0, 0 )
    Else
        DXDraw.D3DDevice7.Clear(1, Rect3D, D3DCLEAR_TARGET, 
                                                      RGB_MAKE(0,0,255), 1.0, 0 );

        // Dibujamos la nueva escena.
        DXDraw.D3DDevice7.BeginScene;
        DXDraw.D3DDevice7.EndScene;

        { Draw FrameRate }
        with DXDraw.Surface.Canvas do
        begin
            try
                Brush.Style := bsClear;
                Font.Color := clYellow;
                Font.Size := 12;
                Textout(15, 15, 'FPS: '+inttostr(DXTimer1.FrameRate));
                Textout(15, 29, 'Tutorial 1: Inicializando');
            finally
                Release; { Indispensable }
            end;
        end;

    // Sacamos la escena a pantalla
    DXDraw.Flip;
end;

    Aunque nuestra escena sólo pone un fondo azúl y un texto en nuestra vista, el código tiene varias cosas que comentar.

    Empezaremos por el principio (¿lógico, no? 8D): la primera línea.

If not DXDraw.CanDraw Then
    Exit;

    Esta línea de código nos asegura que DXDraw está preparado para renderizar una nueva escena. Es común a toda aplicación DelphiX ya sea 3D ó 2D y prescindir de ella es garantía de fracaso total 8D

    Tras esta primera línea tenemos el código que renderiza nuestra sencilla escena cambiadora de color.

    Antes de crear la escena, preparamos la vista borrando su contenido, lo que hacemos con una llamada a la función Clear de la propiedad D3DDevice7 de DXDraw. Esta función requiere los siguientes parámetros:

dwCount : Word -> él número de vistas a "limpiar". Dado que sólo hemos definido 1 vista este valor es 1. También sería 1 si teniendo mas de 1 vista sólo queremos borrar el contenido de una de ellas.

lpRects : TD3DRect -> dimensiones de la(s) vista(s) a "limpiar". Este valor puede ser el puntero del buffer en el que tendremos todas las estructuras TD3DRect con las dimensiones de las vistas que queremos limpiar si dwCount >1

dwFlags : DWord -> flag que regula el comportamiento de la función. Este flag puede ser:

D3DCLEAR_TARGET -> limpia la vista seleccionada
D3DCLEAR_ZBUFFER -> limpia el Z-Buffer (si existe)
D3DCLEAR_STENCIL -> limpia el buffer de pixels (stencil buffer) o una combinación de los 3 mediante el operador lógico Or.

dwColor : TD3DColor -> color usado para "limpiar" la(s) vista(s). Este color también se usa para limpiar el Z-Buffer (si existe)

dvZ : TD3DValue -> Nivel al que se "limpiará" el Z-Buffer. Este es un valor en el rango 0.0-1.0; para 0.0 se borra lo que esté en el primer plano, borrándose todo el buffer hasta su máxima profundidad para el valor 1.0.

dwStencil : DWord -> valor al que se modificarán los píxels del Buffer de pixels (Stencil Buffer) si existe.

    Una vez explicados los parámetros que necesita la función Clear creo que no hace falta explicar el porqué la hemos incluído en un IF-THEN-ELSE que controla si tenemos activo, o no, el Z-Buffer.

    Para fijar el color que usaremos para "limpiar" la vista utilizamos la función RGB_MAKE que devuelve un valor de tipo TD3DColor y que requiere los valores Red, Green, Blue (rojo, verde, azul) para componer un color en formato RGB. Sugiero al lector que experimente con estos valores teniendo en cuenta que han de estar en el rango 0-255.

    Los 2 últimos parámetros son ignorados o no dependiendo de la presencia de los flags D3DCLEAR_ZBUFFER y D3DCLEAR_STENCIL. Si no se pasan estos flags a la función, dichos parámetros son ignorados, sin embargo conviene darles un valor cualquiera para evitar errores de compilación (1.0, y 0, son valores seguros que no darán excesivos problemas si hay un error en la declaración de flags), pero si se incluyen los correspondientes flags hay que tener claros los valores que se pasen pues serán usados por la función.

    Antes de continuar con la explicación del código voy a dar un poco de teoría (si está familiarizado con el page flipping puede saltarse el párrafo siguiente)

    La función Clear no borra la vista tal cual, es decir, sin el código que sigue a la llamada a esta función, veríamos que la ventana no toma el color azul que queremos que tenga. Esto es debido a que lo que se borra no es la memoria de video "visible" o, por asi decirlo, de pantalla, sinó la memoria auxililar denominada BackBuffer. De hecho, la escena se renderiza sobre esta memoria auxiliar y al final del bucle de renderizado se vuelca a la memoria de pantalla para hacerla visible. A esta técnica se la denomina page flipping (intercambio de página). Dicho esto, volvamos al comentario del código.

    Lo siguiente que encontramos es una llamada a DXDraw.D3DDevice7.BeginScene seguida de una llamada a DXDraw.D3DDevice7.EndScene. Dado que nuestra aplicación sólo cambia de color nuestra vista y pone un sencillo texto en ella (lo cual comentaremos mas adelante) es lógico que entre estas 2 funciones no haya código alguno pues nada hay que renderizar, pero como ya habrá supuesto usted, aquí va el código que renderiza una escena 3D (p.ej. un cubo rotando sobre un eje).

    Con BeginScene notificamos al sistema que vamos a renderizar una escena 3D y nos asegura que podemos proceder. 

    Con EndScene notificamos que hemos acabado la renderización y nos habilita para renderizar otra escena. 

    Se puede encadenar el renderizado de varias escenas una detrás de otra siempre que las incluyamos entre los correspondientes BeginScene y EndScene, pero NO SE PUEDE INCLUIR EL RENDERIZADO DE UNA ESCENA DENTRO DE EL RENDERIZADO DE OTRA; es decir, un código como el siguiente:

DXDraw.D3DDevice7.BeginScene;
......
DXDraw.D3DDevice7.BeginScene;
.....
DXDraw.D3DDevice7.EndScene;
.....
DXDraw.D3DDevice7.EndScene;

sólo es garantía de problemas.

    Con nuestra escena ya renderizada, sólo falta poner el texto en pantalla y hacer el intercambio de página (page flipping).

    Para lo primero utilizamos el Canvas que incorpora el componente DXDraw. A todos los efecto el Canvas de DXDraw funciona exactamente igual que el que encontramos en cualquier otro componente Delphi que tenga la propiedad Canvas. La única diferencia es que hemos de encapsularlo en un Try-Finally-end poniendo en la sección Finally una llamada al método DXDraw.Surface.Canvas.Release para garantizar que todo irá como la seda :)) No le recomiendo que experimente con esta línea, le garantizo que los efectos de su ausencia son desastrosos, no sólo para la aplicación.

    Del intercambio de página (page flipping) se encarga el método DXDraw.Flip que ha de ser siempre la última instrucción de nuestro bucle de renderizado. Esta instrucción es la que se encarga de volcar todos los cambios hechos en el BackBuffer a nuestra vista.

    Con esto finalizamos este primer tutorial.


    En este primer tutorial hemos prescindido de todo código que verifique errores y asegure el correcto funcionamiento de la aplicación, dado que no era el objeto de este tutorial, sin embargo, en posteriores ejemplos iremos añadiendo código que monitorice el funcionamiento de la aplicación y genere el correspondiente mensaje de error si se produce alguno. En el siguiente tutorial aprenderemos cómo determinar las capacidades DirectX del hardware, los modos de video disponibles, y a seleccionar el que mas nos guste para nuestra aplicación.

    Por último recomendaros que tecleéis el código pues ello os ayudará a entender mejor lo explicado en este tutorial.


Descargar el Código Fuente de Ejemplo


 

 
Hosted by www.Geocities.ws

1