完美Billboard实现
本帖最后由 Thepoly 于 2023-5-9 12:08 编辑今天带来Billboard的完美实现我是Thepoly
1
什么是Billboard?
基于视角确定纹理矩形朝向的技术就叫做Billboard(公告牌)Billboard结合Alpha纹理和纹理动画能实现许多诸如草,烟,火,雾,爆炸,云等特殊效果
2
核心实现方式
方法一 · Obejct Space广告牌技术的本质就是构建旋转矩阵,而我们知道一个变换矩阵需要3个基向量。广告牌技术使用的基向量通常就是表面法线(normal)、指向上的方向(up)以及指向右的方向(right)。除此之外,我们还需要指定一个锚点(anchor location),这个锚点在旋转过程中是固定不变的,以此来确定多边形在空间中的位置。基于视角指定物体的表面法向与向上向量,叉乘得到物体的向右向量,从而建立一个新的基于视角的坐标系在Obejct Space根据新的旋转后的坐标系实施对物体的旋转。具体实施的话可以记录原物体在Obejct Space的各个顶点相对于Obejct Space原点(0,0,0)的各方向(x,y,z)位移,利用从World Space变换到Obejct Space的相机位置确定视线方向从而确定物体法向(物体表面法向将与视线方向相反且平行),并利用指定向上方向重建新的坐标系,将原物体顶点相对位移转移至新坐标系建立转向后新的位置,实现在物体空间下完成物体转向。最后所得的旋转矩阵为M=(right,up,normal),全程在Obejct Space下变换。
方法二 · World Space通过UNITY_MATRIX_V矩阵,归一化各行的值,并构建旋转矩阵:UNITY_MATRIX_V.xyz = world space camera Right unit vectorUNITY_MATRIX_V.xyz = world space camera Up unit vectorUNITY_MATRIX_V.xyz = -1 * world space camera Forward unit vector这样,我们就得到了一个新的基于视角的坐标系UNITY_MATRIX_M矩阵最后一列是模型中心的世界坐标前三行三列的每一列的向量长度分别对应(x,y,z)三个轴向的缩放值前三行三列的向量除以其对应轴的缩放值,并且将最后一列的前三行与最后一行的前三列置为0即为旋转矩阵,我们可以用这个旋转矩阵得到四元数,各个轴向的旋转角等
我们求出(x,y,z)的长度,即缩放值后,与构建的旋转矩阵相乘(左乘),最后在加上UNITY_MATRIX_M矩阵的最后一行,求出World Space下的位置信息。Tip: 为什么是左乘而不是右乘?左乘:坐标系不动,点动,则左乘。【若绕静坐标系(世界坐标系)旋转,则左乘,也是变换矩阵乘坐标矩阵;】右乘:点不动,坐标系动,则右乘。【若是绕动坐标系旋转(自身建立一个坐标系),则右乘,也就是坐标矩阵乘变换矩阵】即:在固定坐标系下面,我们用左乘的方法;在非固定的坐标系下面,用右乘。最后我们输出到齐次裁剪空间,整个过程是在World Space下计算的。除了矩阵乘法以外我们也可以拆开去计算,这里就可以整理出一个计算公式。
方法三 · BillBoard.cs
void OnWillRenderObject() {transform.LookAt(transform.position + Camera.transform.forward, Camera.transform.up); switch (_mode) { case Mode.HorizontalBillboard: Vector3 angles = transform.eulerAngles; angles.x = 90; transform.eulerAngles = angles; break;
case Mode.VerticalBillboard: Vector3 angles = transform.eulerAngles; angles.x = 0; transform.eulerAngles = angles; break; }}
脚本挂在物体上,直接改变Transform组件,非常简单。但是当物体是勾选了bacthing static标记,Unity会对他们进行静态合并,经过合并后的Mesh是不能变动的,所以会失去效果。方法是在OnWillRenderObject()中实现的。
3
BuildIn下的BillboardShader "Custom/Billboard"{ Properties {_Color ("Color", Color) = (1, 1, 1, 1)_MainTex ("MainTex", 2D) = "white" { }_BillboardMode ("BillboardMode", int) = 0 }
SubShader {
Tags { "RenderType" = "Opaque" "Queue" = "AlphaTest" "DisableBatching" = "True" }
CGINCLUDE#pragma target 2.0ENDCG
Pass{ Name "Unlit" Tags { "LightMode" = "ForwardBase" } CGPROGRAM
#pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; float4 color : COLOR; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO };
uniform float4 _Color; uniform sampler2D _MainTex; uniform float4 _MainTex_ST; uniform float _BillboardMode;
v2f vert(appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); UNITY_TRANSFER_INSTANCE_ID(v, o); //计算新的Billboard顶点位置和法线
//1.构建视口空间下的旋转矩阵 2.模式切换 //UNITY_MATRIX_V.xyz == world space camera Up unit vector float3 upCamVec = lerp(normalize(UNITY_MATRIX_V._m10_m11_m12), float3(0, 1, 0), _BillboardMode); //UNITY_MATRIX_V.xyz == -1 * world space camera Forward unit vector float3 forwardCamVec = -normalize(UNITY_MATRIX_V._m20_m21_m22); //UNITY_MATRIX_V.xyz == world space camera Right unit vector float3 rightCamVec = normalize(UNITY_MATRIX_V._m00_m01_m02); float4x4 rotationCamMatrix = float4x4(rightCamVec, 0, upCamVec, 0, forwardCamVec, 0, 0, 0, 0, 1); //转换法线和切线 v.normalOS = normalize(mul(float4(v.normalOS, 0), rotationCamMatrix)).xyz; v.tangentOS.xyz = normalize(mul(float4(v.tangentOS.xyz, 0), rotationCamMatrix)).xyz; //求出缩放值,前三行三列的每一列的向量长度分别对应X,Y,Z三个轴向的缩放值 v.vertex.x *= length(UNITY_MATRIX_M._m00_m10_m20); v.vertex.y *= length(UNITY_MATRIX_M._m01_m11_m21); v.vertex.z *= length(UNITY_MATRIX_M._m02_m12_m22); //在固定坐标系下面,我们用左乘的方法;在非固定的坐标系下面,用右乘。 // v.vertex = mul(v.vertex, rotationCamMatrix); v.vertex = v.vertex.x * rotationCamMatrix._m00_m01_m02_m03 + v.vertex.y * rotationCamMatrix._m10_m11_m12_m13 + v.vertex.z * rotationCamMatrix._m20_m21_m22_m23 + v.vertex.w * rotationCamMatrix._m30_m31_m32_m33; //最后一列是模型中心的世界坐标,加上的是齐次坐标的偏移值,不加都会在原点 v.vertex.xyz += UNITY_MATRIX_M._m03_m13_m23; o.uv = v.uv * _MainTex_ST.xy + _MainTex_ST.zw; o.vertex = UnityWorldToClipPos(v.vertex); return o; }
fixed4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); fixed4 finalColor; finalColor = tex2D(_MainTex, i.uv); clip(finalColor.a - 0.5); return finalColor * _Color; } ENDCG
} }}
4
URP下的BillboardShader "taecg/Template/Unlit URP Shader"{ Properties { _BaseColor ("Color", Color) = (1, 1, 1, 1) _BaseMap ("MainTex", 2D) = "white" { } _BillboardMode ("BillboardMode", int) = 0 }
SubShader { Tags { "RenderType" = "Opaque" "Queue" = "AlphaTest" "DisableBatching" = "True" }
Pass { Name "Unlit" HLSLPROGRAM
#pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float fogCoord : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO };
CBUFFER_START(UnityPerMaterial) uniform float4 _BaseColor; uniform float4 _BaseMap_ST; uniform float _BillboardMode; CBUFFER_END
TEXTURE2D(_BaseMap);SAMPLER(sampler_BaseMap);
Varyings vert(Attributes v) { Varyings o = (Varyings)0; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); UNITY_TRANSFER_INSTANCE_ID(v, o); //计算新的Billboard顶点位置和法线
//1.构建视口空间下的旋转矩阵 2.模式切换 //UNITY_MATRIX_V.xyz == world space camera Up unit vector float3 upCamVec = lerp(normalize(UNITY_MATRIX_V._m10_m11_m12), float3(0, 1, 0), _BillboardMode); //UNITY_MATRIX_V.xyz == -1 * world space camera Forward unit vector float3 forwardCamVec = -normalize(UNITY_MATRIX_V._m20_m21_m22); //UNITY_MATRIX_V.xyz == world space camera Right unit vector float3 rightCamVec = normalize(UNITY_MATRIX_V._m00_m01_m02); float4x4 rotationCamMatrix = float4x4(rightCamVec, 0, upCamVec, 0, forwardCamVec, 0, 0, 0, 0, 1); //转换法线和切线 v.normalOS = normalize(mul(float4(v.normalOS, 0), rotationCamMatrix)).xyz; v.tangentOS.xyz = normalize(mul(float4(v.tangentOS.xyz, 0), rotationCamMatrix)).xyz; //求出缩放值,前三行三列的每一列的向量长度分别对应X,Y,Z三个轴向的缩放值 v.positionOS.x *= length(UNITY_MATRIX_M._m00_m10_m20); v.positionOS.y *= length(UNITY_MATRIX_M._m01_m11_m21); v.positionOS.z *= length(UNITY_MATRIX_M._m02_m12_m22); //在固定坐标系下面,我们用左乘的方法;在非固定的坐标系下面,用右乘。 // v.positionOS = mul(v.positionOS, rotationCamMatrix); v.positionOS = v.positionOS.x * rotationCamMatrix._m00_m01_m02_m03 + v.positionOS.y * rotationCamMatrix._m10_m11_m12_m13 + v.positionOS.z * rotationCamMatrix._m20_m21_m22_m23 + v.positionOS.w * rotationCamMatrix._m30_m31_m32_m33; //最后一列是模型中心的世界坐标,加上的是齐次坐标的偏移值,不加都会在原点 v.positionOS.xyz += UNITY_MATRIX_M._m03_m13_m23; o.uv = TRANSFORM_TEX(v.uv, _BaseMap); o.positionCS = TransformWorldToHClip(v.positionOS.xyz); o.fogCoord = ComputeFogFactor(o.positionCS.z); return o; }
half4 frag(Varyings i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); half4 finalColor; finalColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv); clip(finalColor.a - 0.5); finalColor.rgb = MixFog(finalColor.rgb, i.fogCoord); return finalColor * _BaseColor; } ENDHLSL
} }}
- End -
公众号链接:https://mp.weixin.qq.com/s/YaeHAWaAsZFye6_GpJfVMA
页:
[1]