#ggfgfgf 2024.7.3 [Shader][战团]着色器入门 https://bbs.mountblade.com.cn/thread-2106079-1-1.html (出处: 骑马与砍杀中文站论坛) (二)骑砍战团着色器详解 在掌握了着色器的基本理论后,下面将从代码层面观察一个示例。 为了更好的了解着色器的基本逻辑,我们将以一个战团着色器为例,对每个变量跟踪并阐明用途。 #SupaNinjaMan 你已经知道着色器由三部分组成,即顶点着色器、像素着色器和技术。 我们还将声明顶点着色器输出的结构,主要是完成主义的缘故,但通常不是必要的。不过,对于需要传递唯一信息的更高级着色器,它是。 定义 VS_OUTPUT 结构 struct VS_OUTPUT_FIRST_SHADER // 定义顶点着色器的输出结构,以及传递到像素着色器的任何变量 { float4 Pos : POSITION; // 顶点在投影世界视图空间中的位置,用于最终的屏幕映射 float2 Tex0 : TEXCOORD0; // 顶点的纹理坐标,类似于UV坐标,用于纹理映射 float4 Color : COLOR0; // 顶点的颜色,可以用于顶点着色 float Fog : FOG; // 雾化效果的值,用于像素着色器中的雾化计算 }; // 结构:输出结构 vs_着色器名称(从引擎传入的值) VS_OUTPUT_FIRST_SHADER vs_my_first_shader(float4 vPosition : POSITION, float4 vColor : COLOR, float2 tc : TEXCOORD0) { VS_OUTPUT_FIRST_SHADER Out; // Out 是输出变量名 float4 vWorldPos = mul(matWorld, vPosition); // 世界空间中的 vPosition,用于雾化计算 Out.Tex0 = tc; //纹理坐标直接传递,不做修改 Out.Pos = mul(matWorldViewProj, vPosition); // 投影世界视图空间中的 vPosition (摄像机的世界空间) // 原生雾化计算 // float3 P = mul(matWorldView, vPosition); // 视图空间中的 vPosition float d = length(P); // 到摄像机的距离 Out.Fog = get_fog_amount_new(d, vWorldPos.z); // 传递给一个函数,使用深度和高度来确定雾化量 Out.Color = vColor; // 直接将输入的顶点颜色 vColor 传递给像素着色器,不进行任何修改 return Out; // 输出到顶点着色器 } 你可能会想,嘿,为什么没有PS_INPUT,你可以做一个,但渲染管线假设VS_OUTPUT完全相同于PS_INPUT, 所以它是多余的,在任何原生着色器中都完全没有使用过。 PS_OUTPUT ps_my_first_shader(VS_OUTPUT_FIRST_SHADER In) { PS_OUTPUT Output; // 命名输出变量为 Output // 注册纹理映射和统一的变量 float4 diffuse_color = tex2D(DiffuseTextureSamplerNoWrap, In.Tex0); //读取位于纹理坐标的像素的颜色 Output.RGBColor = diffuse_color; // 输出像素着色器结构 return Output;// 输出像素着色器结构 } technique my_first_shader // Name the shader technique { pass P0 //第一个通过,我们引擎中只有一个:哭:( { VertexShader = compile vs_2_0 vs_my_first_shader(); // 我们的顶点着色器,使用 vs_2_0 编译,没有常量 PixelShader = compile ps_2_0 ps_my_first_shader(); // 我们的像素着色器,使用 ps_2_0 编译,没有常量 } } #SupaNinjaMan 在HLSL中,特别是在定义着色器技术(technique)时,pass 是一个重要的概念。 每个技术可以包含一个或多个 pass,每个 pass 定义了渲染管线的一个阶段,我们的战团引擎只能有一个:哭:( 还是一头雾水?这是很有可能的,或者你已经读懂了大部分,那恭喜你天赋异禀! 这其中包含大量专业的术语概念,并用中文翻译了。 ////注意,战团的fx源码不支持中文,在实际编写代码时,请使用英文编写和注释。//// 下面将逐行的解释并阐明术语概念。 定义 VS_OUTPUT 结构,这个在第一章就解释过,你应该已经明确了其功能。 但是,其中定义的变量目的不很好理解。 float4 Pos : POSITION; // 顶点在投影世界视图空间中的位置,用于最终的屏幕映射 顶点:代表3D模型的一个顶点 投影世界视图空间:这里应该是透视投影,投影指模型在屏幕上的投影 float2 Tex0 : TEXCOORD0; // 顶点的纹理坐标,类似于UV坐标,用于纹理映射 纹理坐标:纹理坐标通常表示为一对浮点数(u, v),其中: u 表示水平方向的坐标,范围从0到1。 v 表示垂直方向的坐标,范围也是从0到1。 float4 Color : COLOR0; // 顶点的颜色,可以用于顶点着色 float Fog : FOG; // 雾化效果的值,用于像素着色器中的雾化计算 顶点着色器,这一部分是着色器的开端部分,代码执行通常由这一部分开始。 VS_OUTPUT_FIRST_SHADER vs_my_first_shader(float4 vPosition : POSITION, float4 vColor : COLOR, float2 tc : TEXCOORD0) 结构:输出结构 vs_着色器名称(从引擎传入的值) VS_OUTPUT_FIRST_SHADER为结构名,输出结构,由 struct 定义,代表了该顶点着色器的输出结构。实际上,这不是必须的。 vs_my_first_shader 是该顶点着色器的名称,应当是唯一的,供 technique 调用。 (float4 vPosition : POSITION, float4 vColor : COLOR, float2 tc : TEXCOORD0) 四个传入参数,根据语义指示获取了引擎的数据。 vPosition 一个四元组,代表顶点位置,vColor 一个四元组,代表顶点颜色,tc 一个二元组,代表贴图一通道的纹理坐标。 VS_OUTPUT_FIRST_SHADER Out; // Out 是输出变量名,结构是VS_OUTPUT_FIRST_SHADER 由VS_OUTPUT 定义结构,即包含的元素。Out用于返回给顶点着色器数据。 float4 vWorldPos = mul(matWorld, vPosition); // 世界空间中的 vPosition,用于雾化计算 Out.Tex0 = tc;//纹理坐标直接传递,不做修改 为渲染提供每个贴图像素应该显示的位置(相对于模型,在模型上的位置,即 UV ) Out.Pos = mul(matWorldViewProj, vPosition); // 投影世界视图空间中的 vPosition (摄像机的世界空间) Tex0 和 pos 两个属性几乎都是必须的,当然,也可以没有tex属性,那将会使得渲染时不获取贴图的颜色。但是pos是必须的, 因为必须确定渲染的位置。 // 原生雾化计算 // float3 P = mul(matWorldView, vPosition); // 视图空间中的 vPosition float d = length(P); // 到摄像机的距离 Out.Fog = get_fog_amount_new(d, vWorldPos.z); // 传递给一个函数,使用深度和高度来确定雾化量 // 原生雾化计算 //这段几乎每个战团着色器都有 Out.Color = vColor;// 直接将输入的顶点颜色 vColor 传递给像素着色器,不进行任何修改 return Out;// 输出到顶点着色器,返回值out PS_OUTPUT ps_my_first_shader(VS_OUTPUT_FIRST_SHADER In) PS_OUTPUT 像素着色器的输出结构,已经在开头定义过。具体注释过,不再赘述。 ps_my_first_shader 像素着色器名称,应当是唯一的,供 technique 调用。 (VS_OUTPUT_FIRST_SHADER In) 括号内是入参,即传入的参数数据。这里命名一个结构为 VS_OUTPUT_FIRST_SHADER , 名为 In 的变量,接受传入的参数,应该是来自顶点着色器的返回值,即上面return 的 Out 变量。 由于 In 和 Out 两变量的结构相同,都为 VS_OUTPUT_FIRST_SHADER 。 因此数据的传入是 1:1 的。 这里,你其实可以把 In 看作 Out 变量的复制。 PS_OUTPUT Output;// 命名输出变量为 Output ,这里的Output // 注册纹理映射和统一的变量 float4 diffuse_color = tex2D(DiffuseTextureSamplerNoWrap, In.Tex0); //读取位于纹理坐标的像素的颜色 根据 In.Tex0 (即贴图uv) 采样贴图颜色。 Output.RGBColor = diffuse_color; // 输出像素着色器结构 Output的RGBColor元素,即最终绘制的颜色值。 return Output;// 输出像素着色器结构,返回到引擎 下面是参考资料 在计算机图形学中,"顶点"(Vertex)是构成多边形网格(通常用于表示三维模型)的基本单元。每个顶点可以包含多种数据, 但最基本的是其在三维空间中的位置坐标。以下是顶点的一些关键特性和用途: 位置坐标:每个顶点都有一个唯一的三维空间坐标(x, y, z),表示它在虚拟世界中的位置。 纹理坐标(Texture Coordinates):除了空间位置,顶点还可能包含纹理坐标(通常为二维坐标,u, v),用于映射纹理到模型表面。 为什么纹理坐标叫UV?因为它是一个二维坐标组,横坐标U,纵坐标V 法线(Normals):顶点的法线是垂直于模型表面的向量,用于计算光照和阴影效果。 在计算机图形学中,"法线"(Normal)是一个在顶点级别定义的向量,它表示顶点表面在该点的切向空间的垂直方向。法线对于渲染过程中的光照和着色非常重要,因为它们影响表面对光的反射和吸收方式。以下是法线的主要用途和特性: 定义表面方向:法线向量垂直于多边形表面,指向表面之外,这有助于确定光线如何与表面相互作用。 光照计算:在渲染过程中,法线用于计算光照效果,如漫反射(Diffuse Lighting)、镜面反射(Specular Reflection)和环境光遮蔽(Ambient Occlusion)等。这些计算通常基于法线和光源方向之间的关系。 着色:法线对于确定表面的颜色至关重要,因为它们影响光线如何与表面相互作用,从而影响最终的颜色和亮度。 渲染质量:法线的质量可以显著影响渲染的视觉效果。例如,平滑的法线可以产生更柔和的阴影和高光效果,而锐利的法线可以产生更清晰的边缘。 动画和变形:在动画和变形过程中,法线也必须相应地更新,以保持表面的正确方向。 存储和计算:法线通常存储在顶点数据中,并且在顶点着色器中进行处理。在某些情况下,法线可能会被插值,以在像素着色器中使用,这称为法线映射(Normal Mapping)。 法线贴图(Normal Mapping):这是一种高级技术,通过使用一张特殊的纹理图(法线贴图)来存储表面每个点的法线信息,从而在不增加几何复杂度的情况下增加表面的详细程度。 颜色:顶点可能包含颜色信息,用于着色或材质效果。 切线和副法线(Tangents and Bitangents):这些向量用于高级着色技术,如高光和反射效果。 在三维计算机图形学中,切线(Tangent)和副法线(Bitangent)是与顶点相关的向量,它们与法线(Normal)一起定义了局部切线空间(Tangent Space)。 这个空间对于高级着色和光照效果至关重要,尤其是在使用法线贴图和凹凸映射技术时。以下是切线和副法线的详细解释: 切线(Tangent): 切线是一个与表面相切的向量,通常沿着模型的U轴方向。 在网格的每个顶点上,切线向量指示了表面在该点沿U方向的变化率。 在纹理映射中,切线用于确定纹理沿表面的水平方向如何展开。 副法线(Bitangent): 副法线是一个垂直于法线和切线向量的向量,通常沿着模型的V轴方向。 它定义了表面在V方向上的变化率,与切线一起帮助确定纹理的垂直展开方式。 在某些情况下,副法线也用于模拟表面的细节,如在凹凸映射中模拟微小的凹凸不平。 法线(Normal): 法线是垂直于表面并指向外部的向量,用于计算光照和阴影效果。 法线、切线和副法线一起构成了局部切线空间的基础。 切线空间(Tangent Space): 切线空间是一个局部坐标系,其中切线、副法线和法线分别作为X、Y和Z轴。 在这个空间中,法线贴图的数据通常以切线空间为基础进行存储和处理。 计算方法: 切线和副法线通常在网格的每个顶点上计算,并且随着顶点在网格中的移动而进行插值。 它们可以通过顶点的位置和纹理坐标的变化来计算,例如通过两个相邻顶点的位置差和纹理坐标差来确定切线和副法线的方向。 应用: 在渲染过程中,切线和副法线用于将法线贴图中的法线方向从切线空间转换到全局空间。 这对于实现逼真的光照和阴影效果至关重要,尤其是在使用复杂的着色技术时。 存储和使用: 在存储法线贴图时,切线和副法线的方向通常与法线一起存储在贴图中,或者通过计算得到。 在着色器中,使用这些向量将切线空间中的法线转换到全局空间,以便进行光照计算。 通过使用切线、副法线和法线,图形渲染算法能够更准确地模拟光线与表面的相互作用,从而在不增加几何复杂度的情况下,实现更丰富的视觉效果。 顶点属性(Vertex Attributes):除了上述信息,顶点还可以包含其他属性,如骨骼权重和索引,用于动画和变形。 在渲染过程中,顶点数据首先被送入顶点着色器(Vertex Shader),在这里它们可以进行变换、裁剪、光照计算等操作。 然后,变换后的顶点数据被用于构建屏幕空间中的像素,这些像素随后被送入像素着色器(Pixel Shader)进行进一步处理,如纹理映射、颜色混合等。 在计算机图形学中,"投影世界视图空间"(Projection World View Space)通常指的是将三维世界中的顶点坐标转换到一个适合投影(如透视或正交投影)的坐标空间。 这个过程涉及以下几个步骤: 模型变换(Model Transformation): 首先,顶点从模型的局部坐标系转换到世界坐标系。这通常涉及到平移、旋转和缩放变换。 视图变换(View Transformation): 接下来,顶点从世界坐标系转换到视图坐标系(也称为摄像机空间)。这一步是通过摄像机的位置、旋转和方向来实现的, 它将世界坐标系中的顶点移动到以摄像机为中心的空间中。 投影变换(Projection Transformation): 最后,顶点从视图坐标系转换到投影坐标系。这一步通过投影矩阵来完成,它定义了如何将三维视图坐标系中的顶点投影到二维屏幕空间。 投影可以是透视投影,它模拟人眼观察世界的方式,即远处的物体看起来更小;或者是正交投影,它保持物体的尺寸不变,不论它们离摄像机的距离如何。