Este artigo foi traduzido por máquina.

Silverlight 3D

Desenvolvendo objetos 3D no Silverlight

Rajesh Lal

Baixar o código de exemplo

Neste artigo, mostrarei como desenvolver objetos 3D no Silverlight. Vou começar com um plano de fundo breve 3D e, em seguida, examinar alguns dos recursos avançados disponíveis no Silverlight que permitem a criação e exibição de objetos 3D. Examinarei um exemplo simples de um cubo e mostrar a você três maneiras diferentes para criar transformações 3D. Também explicarei quais elementos chaves necessários para exibir um objeto 3D na tela do computador. Finalmente, vou explorar como Silverlight 5 será criar objetos 3D muito mais ricas e permite que você vá além do que é disponível hoje.

O Silverlight oferece suporte a um sistema de coordenadas destro, o que significa que o eixo z positivo está apontando para o visualizador (consulte Figura1). Há três elementos principais de 3D que são necessários para exibir um objeto na tela:

  • Perspectiva
  • Transformação
  • Efeito da luz

A Reference Cube, Showing Sides with Perspective
Figura 1 uma referência cubo, mostrando os lados com perspectiva

Perspectiva significa que partes de objetos mais próximos nos apareçam maiores do que aqueles que estão mais distantes. Por exemplo, em a Figura 1, lateral bd parece maior do que o lado fh. Na vida real, o ponto de Vista cria um ponto de fuga, o que significa que se você estende as linhas ae, bf, cg e dh no eixo z, eles atenderão trás em um único ponto arbitrário.

O segundo aspecto é a transformação. Um objeto 3D, quando exibido em uma tela, deve permitir a movimentação no espaço 3D em todas as direções. Ela pode ser movida em qualquer eixo único — escalada para tamanho — ao mesmo tempo, mantendo intacta a perspectiva. Ele pode ser girados 360 graus em todos os eixos: x, y e z. Isso dá a um objeto 3D a flexibilidade necessária para a renderização na tela.

O último elemento de 3D é o efeito da luz. Luz em 3D cria o sombreamento, que é mais brilhante perto de uma fonte de luz e surge com distância. Processamento 3D, os dois tipos populares de sombreamento são sombreamento "flat" e "gradiente" sombreamento. Explicarei como elas diferem posteriormente. Luz também criará uma sombra nos lados opostos a fonte de luz.

Nos exemplos em breve, vou explorar três maneiras diferentes, na qual você pode criar objetos 3D no Silverlight:

  • Usando a perspectiva 3D
  • Usando vários quadros e um timer.
  • Usando primitivos com a biblioteca XNA

No primeiro método, um objeto é criado usando elementos bidimensionais, mas ele se parece e se comporta como se estivesse no espaço 3D. Perspectiva 3D é um tipo especial de recurso de transformação incluído no Silverlight 4 que permite transformações básicas, como rotação, dimensionamento e tradução no espaço 3D. O segundo método é o método de força bruta, onde você não criar o objeto 3D, mas em vez disso, cria os quadros de saída final para uma transformação específica e exibi-los usando um timer. O método final é criar um objeto 3D avançado bit por bit usando primitivos (uma lista de triângulos) com bibliotecas XNA, que estarão disponíveis no Silverlight 5. Vamos começar.

Criando um cubo usando perspectiva 3D

4 O Silverlight oferece suporte a uma classe de PlaneProjection (consulte a Figura 2), que pode ser usado para a propriedade de projeção de qualquer elemento de interface do usuário, como mostrado no diagrama de classes. A classe PlaneProjection permite transformações de perspectiva 3D no elemento de interface do usuário. Embora ele não permita-se diretamente para a criação de um objeto 3D, você pode usar vários "paredes" para criar um objeto e transformá-lo no espaço 3D.

The PlaneProjection Class
Figura 2 A classe PlaneProjection

A classe PlaneProjection permite que LocalOffset e GlobalOffset, que são usados para converter um objeto em relação a mesmo e, em relação ao outro elemento no espaço global, respectivamente. RotationX, RotationY e RotationZ permitirem girar o elemento no x, y e eixos z e CenterOfRotation permite um ponto central em relação ao plano do elemento.

No meu exemplo, para criar um "cubo", eu vai criar quatro lados do cubo e movê-los no espaço 3D, definindo suas propriedades PlaneProjection, conforme mostrado na a Figura 3.

Figura 3 definindo propriedades de PlaneProjection

<Grid x:Name="LayoutRoot" Background="White" Width="800" Height="700">
  <Rectangle Fill="#9900FF00" Width="250" Height="250" Visibility="Visible">
  <Rectangle.Projection>
    <PlaneProjection x:Name=
      "projectionFront" CenterOfRotationZ="125" RotationX="-180"/>
  </Rectangle.Projection>
</Rectangle>
<Rectangle Fill="#99FF0000" Width="250" Height="250" Visibility="Visible">
  <Rectangle.Projection>
    <PlaneProjection x:Name=
      "projectionBottom" CenterOfRotationZ="125" RotationX="-90" />
  </Rectangle.Projection>
</Rectangle>
<Rectangle Fill="#990000FF" Width="250" Height="250" Visibility="Visible">
  <Rectangle.Projection>
    <PlaneProjection x:Name="projectionBack" CenterOfRotationZ="125" />
  </Rectangle.Projection>
 </Rectangle>
<Rectangle Fill="#99FFFF00" Width="250" Height="250" Visibility="Visible">
  <Rectangle.Projection>
    <PlaneProjection x:Name=
      "projectionTop" CenterOfRotationZ="125" RotationX="90"/>
  </Rectangle.Projection>
</Rectangle>
</Grid>

Na a Figura 4, os lados são girados por 90, -90 e -180 graus para criar os planos de projeção superior, inferior e a parte frontal do cubo. O valor de CenterofRotationZ de 125 cria o ponto central pelo qual todos os planos de podem ser girados ao longo do eixo z.

Projecting the Sides to Simulate a 3D Wall
Figura 4 Projetando os lados para simular uma parede 3D

Depois que o cubo é criado usando o plano de projeção, eu preciso para girá-lo por x, y e z de eixos. Isso é onde posso usar o objeto storyboard no Silverlight. Posso criar três storyboards, uma para cada eixo, como mostrado na a Figura 5.

Figura 5 girando o cubo com Storyboards

<Storyboard x:Name="storyboardRotateX">
  <DoubleAnimation Storyboard.TargetName="projectionFront" 
    Storyboard.TargetProperty="RotationX" From="-180.0" To="180.0" Duration="0:0:10"
    RepeatBehavior="Forever"  />
  <DoubleAnimation Storyboard.TargetName="projectionBottom"
    Storyboard.TargetProperty="RotationX" From="-90.0" To="270.0" Duration="0:0:10"
    RepeatBehavior="Forever"  />
  <DoubleAnimation Storyboard.TargetName="projectionBack" 
    Storyboard.TargetProperty="RotationX" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever"   />
  <DoubleAnimation Storyboard.TargetName="projectionTop"
    Storyboard.TargetProperty="RotationX" From="90.0" To="450.0" Duration="0:0:10"
    RepeatBehavior="Forever"   />
  </Storyboard>
  <Storyboard x:Name="storyboardRotateY">
  <DoubleAnimation Storyboard.TargetName="projectionFront"
    Storyboard.TargetProperty="RotationY" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
  <DoubleAnimation Storyboard.TargetName="projectionBottom"
    Storyboard.TargetProperty="RotationY" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
  <DoubleAnimation Storyboard.TargetName="projectionBack"
    Storyboard.TargetProperty="RotationY" From="0.0" To="360.0" Duration="0:0:10" 
    RepeatBehavior="Forever" />
  <DoubleAnimation Storyboard.TargetName="projectionTop"
    Storyboard.TargetProperty="RotationY" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
  </Storyboard>
  <Storyboard x:Name="storyboardRotateZ">
  <DoubleAnimation Storyboard.TargetName="projectionFront"
    Storyboard.TargetProperty="RotationZ" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
  <DoubleAnimation Storyboard.TargetName="projectionBottom"
    Storyboard.TargetProperty="RotationZ" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
  <DoubleAnimation Storyboard.TargetName="projectionBack"
    Storyboard.TargetProperty="RotationZ" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
  <DoubleAnimation Storyboard.TargetName="projectionTop"
    Storyboard.TargetProperty="RotationZ" From="0.0" To="360.0" Duration="0:0:10"
    RepeatBehavior="Forever" />
</Storyboard>

Em cada storyboard, girar cada um dos quatro projeção planos para manter a estrutura do cubo intactos. Observe que para a rotação do eixo x, o valor de RotationX inicia do valor original RotationX do plano e vai outro 360 graus para ProjectionFront, portanto, ele inicia-180 graus e vai para 180 graus. Como você pode ver na a Figura 6, o cubo está pronto para rotação junto os eixos x, y e z, ele pode ser movido ao longo de qualquer eixo e oferece suporte a coloração de cada um dos lados.

The Cube, Ready for Rotation
Figura 6 O cubo, pronta para rotação

Neste exemplo, eu era facilmente capaz de criar um cubo e transformá-lo no espaço 3D sem muito código. Esta é a força real da transformação em perspectiva 3D. Para operações básicas de 3D, você deve usar essa opção. No entanto, ele vem com várias desvantagens. Para objetos 3D avançados, o número de planos de projeção necessários e suas configurações podem aumentar drasticamente, e você precisa calcular manualmente os ângulos entre cada plano de projeção e o CenterOfRotation. Um segundo problema é que a rotação do objeto 3D depende de storyboards, tornando mais intensivo de CPU; ele não usa a GPU para processar o objeto. Outro problema dessa abordagem é também processam a parte de trás do cubo, mesmo se não estiver visível — não é uma abordagem ideal.

O terceiro elemento principal necessário para a exibição de objetos 3D na tela é o efeito da luz. Na vida real, tem luz, então, como fazer você simular que no espaço 3D na tela? Conforme mencionado, duas maneiras bem conhecidas para isso são sombreamento simples e o sombreamento de gradiente.

Sombreamento constante leva em conta a superfície do plano e aplica um sombreamento médio ao longo do plano. Sombreamento gradiente (sombreamento Gouraud) usa um degradê para sombrear a superfície e leva em consideração os vértices do plano. O plano não é a tela plana sombreado, mas as cores de vértice de diferentes sombreadas, com base em "suaves" em vez disso.

Neste exemplo, cada um dos planos permite para cor de preenchimento (sombreamento simples), bem como gradiente de preenchimento (sombreamento gradiente), que pode ser usado para simular o efeito de luz. Discutirei essas posterior. Uma maneira rápida de simular o efeito de luz é com a sobreposição de um retângulo de gradiente Radial com transparência usando o seguinte código após o objeto 3D:

<Rectangle x:Name=
  "BulbGradient" Height="700" Width="800" Margin="0 50 0 0" Grid.Row="1"
  Visibility="Collapsed">
  <Rectangle.Fill>
    <RadialGradientBrush RadiusX="0.5" RadiusY="0.5" GradientOrigin="0.25,0.25">
      <GradientStop Color="#00000000" Offset="0"/>
      <GradientStop Color="#FF000000" Offset="2"/>
     </RadialGradientBrush>
  </Rectangle.Fill>
</Rectangle>

Criando um cubo usando quadros

A segunda maneira de criar uma experiência 3D é usando os quadros finais. Aqui, você não crie o objeto 3D propriamente dito, mas comece com a saída final necessária e exportá-lo como quadros individuais. Um número de programas de software de modelagem de 3D permitem criar objetos 3D e transformações que podem ser exportadas como vários quadros e então importadas para o Silverlight.

Neste exemplo, examinarei uma animação simples cubo e exportar sua rotação no x, y e z eixos em vários quadros de imagens. Figura 7 mostra oito quadros diferentes de uma rotação de cubo no eixo x. Neste exemplo, posso usar um conjunto mínimo de quadros para criar uma rotação de cubo, mas mais quadros por segundo de criar uma rotação mais suave e uniforme.

Eight Different Frames Simulating Rotation in the X Axis
Figura 7 oito diferentes quadros, simulando a rotação no eixo x

Para simular a rotação no Silverlight, posso usar um timer, conforme mostrado no código a seguir:

DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
timer.Tick += new EventHandler(Tick);
 
private void Tick(object o, EventArgs sender)
{
  string imageuri = "cube/" + axis + "/" + currentImageIndex + ".png";
  bgImage.Source = new BitmapImage(new Uri(imageuri, UriKind.RelativeOrAbsolute));
  if (currentImageIndex <= 8)
    currentImageIndex++;
  else
    currentImageIndex = 1;
}

Observe que se trata de uma versão simplificada do que você pode fazer com este método. Aqui estou usando imagens exportadas para manter a simplicidade, mas permite a um número de softwares 3D para exportação XAML, que cria polígonos com cores e gradientes em vez de imagens, fornecendo uma animação simples e mais suave. O timer também pode ser substituído por storyboards com animações.

Essa abordagem é simples. Você tem um requisito de transformação específica com um determinado objeto 3D. Você não precisa se preocupar sobre como criar o objeto 3D e essa técnica pode ser usada para criar qualquer tipo de objeto 3D — você não está mais limitado a um cubo simple. Este método pode ser usado para todos os tipos de transformações. Você pode traduzir, girar e dimensionar objetos 3D sofisticados. Até mesmo, ele simula o efeito de luz, que também se traduz diretamente a partir do software de modelagem.

A principal limitação dessa abordagem é a flexibilidade da programação contra o objeto 3D. Depois de exportar o objeto 3D, ele gera código estático que pode ser difícil de código contra. Você não pode mover o objeto com relação a outros elementos no seu aplicativo do Silverlight. Outra desvantagem é que o número de quadros necessários aumenta linearmente com cada transformação. Além do que, a renderização acontece na CPU, maior animações com um número maior de quadros deve ter um desempenho hit.

Isso nos leva à terceira abordagem de uso da biblioteca XNA nos futuros Silverlight 5, como você verá, supera a maioria dos problemas com as primeiras duas abordagens. Mas antes disso, vamos falar sobre como um objeto 3D traduz em uma tela 2D matematicamente — a lógica genius 3D.

Noções básicas sobre mundo, modo de exibição e matriz de projeção

Para exibir o objeto, você deve entender os três principais conceitos ou "espaços" e como o objeto é mapeado a partir do seu próprio espaço de objeto na tela:

  • Mundo
  • Exibição
  • Projeção

Figura 8 mostra a ordem na qual um objeto é mapeado para a tela.

The Order in Which a 3D Object Is Mapped to the Screen
Figura 8 A ordem na qual um objeto 3D é mapeado para a tela

O primeiro conjunto de coordenadas para um objeto 3D é o x, y e z coordenadas no espaço de objeto (também chamado de espaço do modelo). Essas coordenadas são em relação umas às outras com seus centros (0, 0, 0). Lembre-se, com coordenadas cartesianas direita, que eixo z positivo é que o visualizador.

Para um cubo 3D, o canto superior direito de lado frontal será (1,1,1) e o canto inferior esquerdo da parte de trás será (-1, -1, -1), conforme mostrado na a Figura 9. No espaço de objeto, as coordenadas são em relação uns aos outros e sua posição só pode variar de -1 a + 1. Para meu cubo usar a 75 por cento do espaço de objeto, preciso multiplicar cada coordenar por.75, portanto, o novo bserá (.75,.75,.75) e o novo g será (-.75,-.75,-.75).

Coordinates in 3D Space
Figura 9 coordenadas no espaço 3D

Quando o objeto é colocado no espaço do mundo, o próprio objeto não é movido, mas em vez disso, mapeado com relação às coordenadas mundo multiplicando suas coordenadas com a matriz de mundo. No espaço de mundo, você pode transformar o objeto 3D, movendo as coordenadas para converter o objeto, alterando o tamanho para dimensionar e alterar o ângulo de rotação do objeto. Para expressar as coordenadas do seu objeto no espaço do mundo, você precisa multiplicar cada posição de vértice com a matriz do mundo:

As coordenadas do mundo de objeto = coordenadas do objeto * Matrix do mundo

O próximo elemento é a visualização da câmera, que indica o ponto do qual você está vendo o objeto. Este ponto pode alterar no espaço 3D, sem alterar as coordenadas do objeto no espaço de objeto, bem como o espaço do mundo real. Para calcular as coordenadas do objeto em relação ao modo de exibição da câmera, você deve multiplicar a matriz de modo de exibição com a matriz de mundo do objeto:

Coordenadas de modo de exibição de objeto = coordenadas do mundo * Matrix de modo de exibição

Por último, o modo de exibição do objeto precisa ser renderizado na tela; Isso é onde você precisa calcular o modo de exibição de perspectiva criado devido a distância. Até agora, o meu objeto está na projeção paralela (os lados são paralelos), mas preciso exibir o objeto na projeção em perspectiva (lados mesclagem em um ponto de fuga), para que eu multiplicar o produto da matriz de modo de exibição e a matriz de mundo com a matriz de projeção do objeto:

Objeto coordenadas finais = coordenadas do mundo * Matrix de modo de exibição * matriz de projeção

Esta é a posição final do 3D do objeto na tela, que também é chamada de WorldViewProjection.

A matriz

Uma struct de Matrix, disponível em Microsoft.Xna.Framework, está incluída no Silverlight 5.  Ele tem uma matriz de homogênea 4x4 com 16 números de ponto flutuante como campos e métodos para gerar uma matriz de transformação (consulte a Figura 10).

A Silverlight 5 Matrix Struct
Figura pré-requisitos Struct de matriz de Silverlight 5

As três primeiras fileiras por colunas (M11 - M33) são usadas para transformações de rotação e escala, e a quarta linha (M41-M43) é usada para conversão (consulte a Figura 11).

Figura 11 A matriz de 4 x 4.

M11 M12 M13 M14
M21 M22 M23 M24
M31 M32 M33 M34
M41 M42 M43 M44

Para entender a matriz melhor, vamos ver como ele é usado com relação a uma transformação. Existem cinco tipos diferentes de matrizes: estrutura 4x4 matrix, matriz de identidade, matriz de tradução, matriz de escala e matriz de rotação.

Matriz de identidade (consulte a Figura 12) é a matriz de unidade de tamanho 4, e isso torna-se a posição original do objeto 3D no espaço do mundo. Se você multiplicar qualquer matriz com a matriz de identidade, você obterá a matriz original sem qualquer alteração. Estrutura de matriz fornece uma propriedade simple que retorna um Matrix.Identity.

Figura 12 A matriz de identidade

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

Para dimensionar o objeto da matriz, fornecem um método chamado Matrix.CreateScale. A matriz de escala é usada para transformação de escala em um objeto 3D, portanto, quando você multiplica um objeto com a matriz de escala (consulte a Figura 13), a matriz resultante é redimensionado de acordo.

Figura 13 A matriz de escala

SX 0 0 0
0 SY 0 0
0 0 SZ 0
0 0 0 1

Objeto da matriz também fornece um método de Matrix.CreateTranslate para mover o objeto no espaço do mundo. Quando multiplicada pela matriz de tradução (consulte a Figura 14), o objeto converte no espaço do mundo.

Figura 14 A matriz de tradução

1 0 0 0
0 1 0 0
0 0 1 0
TX Ty TZ 1

Para rotação, existem vários métodos. O método Matrix.CreateFromYawPitchRoll é usado para girar cada um dos eixos para um valor de número flutuante. Os métodos Matrix.CreateRotationX, Matrix.CreateRotationY e Matrix.CreateRotationZ são para girar o objeto ao longo de x, y e z de eixos. A matriz com relação a teta de ângulo de rotação envolve elementos de M11-M33, conforme mostrado na a Figura 15.

Matriz de rotação de Figura 15 ao longo de X, Y e z eixos

1 0 0 0
0 CoS θ Sen θ 0
0 -Sen θ CoS θ 0
0 0 0 1
Rotação X      
CoS θ 0 Sen θ 0
0 1 0 0
-Sen θ 0 CoS θ 0
0 0 0 1
Rotação Y      
CoS θ Sen θ 0 0
-Sen θ CoS θ 0 0
0 0 1 0
0 0 0 1
Rotação Z      

O Pipeline 3D de aprendizagem para XNA do Silverlight

5 Do Silverlight com bibliotecas XNA fornece um processo passo a passo para a criação de objetos 3D com coordenadas de vértice para renderização na tela. Isso pode ser dividido em cinco etapas principais (consulte a Figura 16) envolvendo os componentes mostrados aqui:

  1. Buffer de vértices
  2. Coordenadas de WorldViewProjection
  3. O sombreamento: vértice, pixel e textura
  4. Processamento de gráfico: rasterizar, recortar e analisar
  5. Saída final: o buffer de quadro

Creating 3D Objects with the Silverlight 5 XNA Libraries
Figura 16 criação de objetos 3D com as bibliotecas XNA Silverlight 5

Abordarei cada etapa e seus componentes.

Buffer de vértices na criação de um buffer de vértices da coleção vértice, a primeira etapa é criar o esqueleto do objeto 3D usando um conjunto de vértices. Cada vértice contém pelo menos x, y e z coordenadas, mas normalmente também tem outras propriedades, como a cor e a textura. Essa coleção de vértices é usada para criar um buffer de vértice, o que vai para a próxima etapa no processo.

Coordenadas de WorldViewProjection as coordenadas finais são calculadas multiplicando o vértices com as matrizes do mundo, modo de exibição e de projeção. Aqui, a relação do objeto com o espaço do mundo, o modo de exibição e a projeção é calculada e aplicadas. Verifique se as duas últimas seções para obter mais detalhes. Uma vez que as coordenadas finais, e em seguida, ocorre o processo real de sombreamento.

Sombreamento sombreamento de vértice, o sombreamento de pixel e sombreamento de textura são usados. Nesta etapa, coloração do primeira vértice é feita e, em seguida, o sombreamento de pixels por acontece. Sombreamento de textura também é aplicado nesta etapa. Essas coordenadas sombreadas finais são usadas para criar um buffer de quadro.

Rasterizar, recortar e analisar durante a rasterização, uma imagem é convertida em pixels e, em seguida, o clipe e aplicar suspensões são usados para remover o esqueleto de 
object, juntamente com as camadas ocultas e invisíveis. Por fim, isso é processado na tela.

Frame Buffer depois que a imagem é rasterizada, cortadas e culled, um buffer de quadros é gerado, que é enviada para a tela para exibição.

Criando um cubo usando primitivos

Com o conhecimento de matrizes, mundo, modo de exibição, projeção e o pipeline de 3D no Silverlight 5 com bibliotecas XNA, vamos criar um cubo 3D e ver como tudo se encaixam.

A principal vantagem dessa abordagem é a aceleração de GPU, que permite a aceleração de hardware, liberando a CPU de processamento do objeto 3D. Isso é ativado, definindo o parâmetro EnableGPUAcceleration na marca do objeto como true no código HTML usado para configurar o plug-in do Silverlight, conforme mostrado aqui:

<object data="data:application/x-silverlight-2,"
  type="application/x-silverlight-2" width="100%" height="100%">
  <param name="EnableGPUAcceleration" value="true" />
  <param name="source" value="ClientBin/Cube3d.xap"/>
  <param name="minRuntimeVersion" value="5.0.60211.0" />
</object>

No XAML, vou acrescentar um objeto de DrawingSurface dentro da grade, que é usado para processar 3D no Silverlight com a Ajuda do método DrawPrimitives do objeto GraphicsDevice (consulte a Figura 17):

<DrawingSurface Loaded="OnLoad" SizeChanged="OnSizeChanged" Draw="OnDraw"/>

The DrawPrimitives Method of the GraphicsDevice Class
Figura 17 O método DrawPrimitives da classe GraphicsDevice

Usarei três métodos na classe DrawingSurface para criar e processar o cubo. O método OnLoad é usado para criar o cubo e inicializando todos os sombreadores e a matriz de modo de exibição, que não se altera neste aplicativo. Observe que o objeto 3D é centralizado em (0,0,0) usando a 75 por cento do espaço de objeto com coordenadas que vão desde (.75,.75,.75) (-.75,-.75,-.75). Aqui, criarei um buffer de vértices para manter a coleção de vértice e inicializar o shaderStream, pixelStream e imageStream fluxos, que serão usados posteriormente na etapa sombreamento. Eu também irá inicializar a matriz de modo de exibição, que é o ângulo no qual a câmera está olhando para o objeto usando cameraPosition e cameraTarget com o parâmetro Vector3.Up (que significa que a câmera está procurando para cima). Esse código é mostrado na Figura 18.

Figura 18 Configurando shaderStream, pixelStream e imageStream

VertexBuffer vertexBuffer;
VertexShader vertexShader;
PixelShader pixelShader;
Texture2D texture;
 
private void OnLoad(object sender, RoutedEventArgs e)
{
  vertexBuffer = CreateCube();
  Stream shaderStream = Application.GetResourceStream(new
    Uri(@"Cube3d;component/shader/shader.vs", UriKind.Relative)).Stream;
  vertexShader = VertexShader.FromStream(resourceDevice, shaderStream);
 
  Stream pixelStream = Application.GetResourceStream(new  
    Uri(@"Cube3d;component/shader/shader.ps", UriKind.Relative)).Stream;
  pixelShader = PixelShader.FromStream(resourceDevice, pixelStream);
           
  Stream imageStream = Application.GetResourceStream(new
    Uri(@"Cube3d;component/scene.jpg",
    UriKind.Relative)).Stream;
  var image = new BitmapImage();
  image.SetSource(imageStream);
  texture = new Texture2D(resourceDevice, image.PixelWidth,
    image.PixelHeight, false, SurfaceFormat.Color);
  image.CopyTo(texture);
        
  Vector3 cameraPosition = new Vector3(0, 0, 5.0f);
  Vector3 cameraTarget = Vector3.Zero; 
  view = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
}

A próxima etapa é criar o buffer de vértices para o cubo 3D. Vou criar um método CreateCube conforme mostrado na a Figura 19 , que retorna um VertexBuffer. Criarei dois retângulos no espaço 3D, com ABCD compõem a parte frontal do cubo e EFGH parte de trás do cubo. Posso usar a estrutura de VertexPositionColor para criar uma coleção de vértices e as cores associadas a cada um deles.

Figura 19, criando o VertexBuffer no método CreateCube

VertexBuffer CreateCube()
{
  var vertexCollection = new VertexPositionColor[36];
 
  // Front coordinates
  Vector3 cubeA = new Vector3(-0.75f, 0.75f, 0.75f);
  Vector3 cubeB = new Vector3(0.75f, 0.75f, 0.75f);
  Vector3 cubeC = new Vector3(-0.75f, -0.75f, 0.75f);
  Vector3 cubeD = new Vector3(0.75f, -0.75f, 0.75f);
  // Back coordinates
  Vector3 cubeE = new Vector3(-0.75f, 0.75f, -0.75f);
  Vector3 cubeF = new Vector3(0.75f, 0.75f, -0.75f);
  Vector3 cubeG = new Vector3(-0.75f, -0.75f, -0.75f);
  Vector3 cubeH = new Vector3(0.75f, -0.75f, -0.75f);
 
  // Colors
  Color cRed = Color.FromNonPremultiplied(255, 0, 0, 156);
  Color cGreen = Color.FromNonPremultiplied(0, 255, 0, 156);
  Color cBlue = Color.FromNonPremultiplied(0, 0, 255, 156);
  Color cYellow = Color.FromNonPremultiplied(255, 255, 0, 156);
  Color cBlack = Color.FromNonPremultiplied(0, 0, 0, 156);
  Color cWhite = Color.FromNonPremultiplied(255, 255, 255, 156);
 
  // Front
  vertexCollection[0] = new VertexPositionColor(cubeA, cGreen);
  vertexCollection[1] = new VertexPositionColor(cubeB, cGreen);
  vertexCollection[2] = new VertexPositionColor(cubeC, cGreen);
  vertexCollection[3] = new VertexPositionColor(cubeB, cBlue);
  vertexCollection[4] = new VertexPositionColor(cubeD, cBlue);
  vertexCollection[5] = new VertexPositionColor(cubeC, cBlue);
  // Back 
  vertexCollection[6] = new VertexPositionColor(cubeG, cBlue);
  vertexCollection[7] = new VertexPositionColor(cubeF, cBlue);
  vertexCollection[8] = new VertexPositionColor(cubeE, cBlue);
  vertexCollection[9] = new VertexPositionColor(cubeH, cGreen);
  vertexCollection[10] = new VertexPositionColor(cubeF, cGreen);
  vertexCollection[11] = new VertexPositionColor(cubeG, cGreen);
  // Top
  vertexCollection[12] = new VertexPositionColor(cubeE, cRed);
  vertexCollection[13] = new VertexPositionColor(cubeF, cRed);
  vertexCollection[14] = new VertexPositionColor(cubeA, cRed);
  vertexCollection[15] = new VertexPositionColor(cubeF, cYellow);
  vertexCollection[16] = new VertexPositionColor(cubeB, cYellow);
  vertexCollection[17] = new VertexPositionColor(cubeA, cYellow);
  // Bottom 
  vertexCollection[18] = new VertexPositionColor(cubeH, cRed);
  vertexCollection[19] = new VertexPositionColor(cubeG, cRed);
  vertexCollection[20] = new VertexPositionColor(cubeC, cRed);
  vertexCollection[21] = new VertexPositionColor(cubeD, cYellow);
  vertexCollection[22] = new VertexPositionColor(cubeH, cYellow);
  vertexCollection[23] = new VertexPositionColor(cubeC, cYellow);
  // Left
  vertexCollection[24] = new VertexPositionColor(cubeC, cBlack);
  vertexCollection[25] = new VertexPositionColor(cubeG, cBlack);
  vertexCollection[26] = new VertexPositionColor(cubeA, cBlack);
  vertexCollection[27] = new VertexPositionColor(cubeA, cWhite);
  vertexCollection[28] = new VertexPositionColor(cubeG, cWhite);
  vertexCollection[29] = new VertexPositionColor(cubeE, cWhite);
  // Right 
  vertexCollection[30] = new VertexPositionColor(cubeH, cWhite);
  vertexCollection[31] = new VertexPositionColor(cubeD, cWhite);
  vertexCollection[32] = new VertexPositionColor(cubeB, cWhite);
  vertexCollection[33] = new VertexPositionColor(cubeH, cBlack);
  vertexCollection[34] = new VertexPositionColor(cubeB, cBlack);
  vertexCollection[35] = new VertexPositionColor(cubeF, cBlack);
 
  var vb = new VertexBuffer(resourceDevice,
    VertexPositionColor.VertexDeclaration,
    vertexCollection.Length, BufferUsage.WriteOnly);
  vb.SetData(0, vertexCollection, 0, vertexCollection.Length, 0);
  return vb;
}

O método OnSizeChanged de superfície de desenho é usado para atualizar a projeção e a taxa de proporção da tela com base na dimensão de superfície:

private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
  DrawingSurface surface = sender as DrawingSurface;
  float sceneAspectRatio = (float)surface.ActualWidth / (float)surface.ActualHeight
  projection = Matrix.CreatePerspectiveFieldOfView
               (MathHelper.PiOver4, sceneAspectRatio, 1.0f, 100.0f);
}

O último método é OnDraw, que permite a transformação dinâmica no cubo 3D. Isso é onde posso aplicar Matrix.CreateScale para redimensionar o cubo, Matrix.CreateFromYawPitchRoll para girá-lo e Matrix.CreateTranslate para movê-lo. Aqui, a matriz de worldViewProjection é calculada e enviado para o método vertexShader para sombreamento os vértices, que por sua vez, transfere para o pixelShader para sombrear "paredes" do cubo. Isso por sua vez também pode ser enviado para textureShader, que pode ser baseado em uma imagem. Finalmente, o objeto GraphicDevice chama um método DrawPrimitives para processar os quadros de saída, conforme mostrado na a Figura 20.

Figura 20 chamando o método DrawPrimitives para processar os quadros de saída

void OnDraw(object sender, DrawEventArgs args)
{
  Matrix position = Matrix.Identity;
  Matrix scale = Matrix.CreateScale(1.0f);
  float xf = 0.0f; float yf = 0.0f; float zf = 0.0f;
 
  if (cubeXAxis) xf = MathHelper.PiOver4 * (float)args.TotalTime.TotalSeconds;
  if (cubeYAxis) yf = MathHelper.PiOver4 * (float)args.TotalTime.TotalSeconds;
  if (cubeZAxis) zf = MathHelper.PiOver4 * (float)args.TotalTime.TotalSeconds;
 
  Matrix rotation = Matrix.CreateFromYawPitchRoll(xf, yf, zf);
  Matrix world;
  if (translateZ != 0)
    world = rotation * Matrix.CreateTranslation(0, 0, (float)translateZ);
  else
    world = scale * rotation * position;
           
  // Calculate the final coordinates to pass to the shader.
Matrix worldViewProjection = world * view * projection;
  args.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,
    new Microsoft.Xna.Framework.Color(0, 0, 0, 0), 10.0f, 0);
  
  // Set up vertex pipeline.
args.GraphicsDevice.SetVertexBuffer(vertexBuffer);
  args.GraphicsDevice.SetVertexShader(vertexShader);
  args.GraphicsDevice.SetVertexShaderConstantFloat4(0, ref worldViewProjection);
 
  // Set up pixel pipeline.
args.GraphicsDevice.SetPixelShader(pixelShader);
  args.GraphicsDevice.Textures[0] = texture;
  args.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
  args.InvalidateSurface();
}

Isso dinamicamente processa o cubo 3D final na superfície de desenho (consulte Figura 21).

The Final 3D Cube on the Drawing Surface
Figura 21 Final cubo 3D na superfície de desenho

Essa abordagem usa aceleração de GPU e não processa os lados do objeto 3D que estão ocultos ou traseira, ao contrário da minha primeira abordagem. Isso também usa o buffer de quadros na memória para processar a saída final, como a segunda abordagem, mas com controle total e a flexibilidade da programação em relação ao objeto, assim como acontece com o método OnDraw.

Agora você já aprendeu três maneiras diferentes de criar objetos 3D no Silverlight, cada um com seus pontos fortes e fracos. Esses devem ser adições úteis à sua caixa de ferramentas ao explorar o mundo de 3D no Silverlight.

Rajesh Lal é apaixonado pelo Silverlight tecnologias e da Web. Escreveu vários livros no gadget do Windows, o widget de Web e tecnologias móveis da Web, incluindo um próximo livro, "Fun com o Silverlight 4" (CreateSpace, 2011). Para obter mais informações, entre em contato com mconnectrajesh@hotmail.com ou visite silverlightfun.com.

Graças aos seguintes especialistas técnicos para revisão deste artigo:Marc Clifton, Rick Kingslan e Josh Smith