原文:
Checkerboard Pattern
Summary
我觉得使用着色器来生成图片比较有意思。下面我以棋盘格为例,像你们展示如何通过程序生成模型纹理的。
Stripes
参考前面关于二维平面映射的教程,这里我们也是基于模型顶点的世界坐标来生成UV,这样的话,移动模型的过程中,其表面纹理在前后帧的渲染图可以无缝衔接。如果你希望生成的纹理跟随模型一起运动,那么可以选择基于模型坐标系进行计算。
首先,我们在顶点着色器中通过坐标变换,计算得到顶点的世界坐标,然后通过中间插值数据结构将计算结果传递到片段着色器。
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct v2f{ float4 position : SV_POSITION; float3 worldPos : TEXCOORD0; }
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; }
|
然后在片段着色器中,我们首先考虑一个维度上的棋盘格效果,也就是黑白相间的条纹效果。方法很简单,就是选取世界坐标中的一个轴向的值,然后取整,两个整数之间的数取整后的结果是一样的,也就是说取整后的值代表了两个整数之间的区域,正是我们这个里的条纹效果。
然后我们要区分条纹的奇偶顺序,因为两个相邻的条纹刚好可以用两个相邻的整数来表示,所以只需要求其整数的奇偶性。求一个整数的奇偶性,可以通过对2的求余操作来实现。然后通过奇偶性来判断其颜色。
1 2 3 4 5 6 7 8 9
| fixed4 frag(v2f i) : SV_TARGET{ float chessboard = floor(i.worldPos.x); chessboard = frac(chessboard * 0.5); chessboard *= 2; return chessboard; }
|
Checkerboard in 2d and 3d
接下来,我们处理两个轴向的棋盘格。按照前面的操作,另外在选一个轴向进行计算,这时候分别知道两个轴的奇偶性,然后两个奇偶值相加,再求一遍奇偶性,得到最终格子的颜色。实际上可以进一步优化,可以直接在取整之后就求和,然后后面的奇偶求解可以合并。
1 2 3 4 5 6 7 8 9
| fixed4 frag(v2f i) : SV_TARGET{ float chessboard = floor(i.worldPos.x) + floor(i.worldPos.y); chessboard = frac(chessboard * 0.5); chessboard *= 2; return chessboard; }
|
我们还可以进一步扩展到三个轴。
1 2 3 4 5 6 7 8 9
| fixed4 frag(v2f i) : SV_TARGET{ float chessboard = floor(i.worldPos.x) + floor(i.worldPos.y) + floor(i.worldPos.z); chessboard = frac(chessboard * 0.5); chessboard *= 2; return chessboard; }
|
Scaling
下面我们再给棋盘格增加一个缩放功能。我们需要在材质面板上引入缩放变量,方便后续调参。然后在片段着色器中,先将世界坐标处以这个缩放参数,然后在执行上面的操作。这样我们在材质面板上减小缩放变量时,棋盘格的大小也会变小。
除此之外,这里还有一个细微的改变,就是我们不再是对各个轴向分开取整,而是直接采样向量的方法,同时对所有轴向进行求整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
Properties{ _Scale ("Pattern Size", Range(0,10)) = 1 }
float _Scale;
fixed4 frag(v2f i) : SV_TARGET{ float3 adjustedWorldPos = floor(i.worldPos / _Scale); float chessboard = adjustedWorldPos.x + adjustedWorldPos.y + adjustedWorldPos.z; chessboard = frac(chessboard * 0.5); chessboard *= 2; return chessboard; }
|
Customizable Colors
最后,我们还可以增加两个变量来控制棋盘格的颜色。在片段着色器的最后我使用线性插值函数来实现两种颜色的二选一操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
Properties{ _Scale ("Pattern Size", Range(0,10)) = 1 _EvenColor("Color 1", Color) = (0,0,0,1) _OddColor("Color 2", Color) = (1,1,1,1) }
float4 _EvenColor; float4 _OddColor;
fixed4 frag(v2f i) : SV_TARGET{ float3 adjustedWorldPos = floor(i.worldPos / _Scale); float chessboard = adjustedWorldPos.x + adjustedWorldPos.y + adjustedWorldPos.z; chessboard = frac(chessboard * 0.5); chessboard *= 2;
float4 color = lerp(_EvenColor, _OddColor, chessboard); return color; }
|
下面是最终的棋盘格生成着色器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| Shader "Tutorial/011_Chessboard" { Properties{ _Scale ("Pattern Size", Range(0,10)) = 1 _EvenColor("Color 1", Color) = (0,0,0,1) _OddColor("Color 2", Color) = (1,1,1,1) }
SubShader{ Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{ CGPROGRAM #include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
float _Scale;
float4 _EvenColor; float4 _OddColor;
struct appdata{ float4 vertex : POSITION; };
struct v2f{ float4 position : SV_POSITION; float3 worldPos : TEXCOORD0; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; }
fixed4 frag(v2f i) : SV_TARGET{ float3 adjustedWorldPos = floor(i.worldPos / _Scale); float chessboard = adjustedWorldPos.x + adjustedWorldPos.y + adjustedWorldPos.z; chessboard = frac(chessboard * 0.5); chessboard *= 2;
float4 color = lerp(_EvenColor, _OddColor, chessboard); return color; }
ENDCG } } FallBack "Standard" }
|
希望本篇对你有所帮助,能够让你知道如何通过程序实现模型纹理图。
你可以在以下链接找到源码:https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/011_ChessBoard/Chessboard.shader
希望你能喜欢这个教程哦!如果你想支持我,可以关注我的推特,或者通过ko-fi、或patreon给两小钱。总之,各位大爷,走过路过不要错过,有钱的捧个钱场,没钱的捧个人场:-)!!!