SharpDX初学者教程第4部分:绘制三角形

原文 http://www.johanfalk.eu/blog/sharpdx-beginners-tutorial-part-4-drawing-a-triangle

现在我们有了一个Direct3D初始化的窗口,现在是时候绘制一些东西了,就像所有其他教程一样,我们也将开始绘制一个三角形!要渲染我们的第一个三角形,实际上我们必须添加很多部分,所以让我们开始吧。

1.顶点

要创建三角形,我们将使用顶点。顶点是3D空间中的精确点,也可以包含其他信息(我们将在后面的教程中看到)。目前,我们的顶点仅由3个值表示,即x,y和z坐标。

对于三角形,我们将需要3个顶点,每个角落一个。稍后我们将介绍有关不同坐标系的更多细节,但是现在我们的可见空间在x,y和z方向上介于-1和1之间。这就是我决定设置三角形的方法,您可以使用不同的值来查看三角形的变化:

所以我们添加的第一个代码是我们的Game类的变量,它保存了这些坐标,为此我们使用SharpDX中提供的Vector3类:

private Vector3[] vertices = new Vector3[] { new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.5f, 0.5f, 0.0f), new Vector3(0.0f, -0.5f, 0.0f) };

2.顶点缓冲区

我们刚刚创建的顶点数组存储在系统内存中,但是为了渲染我们的对象,我们需要将数据传输到视频内存。为此,我们将使用缓冲区。在DirectX中,我们有三种不同类型的缓冲区:Vertex,Index和Constant缓冲区。当我们渲染需要DirectX的数据时,缓冲区中的数据会自动从系统内存复制到视频内存。

顶点缓冲区是我们现在将使用的,这个缓冲区类型,顾名思义,保存每个顶点的数据。目前,我们的顶点只有一个位置向量,但稍后我们会向每个顶点添加更多信息。这里的第一步是向我们的类添加一个新变量,它是对缓冲区的引用:

private D3D11.Buffer triangleVertexBuffer;

然后我们在我们的类中添加一个名为InitializeTriangle的新方法,如下所示:

private void InitializeTriangle() { triangleVertexBuffer = D3D11.Buffer.Create<Vector3>(d3dDevice, D3D11.BindFlags.VertexBuffer, vertices); }

这里方法D3D.Buffer.Create <T>用于创建新缓冲区,泛型类型参数T指定缓冲区中每个元素的哪种数据类型。第一个参数是我们希望使用的Direct3D设备。第二个参数是我们想要创建的缓冲区类型,在本例中是顶点缓冲区。最后,我们提供了加载到缓冲区的初始数据,在本例中是我们的位置数组。
还要在Game类构造函数的末尾添加对此方法的调用,并释放缓冲区:

public Game()
{
   [...]
   InitializeTriangle();
} 

public void Dispose() { triangleVertexBuffer.Dispose(); [...] }

3.顶点和像素着色器

DirectX 11中图形管道包含几个可编程步骤。现在我们将重点关注Vertex Shader Stage和Pixel Shader Stage。

顶点着色器阶段负责处理顶点,这可以包括例如变换(平移,旋转,缩放等)。

像素着色器阶段处理针对每个像素运行,并接收插值的每顶点数据,以及常量变量和纹理。该着色器针对渲染图元的每个像素运行,并应返回像素的最终颜色。

首先我们添加两个类变量,我们的顶点和像素着色器:

private D3D11.VertexShader vertexShader;
private D3D11.PixelShader pixelShader;

接下来我们需要编译我们的着色器代码(我们将很快编写),我们将其置于一个新的私有方法中,顶部的using指令也是必需的:

using SharpDX.D3DCompiler;
[...]
private void InitializeShaders() { using(var vertexShaderByteCode = ShaderBytecode.CompileFromFile("vertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { vertexShader = new D3D11.VertexShader(d3dDevice, vertexShaderByteCode); } using(var pixelShaderByteCode = ShaderBytecode.CompileFromFile("pixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug)) { pixelShader = new D3D11.PixelShader(d3dDevice, pixelShaderByteCode); } }

这里我们首先指出要编译的文件,vertexShader.hlsl和pixelShader.hlsl。我们还在着色器代码“main”中指定入口点方法的名称。然后我们还设置要使用的HLSL版本,在本例中为4.0。最后,我们还将编译设置为调试模式。

现在还必须将设备上下文配置为在绘制时使用这些着色器,因此将此代码添加到InitializeShaders()方法的末尾:

private void InitializeShaders() { [...] // Set as current vertex and pixel shaders d3dDeviceContext.VertexShader.Set(vertexShader); d3dDeviceContext.PixelShader.Set(pixelShader); d3dDeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; }

这里我们还设置了原始拓扑,它指定了如何绘制顶点。在这种情况下,我们将使用“Triangle List”,我们将在后面的教程中使用其他类型,但您可以查看MSDN文档以获得不同类型的良好说明。

现在,让我们添加着色器代码:

  1. 右键单击解决方案资源管理器中的项目,然后选择添加 - >新项...
  2. 找到“文本文件”并输入“vertexShader.hlsl”作为名称。按添加。
  3. 在解决方案资源管理器中选择该文件,然后在“属性”窗口中,将“复制到输出目录”设置为“始终复制”。
  4. 重复步骤1-3,但将文件命名为“pixelShader.hlsl”。

现在打开vertexShader.hlsl并写入:

float4 main(float4 position : POSITION) : SV_POSITION
{
   return position;
}

在这里,我们创建入口点方法“main”,如前所述。从方法开始只返回从顶点缓冲区获得的相同位置。注意“:POSITION”和“:SV_POSITION”,这称为语义并指定变量的预期用途,我们将在本教程后面看到更多为什么这很重要。

现在打开pixelShader.hlsl文件并输入以下代码:

float4 main(float4 position : SV_POSITION) : SV_TARGET
{
   return float4(1.0, 0.0, 0.0, 1.0); } 

我们再次创建一个main方法,该方法的参数是顶点着色器的输出。但请记住,顶点着色器是针对每个顶点运行的,而像素着色器是针对每个像素运行的,因此这将是一个插值位置。从这个方法我们返回一个float4,它是我们的颜色,格式为红色,绿色,蓝色,alpha。因此,这将为所有像素生成红色。值得注意的是float4中的值介于0和1之间,因此float4(0,0,0,1)将给出黑色,而float4(1,1,1,1)将给出白色像素。

当然,我们也在游戏构造函数中调用InitializeShaders()方法并处理着色器,这应该在InitializeTriangle()方法之前进行:

public Game()
{
   [...]
   InitializeDeviceResources();
   InitializeShaders();
   InitializeTriangle();
}
public void Dispose() { triangleVertexBuffer.Dispose(); vertexShader.Dispose(); pixelShader.Dispose(); [...] }

4.输入布局

我们现在有一个顶点缓冲区,它有我们的顶点数据。但DirectX还想知道数据的结构以及每个顶点元素的类型,为此我们使用输入布局。这需要两步。首先,我们需要描述顶点中的每个元素,然后从中创建输入布局。

由于我们的顶点到目前为止只有一个元素,所以让我们在Game类中添加一个新的InputElements数组:

private D3D11.InputElement[] inputElements = new D3D11.InputElement[] 
{
    new D3D11.InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };

可以从着色器代码识别“POSITION”,这称为语义,用于与着色器中的输入签名匹配。第二个参数是要使用的语义槽,如果您有多个POSITION语义,则使用此参数。第三个是这个元素的数据类型,在这种情况下3个浮点数作为我们的顶点的位置是Vector3。

接下来,我们需要从编译的顶点着色器中获取输入着色器。首先创建一个新变量来保存我们的Game类的输入签名:

private ShaderSignature inputSignature;

然后在InitializeShaders()方法中,我们可以从编译的着色器字节代码中获取签名,如下所示:

using(var vertexShaderByteCode = ShaderBytecode.CompileFromFile("vertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { inputSignature = ShaderSignature.GetInputSignature(vertexShaderByteCode); [...] }

现在我们需要从InputElement数组和输入签名创建一个输入布局,所以在Game类中添加另一个变量:

private D3D11.InputLayout inputLayout;

然后通过创建一个新的InputLayout实例在InitializeShaders()方法的末尾分配它。然后我们将其设置为设备上下文中的当前输入布局。

private void InitializeShaders() { [...] inputLayout = new D3D11.InputLayout(d3dDevice, inputSignature, inputElements); d3dDeviceContext.InputAssembler.InputLayout = inputLayout; }

第一个元素是我们的Direct3D设备,然后是着色器的输入签名,最后是输入元素数组。

并且不要忘记处理输入布局和输入签名:

public void Dispose() { inputLayout.Dispose(); inputSignature.Dispose(); [...] }

5.设置视口:

在我们绘制任何东西之前,我们必须指定视口。DirectX使用称为标准化设备坐标的东西,在左上角指定为(-1,-1),在屏幕右下角指定为(1,1),因此在中间指定(0,0)。视口将这些角映射到像素坐标。

首先在Viewport的Game类中创建另一个变量:

private Viewport viewport;

在InitializeDeviceResources()方法中,使用以下代码创建一个新视口并在设备上下文中设置它:

// Set viewport
viewport = new Viewport(0, 0, Width, Height);
d3dDeviceContext.Rasterizer.SetViewport(viewport);

前两个参数是(-1,-1)的x和y位置,最后两个参数是视口的宽度和高度。因为我们想要使用完整的窗口,我们将它映射到左上角(0,0)并将其设置为窗口的整个宽度和高度。

6.绘制顶点数据

完成所有这些工作后,终于可以在屏幕上绘制三角形!这只是两个方法调用,我们在draw()方法的中间添加:

private void Draw() { d3dDeviceContext.OutputMerger.SetRenderTargets(renderTargetView); d3dDeviceContext.ClearRenderTargetView(renderTargetView, new SharpDX.Color(32, 103, 178)); d3dDeviceContext.InputAssembler.SetVertexBuffers(0, new D3D11.VertexBufferBinding(triangleVertexBuffer, Utilities.SizeOf<Vector3>(), 0)); d3dDeviceContext.Draw(vertices.Count(), 0); swapChain.Present(1, PresentFlags.None); }

第一种方法告诉设备上下文使用保存三角形顶点数据的顶点缓冲区,第二个参数指定每个顶点数据的大小(以字节为单位)。要获得这个大小,我们使用SharpDX中提供的一个很好的帮助方法。

设备上下文中的Draw()方法从顶点缓冲区中绘制vertices.Count()许多顶点。第二个参数指定顶点缓冲区中的偏移量,通过将此设置为1,例如,将跳过第一个顶点。

现在,当您运行该程序时,您应该得到以下结果:

 

 

像往常一样,代码可以在GitHub上找到:https//github.com/mrjfalk/SharpDXTutorials/tree/master/BeginnersTutorial-Part4

 public class Game : IDisposable
    {
        private RenderForm renderForm;

        private const int Width = 1280;
        private const int Height = 720;

        private D3D11.Device d3dDevice;
        private D3D11.DeviceContext d3dDeviceContext;
        private SwapChain swapChain;
        private D3D11.RenderTargetView renderTargetView;
        private Viewport viewport;

        // Shaders
        private D3D11.VertexShader vertexShader;
        private D3D11.PixelShader pixelShader;
        private ShaderSignature inputSignature;
        private D3D11.InputLayout inputLayout;

        private D3D11.InputElement[] inputElements = new D3D11.InputElement[]
        {
            new D3D11.InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
        };

        // Triangle vertices
        private Vector3[] vertices = new Vector3[] { new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.5f, 0.5f, 0.0f), new Vector3(0.0f, -0.5f, 0.0f) };
        private D3D11.Buffer triangleVertexBuffer;

        /// <summary>
        /// Create and initialize a new game.
        /// </summary>
        public Game()
        {
            // Set window properties
            renderForm = new RenderForm("My first SharpDX game");
            renderForm.ClientSize = new Size(Width, Height);
            renderForm.AllowUserResizing = false;

            InitializeDeviceResources();
            InitializeShaders();
            InitializeTriangle();
        }

        /// <summary>
        /// Start the game.
        /// </summary>
        public void Run()
        {
            // Start the render loop
            RenderLoop.Run(renderForm, RenderCallback);
        }

        private void RenderCallback()
        {
            Draw();
        }

        private void InitializeDeviceResources()
        {
            ModeDescription backBufferDesc = new ModeDescription(Width, Height, new Rational(60, 1), Format.R8G8B8A8_UNorm);

            // Descriptor for the swap chain
            SwapChainDescription swapChainDesc = new SwapChainDescription()
            {
                ModeDescription = backBufferDesc,
                SampleDescription = new SampleDescription(1, 0),
                Usage = Usage.RenderTargetOutput,
                BufferCount = 1,
                OutputHandle = renderForm.Handle,
                IsWindowed = true
            };

            // Create device and swap chain
            D3D11.Device.CreateWithSwapChain(DriverType.Hardware, D3D11.DeviceCreationFlags.None, swapChainDesc, out d3dDevice, out swapChain);
            d3dDeviceContext = d3dDevice.ImmediateContext;

            viewport = new Viewport(0, 0, Width, Height);
            d3dDeviceContext.Rasterizer.SetViewport(viewport);

            // Create render target view for back buffer
            using (D3D11.Texture2D backBuffer = swapChain.GetBackBuffer<D3D11.Texture2D>(0))
            {
                renderTargetView = new D3D11.RenderTargetView(d3dDevice, backBuffer);
            }
        }

        private void InitializeShaders()
        {
            // Compile the vertex shader code
            using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("vertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug))
            {
                // Read input signature from shader code
                inputSignature = ShaderSignature.GetInputSignature(vertexShaderByteCode);

                vertexShader = new D3D11.VertexShader(d3dDevice, vertexShaderByteCode);
            }

            // Compile the pixel shader code
            using (var pixelShaderByteCode = ShaderBytecode.CompileFromFile("pixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug))
            {
                pixelShader = new D3D11.PixelShader(d3dDevice, pixelShaderByteCode);
            }

            // Set as current vertex and pixel shaders
            d3dDeviceContext.VertexShader.Set(vertexShader);
            d3dDeviceContext.PixelShader.Set(pixelShader);

            d3dDeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;

            // Create the input layout from the input signature and the input elements
            inputLayout = new D3D11.InputLayout(d3dDevice, inputSignature, inputElements);

            // Set input layout to use
            d3dDeviceContext.InputAssembler.InputLayout = inputLayout;
        }

        private void InitializeTriangle()
        {
            // Create a vertex buffer, and use our array with vertices as data
            triangleVertexBuffer = D3D11.Buffer.Create<Vector3>(d3dDevice, D3D11.BindFlags.VertexBuffer, vertices);
        }

        /// <summary>
        /// Draw the game.
        /// </summary>
        private void Draw()
        {
            // Set back buffer as current render target view
            d3dDeviceContext.OutputMerger.SetRenderTargets(renderTargetView);

            // Clear the screen
            d3dDeviceContext.ClearRenderTargetView(renderTargetView, new SharpDX.Color(32, 103, 178));

            // Set vertex buffer
            d3dDeviceContext.InputAssembler.SetVertexBuffers(0, new D3D11.VertexBufferBinding(triangleVertexBuffer, Utilities.SizeOf<Vector3>(), 0));

            // Draw the triangle
            d3dDeviceContext.Draw(vertices.Count(), 0);

            // Swap front and back buffer
            swapChain.Present(1, PresentFlags.None);
        }

        public void Dispose()
        {
            inputLayout.Dispose();
            inputSignature.Dispose();
            triangleVertexBuffer.Dispose();
            vertexShader.Dispose();
            pixelShader.Dispose();
            renderTargetView.Dispose();
            swapChain.Dispose();
            d3dDevice.Dispose();
            d3dDeviceContext.Dispose();
            renderForm.Dispose();
        }
    }

 

posted on 2019-05-03 01:32 NET未来之路 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/10804116.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值