Real-Time Rendering——5.3.2 Implementation Example 实现示例

We will now present an example shading model implementation. As mentioned earlier,the shading model we are implementing is similar to the extended Gooch model from Equation 5.1,but modified to work with multiple light sources. It is described by

 

我们现在将给出一个着色模型实现的例子。如前所述,我们正在实施的阴影模型类似于方程5.1的扩展Gooch 模型,但经过修改,可用于多个光源。它被描述为

with the following intermediate calculations: 

通过以下中间计算:

This formulation fits the multi-light structure in Equation 5.6, repeated here for convenience: 

该公式符合等式5.6中的多光结构,为方便起见,此处重复:

The lit and unlit terms in this case are 

在这种情况下,亮和不亮的术语是

with the cool color’s unlit contribution adjusted to make results look more like the original equation. 

冷色的无光贡献进行了调整,使结果看起来更像 原始方程。

In most typical rendering applications, varying values for material properties such as would be stored in vertex data or, more commonly, in textures (Chapter 6).However, to keep this example implementation simple, we will assume that is constant across the model. 

在大多数典型的渲染应用程序中,材质属性的变化值,比如,会存储在顶点数据中,或者更常见的是,存储在纹理中(第6章)。然而,为了保持这个示例实现简单,我们将假设在整个模型中是恒定的。

 This implementation will use the shader’s dynamic branching capabilities to loop over all light sources. While this straightforward approach can work well for reasonably simple scenes, it does not scale well to large and geometrically complex scenes with many light sources. Rendering techniques to efficiently handle large light counts will be covered in Chapter 20. Also, in the interest of simplicity, we will only support one type of light source: point lights. Although the implementation is quite simple, it follows the best practices covered earlier.

这个实现将使用着色器的动态分支功能来循环所有光源。虽然这种简单的方法对于相当简单的场景可以很好地工作,但是对于具有许多光源的大型和几何复杂的场景,它不能很好地扩展。有效处理大量光线的渲染技术将在第20章讨论。此外,为了简单起见,我们将只支持一种类型的光源:点光源。尽管实现非常简单,但它遵循了前面介绍的最佳实践。

Shading models are not implemented in isolation, but in the context of a larger rendering framework. This example is implemented inside a simple WebGL 2 application,modified from the “Phong-shaded Cube” WebGL 2 sample by Tarek Sherif, but the same principles apply to more complex frameworks as well.

着色模型不是孤立地实现的,而是在更大的渲染框架的上下文中实现的。这个例子是在一个简单的WebGL 2应用程序中实现的,该应用程序由Tarek Sherif的“Phong-shaded Cube”web GL 2示例修改而来,但是相同的原理也适用于更复杂的框架。

We will be discussing some samples of GLSL shader code and JavaScript WebGL calls from the application. The intent is not to teach the specifics of the WebGL API but to show general implementation principles. We will go through the implementation in “inside out” order, starting with the pixel shader, then the vertex shader, and finally the application-side graphics API calls.

们将讨论应用程序中GLSL着色器代码和JavaScript WebGL调用的一些示例。目的不是讲授WebGL API的细节,而是展示一般的实现原则。我们将按照“从里到外”的顺序完成实现,从像素着色器开始,然后是顶点着色器,最后是应用程序端图形API调用。

Before the shader code proper, the shader source includes definitions of the shader inputs and outputs. As discussed earlier in Section 3.3, using GLSL terminology,shader inputs fall into two categories. One is the set of uniform inputs, which have values set by the application and which remain constant over a draw call. The second type consists of varying inputs, which have values that can change between shader invocations (pixels or vertices). Here we see the definitions of the pixel shader’s varying inputs, which in GLSL are marked in, as well as its outputs:

在着色器代码本身之前,着色器源包括着色器输入和输出的定义。正如前面在3.3节中讨论的,使用GLSL术语,着色器输入分为两类。一个是一组统一的输入,其值由应用程序设置,并且在绘制调用过程中保持不变。第二种类型由变化的输入组成,其值可以在着色器调用(像素或顶点)之间变化。在这里,我们看到了像素着色器的不同输入的定义,在GLSL中标记为,以及它的输出:

This pixel shader has a single output, which is the final shaded color. The pixel shader inputs match the vertex shader outputs, which are interpolated over the triangle before being fed into the pixel shader. This pixel shader has two varying inputs:surface position and surface normal, both in the application’s world-space coordinate system. The number of uniform inputs is much larger, so for brevity we will only show the definitions of two, both related to light sources: 

这个像素着色器有一个单一的输出,这是最终的着色颜色。像素着色器输入与顶点着色器输出相匹配,顶点着色器输出在输入到像素着色器之前进行三角形插值。这个像素着色器有两个不同的输入:表面位置和表面法线,都在应用程序的世界空间坐标系中。统一输入的数量要大得多,因此为了简洁起见,我们将只显示两个统一输入的定义,它们都与光源相关:

Since these are point lights, the definition for each one includes a position and a color. These are defined as vec4 instead of vec3 to conform to the restrictions of the GLSL std140 data layout standard. Although, as in this case, the std140 layout can lead to some wasted space, it simplifies the task of ensuring consistent data layout between CPU and GPU, which is why we use it in this sample. The array of Light structs is defined inside a named uniform block, which is a GLSL feature for binding a group of uniform variables to a buffer object for faster data transfer. The array length is defined to be equal to the maximum number of lights that the application allows in a single draw call. As we will see later, the application replaces the MAXLIGHTS string in the shader source with the correct value (10 in this case) before shader compilation.The uniform integer uLightCount is the actual number of active lights in the draw call. 

因为这些是点光源,所以每个点光源的定义都包括位置和颜色。这些被定义为vec4而不是vec3,以符合GLSL std140数据布局标准的限制。虽然在这种情况下,std140布局会导致一些空间浪费,但它简化了确保CPU和GPU之间数据布局一致的任务,这就是我们在本示例中使用它的原因。轻型结构数组在命名的统一块中定义,这是一个GLSL特性,用于将一组统一变量绑定到缓冲区对象,以加快数据传输。数组长度被定义为等于应用程序在单个绘制调用中允许的最大灯光数。正如我们将在后面看到的,在着色器编译之前,应用程序将着色器源中的MAXLIGHTS字符串替换为正确的值(在本例中为10)。统一整数uLightCount是drawcall中活动灯光的实际数量。

Next, we will take a look at the pixel shader code:

 接下来,我们将看看像素着色器代码:

 We have a function definition for the lit term, which is called by the main() function. Overall, this is a straightforward GLSL implementation of Equations 5.20 and 5.21. Note that the values of funlit() and cwarm are passed in as uniform variables.Since these are constant over the entire draw call, the application can compute these values, saving some GPU cycles.我们对lit项有一个函数定义,由main()函数调用。总的来说,这是方程5.20和5.21的简单的GLSL实现。注意,Funlit()和Cwarm的值是作为统一变量传入的。由于这些值在整个drawcall中保持不变,因此应用程序可以计算这些值,从而节省一些GPU周期。

This pixel shader uses several built-in GLSL functions. The reflect() function reflects one vector, in this case the light vector, in the plane defined by a second vector,in this case the surface normal. Since we want both the light vector and reflected vector to point away from the surface, we need to negate the former before passing it into reflect(). The clamp() function has three inputs. Two of them define a range to which the third input is clamped. The special case of clamping to the range between 0 and 1 (which corresponds to the HLSL saturate() function) is quick, often effectively free, on most GPUs. This is why we use it here, although we only need to clamp the value to 0, as we know it will not exceed 1. The function mix() also has three inputs and linearly interpolates between two of them, the warm color and the highlight color in this case, based on the value of the third, a mixing parameter between 0 and 1. In HLSL this function is called lerp(), for “linear interpolation.” Finally, normalize() divides a vector by its length, scaling it to a length of 1.

 这个像素着色器使用了几个内置的GLSL函数。reflect()函数在由第二个向量(在本例中为曲面法线)定义的平面中反射一个向量(在本例中为灯光向量)。因为我们希望灯光向量和反射向量都指向远离曲面的方向,所以我们需要在将前者传递给reflect()之前对其取反。clamp()函数有三个输入。其中两个定义了第三输入限制的范围。在大多数GPU上,限制到0和1之间的范围(对应于HLSL saturate()函数)的特殊情况是快速的,通常是有效自由的。这就是我们在这里使用它的原因,尽管我们只需要将值固定为0,因为我们知道它不会超过1。函数mix()也有三个输入,并基于第三个输入(介于0和1之间的混合参数)的值,在其中两个输入(在本例中为暖色和高光色)之间进行线性插值。在HLSL,这个函数被称为lerp(),意为“线性插值”。最后,normalize()将一个向量除以它的长度,将其缩放到长度1。

Now let us look at the vertex shader. We will not show any of its uniform definitions since we already saw some example uniform definitions for the pixel shader, but the varying input and output definitions are worth examining:

现在让我们看看顶点着色器。我们将不会显示它的任何统一定义,因为我们已经看到了像素着色器的一些统一定义示例,但不同的输入和输出定义值得研究:

Note that, as mentioned earlier, the vertex shader outputs match the pixel shader varying inputs. The inputs include directives that specify how the data are laid out in the vertex array. The vertex shader code comes next: 

注意,如前所述,顶点着色器输出与像素着色器可变输入相匹配。输入包括指定数据如何在顶点数组中布局的指令。接下来是顶点着色器代码:

These are common operations for a vertex shader. The shader transforms the surface position and normal into world space and passes them to the pixel shader for use in shading. Finally, the surface position is transformed into clip space and passed into gl Position, a special system-defined variable used by the rasterizer.The gl Position variable is the one required output from any vertex shader. 

这些是顶点着色器的常见操作。着色器将曲面位置和法线转换到世界空间,并将它们传递给像素着色器以用于着色。最后,曲面位置被转换到裁剪空间并传递到gl位置,这是光栅化使用的一个特殊的系统定义变量。gl位置变量是任何顶点着色器所需的输出。

Note that the normal vectors are not normalized in the vertex shader. They do not need to be normalized since they have a length of 1 in the original mesh data and this application does not perform any operations, such as vertex blending or nonuniform scaling, that could change their length unevenly. The model matrix could have a uniform scale factor, but that would change the length of all normals proportionally and thus not result in the problem shown on the right side of Figure 5.10.

请注意,法线向量在顶点着色器中没有归一化。它们不需要归一化,因为它们在原始网格数据中的长度为1,并且该应用程序不执行任何操作,例如顶点混合或非均匀缩放,这些操作可能会不均匀地改变它们的长度。模型矩阵可以有一个统一的比例因子,但是这会成比例地改变所有法线的长度,因此不会导致图5.10右侧所示的问题。、

The application uses theWebGL API for various rendering and shader setup. Each of the programmable shader stages are set up individually, and then they are all bound to a program object. Here is the pixel shader setup code:

该应用程序使用wegl API进行各种渲染和着色器设置。每个可编程着色器阶段都是单独设置的,然后它们都绑定到一个程序对象。以下是像素着色器设置代码:

Note the “fragment shader” references. This term is used byWebGL (and OpenGL,on which it is based). As noted earlier in this book, although “pixel shader” is less precise in some ways, it is the more common usage, which we follow in this book. This code is also where the MAXLIGHTS string is replaced with the appropriate numerical value. Most rendering frameworks perform similar pre-compilation shader manipulations.

请注意“片段着色器”参考。这个术语由WebGL(以及它所基于的OpenGL)使用。正如本书前面提到的,虽然“像素着色器”在某些方面不太精确,但它是更常见的用法,这是我们在本书中遵循的。在这段代码中,MAXLIGHTS字符串也被替换为适当的数值。大多数渲染框架执行类似的预编译着色器操作。

There is more application-side code for setting uniforms, initializing vertex arrays,clearing, drawing, and so on, which you can view in the program and which are explained by numerous API guides. Our goal here is to give a sense of how shaders are treated as separate processors, with their own programming environment. We thus end our walkthrough at this point. 

还有更多的应用程序端代码用于设置统一、初始化顶点数组、清除、绘制等等,这些代码可以在程序中查看,并由大量API指南解释。我们在这里的目标是让大家了解着色器是如何被视为独立的处理器,拥有自己的编程环境。我们就此结束我们的演练。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

椰子糖莫莫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值