Skip to content

Surface shaders (experimental)

This chapter is experimental. You can write what a Renderer draws in C#.

SyntaxDescriptionNote
[Surface] static Color4 M(SurfaceId id, ...)Deciding the color of a pixelRead a buffer by position with buf.Sample(id.UV); the nearest cell is returned as is, without interpolation. Put it on a Renderer with Gpu.Show(nameof(M), rend, buf, gain), passing the values on every call.
Vector3.Dot(id.Normal, toLight)Shading from the surface normal
Vector3.Dot(id.Normal, id.ViewDir)Using the view direction
static Color4 M(SurfaceId id, GpuBuffer2D buf, int n, bool b, Vector2 v, Vector3 w)Value-type parametersPass the values at the same positions in Gpu.Show.
[Surface] static Vector3 M(VertexId v, ...)Moving vertices
SyntaxDescriptionErrorReasonAlternative
static Color4 M(int n)The first parameter is neither type, so the role is undeterminedTUKI0001by designTake SurfaceId to return a color, VertexId to return a position
static float M(SurfaceId id)The color side does not return a pixel valueTUKI0001by designReturn Color4
static Vector3 M(VertexId v) aloneNothing is drawn without a color sideTUKI0001by designWrite the color side under the same name
id.ShadowHow shadows land depends on how other objects are drawnCS1061by designUse a hand-written shader
SyntaxTypesContents
id.UVVector2Position on the surface (0 to 1). The value set on the mesh arrives as is
id.NormalVector3Direction the surface faces (world space, length 1). It keeps the original direction even when vertices are moved
id.ViewDirVector3Direction from that pixel toward the viewer (world space, length 1)
v.PositionVector3Position of the vertex (mesh space). The return value is in the same space
v.UVVector2Position on the surface (0 to 1)

SurfaceId and VertexId have these 5 members; any other name is CS1061.

The color you return becomes that pixel.

using UnityEngine;
using Tsukimi;
public class ParSurfaceTint : TsukimiBehaviour
{
public Renderer display;
public float gain;
private GpuBuffer2D heat;
[Kernel]
static Color4 Step(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R * 0.99f, 0f, 0f, 1f);
}
[Surface]
static Color4 Tint(SurfaceId id, GpuBuffer2D buf, float gain)
{
float t = buf.Sample(id.UV).R * gain;
return new Color4(t, t * t, 0f, 1f);
}
void Start()
{
heat = Gpu.Buffer(64, 64);
}
void Update()
{
Gpu.Show(nameof(Tint), display, heat, gain);
}
}

You choose the light direction yourself; lights placed in the scene are not readable.

using UnityEngine;
using Tsukimi;
public class ParSurfaceShading : TsukimiBehaviour
{
public Renderer display;
private GpuBuffer2D buf;
[Surface]
static Color4 Lit(SurfaceId id, GpuBuffer2D buf)
{
Vector3 toLight = new Vector3(0.3f, 1f, 0.2f).normalized;
float lit = Mathf.Clamp01(Vector3.Dot(id.Normal, toLight) * 0.5f + 0.5f);
Color4 albedo = buf.Sample(id.UV);
return albedo * lit;
}
void Start()
{
buf = Gpu.Buffer(32, 32);
}
void Update()
{
Gpu.Show(nameof(Lit), display, buf);
}
}

Useful for effects that strengthen at grazing angles.

using UnityEngine;
using Tsukimi;
public class ParSurfaceRim : TsukimiBehaviour
{
public Renderer display;
public Vector4 glow;
private GpuBuffer2D mist;
[Surface]
static Color4 Fogged(SurfaceId id, GpuBuffer2D mist, Vector4 glow)
{
float m = mist.Sample(id.UV).R;
float rim = 1f - Mathf.Abs(Vector3.Dot(id.Normal, id.ViewDir));
return Color4.Lerp(new Color4(m, m, m, 1f), new Color4(glow), rim);
}
void Start()
{
mist = Gpu.Buffer(64, 64);
}
void Update()
{
Gpu.Show(nameof(Fogged), display, mist, glow);
}
}

Besides GpuBuffer2D and float, parameters can be int bool Vector2 Vector3 Vector4.

using UnityEngine;
using Tsukimi;
public class ParSurfaceValueArgs : TsukimiBehaviour
{
public Renderer display;
public int bands;
public bool invert;
public Vector2 shift;
public Vector3 tint;
private GpuBuffer2D buf;
// Besides GpuBuffer2D and float, parameters can be int, bool, Vector2, Vector3, or Vector4
[Surface]
static Color4 Banded(SurfaceId id, GpuBuffer2D buf, int bands, bool invert, Vector2 shift, Vector3 tint)
{
float v = buf.Sample(id.UV + shift).R;
float stepped = Mathf.Floor(v * bands) / 8f;
float t = invert ? 1f - stepped : stepped;
return new Color4(t * tint.x, t * tint.y, t * tint.z, 1f);
}
void Start()
{
buf = Gpu.Buffer(64, 64);
}
void Update()
{
Gpu.Show(nameof(Banded), display, buf, bands, invert, shift, tint);
}
}

Writing the color side under the same name makes them one shader.

using UnityEngine;
using Tsukimi;
public class ParSurfaceVertex : TsukimiBehaviour
{
public Renderer display;
public float amplitude;
private GpuBuffer2D height;
static float ToHeight(Color4 c, float amplitude)
{
return (c.R - 0.5f) * amplitude;
}
[Kernel]
static Color4 Wave(KernelId id, GpuBuffer2D prev)
{
return new Color4(prev[id].R, 0.5f, 0.5f, 1f);
}
[Surface]
static Vector3 Surf(VertexId v, GpuBuffer2D height, float amplitude)
{
return v.Position + new Vector3(0f, ToHeight(height.Sample(v.UV), amplitude), 0f);
}
[Surface]
static Color4 Surf(SurfaceId id, GpuBuffer2D height, float amplitude)
{
float d = 1f / height.width;
float hx = ToHeight(height.Sample(id.UV + new Vector2(d, 0f)), amplitude)
- ToHeight(height.Sample(id.UV - new Vector2(d, 0f)), amplitude);
float hy = ToHeight(height.Sample(id.UV + new Vector2(0f, d)), amplitude)
- ToHeight(height.Sample(id.UV - new Vector2(0f, d)), amplitude);
Vector3 n = new Vector3(-hx, 0.2f, -hy).normalized;
float lit = Mathf.Clamp01(Vector3.Dot(n, new Vector3(0.3f, 1f, 0.2f).normalized) * 0.5f + 0.5f);
return new Color4(0.2f * lit, 0.5f * lit, 0.8f * lit, 1f);
}
void Start()
{
height = Gpu.Buffer(128, 128);
}
void Update()
{
Gpu.Show(nameof(Surf), display, height, amplitude);
}
}