Using a Render Pipeline to Render Primitives
使用渲染管道去渲染一个几何体
Render a simple 2D triangle. 渲染一个简单的三角形
需要下载示例代码的请搜索上传文件。
文件名:“Using a Render Pipeline to Render Primitives .zip”
Overview
In Using Metal to Draw a View’s Contents用metal 绘制视图内容一文中, you learned how to set up an MTKView object and to change the view’s contents using a render pass.
That sample simply erased the view’s contents to a background color. 那个例子简单的擦除掉视图内容并填充了一个背景色。
This sample shows you how to configure a render pipeline and use it as part of the render pass to draw a simple 2D colored triangle into the view.
本立展示如何定义一个渲染管道(管道就是渲染流程的意思,把数据送到一个入口,然后通过已经定义好的渲染管道,加工成图形显示数据后在传出。记住,渲染管道就是渲染流程即可,为了和英文统一,以后统一采用渲染管道),然后用选它来绘制一个简单的2D彩色三角形。
The sample supplies a position and color for each vertex顶点, and the render pipeline uses that data to render the triangle, interpolating内插 color values between the colors specified for the triangle’s vertices.

The Xcode project contains schemes for running the sample on macOS, iOS, and tvOS.
Understand the Metal Render Pipeline 了解Metal渲染管道
注:关于Metal中RenderPipeline的相关术语,并没有严格的规定,希望大家在学习中尽可能使用英文术语,但要明白其中的含义。
A render pipeline processes加工处理 drawing commands and writes data into a render pass’s targets.
A render pipeline has many stages阶段, some programmed using shaders and others with fixed or configurable behavior.这些阶段一些用“着色器”编程,另外一些是不可修改的或行为定义。
This sample focuses on the three main stages of the pipeline: the vertex stage, the rasterization stage, and the fragment stage.
The vertex stage and fragment stage are programmable, so you write functions for them in Metal Shading Language (MSL).
The rasterization stage has fixed固定的 behavior.
Figure 1 Main stages of the Metal graphics render pipeline图形渲染管道的主要阶段(步骤)

Rendering starts with a drawing command, which includes a vertex count and what kind of primitive to render. For example, here’s the drawing command from this sample:
// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
The vertex stage provides data for each vertex. When enough vertices have been processed, the render pipeline rasterizes the primitive, determining判断 which pixels像素点 in the render targets lie within the boundaries of the primitive. The fragment stage determines the values to write into the render targets for those pixels.
In the rest of this sample, you will see how to write the vertex and fragment functions, how to create the render pipeline state object, and finally, how to encode a draw command that uses this pipeline.
Decide How Data is Processed by Your Custom Render Pipeline
A vertex function generates data for a single vertex and a fragment function generates data for a single fragment, but you decide how they work.
You configure the stages of the pipeline with a goal in mind, meaning that you know what you want the pipeline to generate and how it generates those results.
Decide what data to pass into your render pipeline and what data is passed to later stages of the pipeline. There are typically three places where you do this:
- The inputs to the pipeline, which are provided by your app and passed to the vertex stage.
- The outputs of the vertex stage, which is passed to the rasterization stage.
- The inputs to the fragment stage, which are provided by your app or generated by the rasterization stage.
In this sample, the input data for the pipeline is the position of a vertex and its color. To demonstrate演示 the kind of transformation you typically perform in a vertex function, input coordinates are defined in a custom coordinate space, measured in pixels from the center of the view. These coordinates坐标 need to be translated into Metal’s coordinate system.
Declare an AAPLVertex structure, using SIMD vector types to hold the position and color data.
To share a single definition for how the structure is laid out in memory, declare the structure in a common header and import it in both the Metal shader and the app. 为了在内存中共享单一定义的数据结构,在一个通用头文件中定义结构,然后在着色器和程序中同时导入结构。
typedef struct
{
vector_float2 position;
vector_float4 color;
} AAPLVertex;
SIMD types are commonplace常见的 in Metal Shading Language, and you should also use them in your app using the simd library.
SIMD types contain multiple channels of a particular data type, so declaring the position as a vector_float2 means it contains two 32-bit float values (which will hold the x and y coordinates.)
Colors are stored using a vector_float4, so they have four channels – red, green, blue, and alpha.
In the app, the input data is specified using a constant array:
static const AAPLVertex triangleVertices[] =
{
// 2D positions, RGBA colors
{ { 250, -250 }, { 1, 0, 0, 1 } },
{ { -250, -250 }, { 0, 1, 0, 1 } },
{ { 0, 250 }, { 0, 0, 1, 1 } },
};
The vertex stage generates data for a vertex, so it needs to provide a color and a transformed position.
Declare a RasterizerData structure containing a position and a color value, again using SIMD types. (在Metal着色器中)声明一个RasterizerData结构包含位置和颜色,继续使用SIMD
struct RasterizerData
{
// The [[position]] attribute of this member indicates that this value
// is the clip space position of the vertex when this structure is
// returned from the vertex function.
//当顶点着色器返回这个结构时,这个成员的[[position]]属性表明了表明了这个值是顶点切片空间位置。
float4 position [[position]];
// Since this member does not have a special attribute, the rasterizer
// interpolates its value with the values of the other triangle vertices
// and then passes the interpolated value to the fragment shader for each
// fragment in the triangle.
//因为这个成员没有一个指定的属性,栅格化器用其他三角形顶点的值插值它的值,然后将插值的值传递给三角形中每个片元的片元着色器。
float4 color;
};
The output position (described in detail below) must be defined as a vector_float4. The color is declared as it was in the input data structure.
You need to tell Metal which field in the rasterization data provides position data, because Metal doesn’t enforce any particular naming convention for fields in your struct.
Annotate the position field with the [[position]] attribute qualifier to declare that this field holds the output position.
The fragment function simply passes the rasterization stage’s data to later stages so it doesn’t need any additional arguments.
Declare the Vertex Function声明顶点函数
Declare the vertex function, including its input arguments and the data it outputs.
Much like compute functions were declared using the kernel keyword, you declare a vertex function using the vertex keyword.
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])
The first argument, vertexID, uses the [[vertex_id]] attribute qualifier, which is another Metal keyword.
When you execute a render command, the GPU calls your vertex function multiple times, generating a unique value for each vertex.
The second argument, vertices, is an array that contains the vertex data, using the AAPLVertex struct previously defined.
To transform the position into Metal’s coordinates, the function needs the size of the viewport (in pixels) that the triangle is being drawn into, so this is stored in the viewportSizePointer argument.为了转化位置到Metal坐标,顶点函数需要那个要绘制三角形视口的尺寸(像素),所以视口尺寸参数时第三个参数。
The second and third arguments have the [[buffer(n)]] attribute qualifier修饰符.
By default, Metal assigns slots in the argument table for each parameter automatically.
When you add the [[buffer(n)]] qualifier to a buffer argument, you tell Metal explicitly明确地 which slot to use.
Declaring slots explicitly can make it easier to revise修订 your shaders without also needing to change your app code.
Declare the constants for the two indicies索引 in the shared header file.
The function’s output is a RasterizerData struct.
Write the Vertex Function 编写顶点函数
Your vertex function must generate both fields of the output struct.
Use the vertexID argument to index into the vertices array and read the input data for the vertex.
Also, retrieve检索 the viewport dimensions.
float2 pixelSpacePosition = vertices[vertexID].position.xy;
// Get the viewport size and cast to float.
vector_float2 viewportSize = vector_float2(*viewportSizePointer);
Vertex functions must provide position data in clip-space coordinates切片空间坐标, which are 3D points specified using a four-dimensional homogenous vector (x,y,z,w)四维齐次向量. The rasterization stage takes the output position and divides the x,y, and z coordinates by w to generate a 3D point in normalized device coordinates. Normalized标准化 device coordinates are independent独立 of viewport size.
Figure 2 Normalized device coordinate system

Normalized device coordinates use a left-handed coordinate system and map to positions in the viewport.
Primitives are clipped to a box in this coordinate system and then rasterized.
The lower-left corner of the clipping box is at an (x,y) coordinate of (-1.0,-1.0) and the upper-right corner is at (1.0,1.0).
Positive-z values point away from the camera (into the screen.)
The visible portion部分 of the z coordinate is between 0.0 (the near clipping plane) and 1.0 (the far clipping plane).
Transform the input coordinate system to the normalized device coordinate system.转化一个输入坐标系到 标准设备坐标系

Because this is a 2D application and does not need homogenous齐次 coordinates, first write a default value to the output coordinate, with the the w value is set to 1.0 and the other coordinates set to 0.0. 因为这个2D程序并不需要齐次坐标系,所以首先直接先给输出坐标一个缺省值(w设为1.0,其他设为0.0)。
This means that the coordinates are already in the normalized device coordinate space and the vertex function should generate (x,y) coordinates in that coordinate space.
Divide the input position by half the viewport size to generate normalized device coordinates.把坐标除以视口一般进行标准化坐标。
Since this calculation is performed using SIMD types, both channels can be divided at the same time using a single line of code.//由于此计算是使用SIMD类型执行的,因此可以使用一行代码同时计算(除)两个通道(x,y)。
Perform the divide and put the results in the x and y channels of the output position.
out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
out.position.xy = pixelSpacePosition / (viewportSize / 2.0);
Finally, copy the color value into the out.color return value.
out.color = vertices[vertexID].color;
Write a Fragment Function 编写片元函数
A fragment is a possible change to the render targets. The rasterizer determines which pixels of the render target are covered by the primitive.
Only fragments whose pixel centers are inside the triangle are rendered.
Figure 3 Fragments generated by the rasterization stage 片元化阶段生产片元

A fragment function processes incoming information from the rasterizer for a single position and calculates output values for each of the render targets. These fragment values are processed by later stages in the pipeline, eventually最终 being written to the render targets.
- Note: The reason a fragment is called a possible change is because the pipeline stages after the fragment stage can be configured to reject some fragments or change what gets written to the render targets. In this sample, all values calculated by the fragment stage are written as-is照原样 to the render target. 注:片元化的可更改指的是之后的流程可以被配置为拒绝某些片元或更改写入目标的内容。
The fragment shader in this sample receives the same parameters that were declared in the vertex shader’s output. Declare the fragment function using the fragment keyword. It takes a single argument, the same RasterizerData structure that was provided by the vertex stage. Add the [[stage_in]] attribute qualifier to indicate表明 that this argument is generated by the rasterizer.
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
If your fragment function writes to multiple render targets, it must declare a struct with fields for each render target.如果你的片元函数需要写入多个渲染目标,就必须声明一个多字段的结构,每个字段针对每个渲染目标。
Because this sample only has a single render target, you specify a floating-point vector directly as the function’s output. This output is the color to be written to the render target.
The rasterization stage calculates values for each fragment’s arguments and calls the fragment function with them.
The rasterization stage calculates its color argument as a blend混合 of the colors at the triangle’s vertices.
The closer a fragment is to a vertex, the more that vertex contributes to the final color.
Figure 4 Interpolated fragment colors 内插片元颜色

Return the interpolated内插 color as the function’s output.
return in.color; //这里的in可能是固定用法,知道的请告诉我。
Create a Render Pipeline State Object 建立一个渲染管道状态对象
Now that the functions are complete, you can create a render pipeline that uses them.
First, get the default library and obtain a [MTLFunction][MTLFunction] object for each function.现在,函数都定义好了,你能建立一个渲染管道来使用它们。首先建立缺省库。
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
Next, create a [MTLRenderPipelineState][MTLRenderPipelineState] object.
Render pipelines have more stages to configure, so you use a [MTLRenderPipelineDescriptor][MTLRenderPipelineDescriptor] to configure the pipeline.
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
In addition to specifying the vertex and fragment functions, you also declare the pixel format of all render targets that the pipeline will draw into. 在指定顶点函数和片元函数后,进一步声明了pixel format像素格式给渲染管道要绘制的所有渲染目标。
A pixel format ([MTLPixelFormat][MTLPixelFormat]) defines the memory layout of pixel data. 一个像素格式定义了内存的像素数据布局。
For simple formats, this definition includes the number of bytes per pixel, the number of channels of data stored in a pixel, and the bit layout of those channels.对于简单的(像素)格式,这个定义包括每个像素的字节数,每个像素保存的频道数和这些频道的位布局。
Since this sample only has one render target and it is provided by the view, copy the view’s pixel format into the render pipeline descriptor.
Your render pipeline state must use a pixel format that is compatible with the one specified by the render pass.
In this sample, the render pass and the pipeline state object both use the view’s pixel format, so they are always the same.
When Metal creates the render pipeline state object, the pipeline is configured to convert the fragment function’s output into the render target’s pixel format.
If you want to target a different pixel format, you need to create a different pipeline state object.
You can reuse the same shaders in multiple pipelines targeting different pixel formats.
对于不同像素格式的多种渲染管道,你可以重用着色器。
Set a Viewport 设置一个视口
Now that you have the render pipeline state object for the pipeline, you’ll render the triangle. You do this using a render command encoder. First, set the viewport, so that Metal knows which part of the render target you want to draw into.
// Set the region of the drawable to draw into.
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, 0.0, 1.0 }];
Set the Render Pipeline State
Set the render pipeline state for the pipeline you want to use.
[renderEncoder setRenderPipelineState:_pipelineState];
Send Argument Data to the Vertex Function 发送参数到顶点函数
Often经常性的, you use buffers ([MTLBuffer][MTLBuffer]) to pass data to shaders.
However, when you need to pass only a small amount of data to the vertex function, as is the case here, copy the data directly into the command buffer.经常性的,使用MTLBuffer缓冲来发送数据到着色器。然而,当你传送少量数据给顶点函数时,就像本例中情况,(可以)直接复制数据到命令缓冲。
The sample copies data for both parameters into the command buffer.本例复制两个参数给命令缓冲:
The vertex data is copied from an array defined in the sample.顶点数据复制自一个数组
The viewport data is copied from the same variable that you used to set the viewport. 视口数据复制自你设置好的视口变量。
In this sample, the fragment function uses only the data it receives from the rasterizer, so there are no arguments to set.
[renderEncoder setVertexBytes:triangleVertices
length:sizeof(triangleVertices)
atIndex:AAPLVertexInputIndexVertices];
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:AAPLVertexInputIndexViewportSize];
Encode the Drawing Command 编码绘制命令
Specify the kind of primitive, the starting index, and the number of vertices.指定几何图形的种类,索引起始点,和顶点数量
When the triangle is rendered, the vertex function is called with values of 0, 1, and 2 for the vertexID argument. 当渲染这个三角形时,顶点函数的参数依次是vertexID的0,1,2参数。
回见“声明顶点函数”一节,
// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
As with Drawing to the Screen Using Metal, you end the encoding process and commit the command buffer.
However, you could encode more render commands using the same set of steps.
The final image is rendered as if the commands were processed in the order they were specified.
(For performance, the GPU is allowed to process commands or even parts of commands in parallel, so long as the final result appears to have been rendered in order. )
Experiment with the Color Interpolation 体验色彩内插
In this sample, color values were interpolated across the triangle.
That’s often what you want, but sometimes you want a value to be generated by one vertex and remain constant across the whole primitive.
Specify the flat attribute qualifier修饰符 on an output of the vertex function to do this.
Try this now.
Find the definition of RasterizerData in the sample project and add the [[flat]] qualifier to its color field.
float4 color [[flat]];
Run the sample again. //三角形颜色会成为单一红色。
The render pipeline uses the color value from the first vertex (called the provoking vertex) uniformly across the triangle, and it ignores the colors from the other two vertices.
You can use a mix of flat shaded and interpolated values, simply by adding or omitting删除 the flat qualifier on your vertex function’s outputs.
The [Metal Shading Language specification][ShadingLanguageSpec] defines other attribute qualifiers you can also use to modify the rasterization behavior.
加油,看到这里的同学们。

这应该就是我们要的结果吧。
这几篇文章,我都会单独上传源代码,需要调试运行的伙伴们,请直接下载。
201



被折叠的 条评论
为什么被折叠?



