面向 VRChat 的渲染入门 (1) Shader 基础笔记
2025/10/20大约 2 分钟
前言
算是一个作业记录?哈哈哈
任务是使用 shader 实现:
- 线性渐变
- 径向渐变
- 进度条
什么是线性渐变和径向渐变
不知道喵,看 MDN linear-gradient() 和 radial-gradient() 体会一下。

环境预设
搭建一个场景,存在这样的一个 Cube:

一个 Cube 有 6 个面,考虑每个面渲染一个图像,主要工作在顶点着色器上,我们预先写下一个板子:
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" { }
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 worldNormal : NORMAL;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 linear_gradient(float2 uv, float4 color1, float4 color2)
{
}
fixed4 radial_gradient(float2 uv, float4 color1, float4 color2)
{
}
fixed4 progress_bar(float2 uv, float4 color, float percentage)
{
}
fixed4 frag(v2f i) : SV_Target
{
float3 normal = normalize(i.worldNormal);
float time = abs(sin(_Time.y));
float4 col = step(0.5, dot(normal, float3(-1, 0, 0))) * linear_gradient(i.uv, float4(1.0, 0.0, 0.0, 0.0), float4(0.0, 1.0, 0.0, 0.0))
+ step(0.5, dot(normal, float3(0, 1, 0))) * radial_gradient(i.uv, float4(1.0, 0.0, 0.0, 0.0), float4(0.0, 1.0, 0.0, 0.0))
+ step(0.5, dot(normal, float3(0, 0, -1))) * progress_bar(i.uv, float4(0.0, 1.0, 0.0, 0.0), time);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDHLSL
}
}
}工作就变成了实现 linear_gradient 、radial_gradient 和 progress_bar 这三个函数。
线性渐变
考虑最简单的无角度单向渐变,给定两个颜色 color1 和 color2,在一个方形平面内,我们有:
(0,1) (1,1)
+---+
| |
+---+
(0,0) (1,0)
--> x注意到坐标范围是 [0,1],则我们可以直接用作颜色权重。以 x 坐标为渐变方向,给定的任意一点的颜色值为:
写成 HLSL 是:
fixed4 linear_gradient(float2 uv, float4 color1, float4 color2)
{
return color2 * uv.x + color1 * (1.0 - uv.x);
}径向渐变
仍然考虑上述平面,以平面中心为圆心,半径为 0.5,则面上给定的任意一点颜色值为:
其中
写成 HLSL 代码为:
fixed4 radial_gradient(float2 uv, float4 color1, float4 color2)
{
float r = length(uv - 0.5);
return color2 * 2 * r + color1 * 2 * (0.5 - r);
}进度条
进度条可以看作是一个圆环。为了制作一个不影响其他地方颜色的圆环,我们需要内外两个遮罩:

对这两个遮罩做乘法,我们将得到

然后在这个遮罩上根据百分比铺色即可。注意需要从屏幕坐标转为极坐标,相应的代码为:
fixed4 progress_bar(float2 uv, float4 color, float percentage)
{
float2 rpos = float2(length(uv - 0.5), (atan2(uv.x - 0.5, uv.y - 0.5) / UNITY_PI + 1.0) / 2);
float mask0 = step(0.3, rpos.x);
float mask1 = step(rpos.x, 0.5);
float ringmask = step(rpos.y, percentage) * mask0 * mask1;
float4 ring = color * float4(ringmask, ringmask, ringmask, 0);
return ring;
}Fin.