我一直在尝试制作一个着色器,它可以让光线行进在没有重型显卡的设备上发生。我正在使用 unity3d,但在一个问题上停留了大约 2 周。
我现在拥有的着色器非常简单,并且可以在不同平台上提供良好的 fps。它甚至在 WebGL 中运行得非常快(这一直是一场噩梦),但最后一个难题是允许用户选择不同的光线行进形状。
我一辈子都想不通为什么,第 94 行的简单开关在 WebGL 中不起作用。
我试过了:
- 将其重写为 if/else 语句
- 三元运算符
- 试图通过线性逻辑运算符确定正确的符号距离
- 基于比较的数学,以避免与条件分支。
我现在很想使用诸如 step 或 lerp 之类的函数的数学版本,但它们的实现有时也涉及似乎分支着色器的条件或逻辑运算符。WebGL 只是忽略我尝试的所有内容,它只会在 Web 控制台中打印(错误:制服太多)。
就好像它总是知道我在做什么(◕︵◕)
我也在尝试安装其他 Webgl 调试控制台,例如 spector.js,但我没有从中找到任何重要的线索。
我是着色器编程的新手,所以真的不知道调试它的最佳方法,并且想知道社区中是否有人知道出路。
Properties
{
_MainTex ("Texture", 2D) = "white" { }
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "SignedDistanceFunctions.cginc"
sampler2D _MainTex;
float4x4 _FrustrumCornersES;
float4 _TexelSize;
float4x4 _CameraInvViewMatrix;
float _Scale;
float _Size;
float4 _LightDir;
sampler2D _CameraDepthTexture;
float _SphereScale;
float _SmoothUnion;
//Shape Arrays
int _ShapeCount;
float4 shapePosition[256];
float4x4 shapeToLocal[256];
float4 shapeExtent[256];
float shapeType[256];
float4 shapeColor[256];
struct appdata
{
float4 vertex: POSITION;
float2 uv: TEXCOORD0;
};
struct v2f
{
float2 uv: TEXCOORD0;
float4 vertex: SV_POSITION;
float3 ray: TEXCOORD1;
};
// How many times each ray is marched
// Higher values give higher resolution
// (and potentially longer draw distances)
// but lower performance
static const int maxSteps = 60;
//How close does a ray have to get to be consider a hit
//Higher values give a sharper definition of shape
// but lower performance
static const float epsilon = 0.03;
// The maximum distance we want a ray to be from
// the nearest surface before giving up
//Higher values give a longer draw distance but
// lower performance
static const float maxDist = 10;
// Utility function to find out
// when two values are equal
// Return 1 if equal, 0 if not.
float when_eq(float x, float y) {
return 1.0 - abs(sign(x - y));
}
//Get the specific shape of a raymarch object
float4 GetShape(float3 p, int index)
{
float3 position = mul(shapeToLocal[index], float4((p),1.0));
float3 col = float3(shapeColor[index].x,
shapeColor[index].y,
shapeColor[index].z);
float dst = 0;
switch (shapeType[index])
{
case 0:
dst = sdSphere(shapePosition[index] - p,
length(float3(shapeExtent[index].x,
shapeExtent[index].y,
shapeExtent[index].z)));
break;
case 1:
dst = sdBox(position,
float3(shapeExtent[index].x,
shapeExtent[index].y,
shapeExtent[index].z));
break;
case 2:
dst = sdTorus(position,
length(shapeExtent[index].x),
length(shapeExtent[index].z));
break;
case 3:
dst = sdCone(position,
float2(shapeExtent[index].x,
shapeExtent[index].z),
shapeExtent[index].y);
break;
case 4:
dst = sdCylinder(position,
length(float2(shapeExtent[index].x,
shapeExtent[index].z)),
shapeExtent[index].y);
break;
}
return float4(col, dst);
}
// Map out Signed distances by looping
// over all shapes we feed the shader
float4 map(float3 p)
{
float4 dist = GetShape(p,0);
[unroll(4)]
for (int i = 0; i < _ShapeCount; i++){
dist = opSmoothUnion(dist,
GetShape(p,i),
_SmoothUnion);
}
return dist;
}
//Get normals given a point
float3 calcNormal(float3 p)
{
float x = map(float3(p.x + epsilon, p.y, p.z)).w
- map(float3(p.x - epsilon, p.y, p.z)).w;
float y = map(float3(p.x, p.y + epsilon, p.z)).w
- map(float3(p.x, p.y - epsilon, p.z)).w;
float z = map(float3(p.x, p.y, p.z + epsilon)).w
- map(float3(p.x, p.y, p.z - epsilon)).w;
return normalize(float3(x,y,z));
}
// Actual Raymarching function,
// returns a color for different points
// where our rays hit objects.
fixed4 raymarch(float3 rayOrigin, float3 rayDirection, float s)
{
fixed4 col = fixed4(0, 0, 0, 0);
const int timeStep = 100;
float travelled = 0;
for (int i = 0; i < timeStep; i ++)
{
float3 position = rayOrigin + rayDirection * travelled;
float4 surf = map(position);
if (travelled > maxDist || travelled >= s)
// if (travelled >= s)
{
col = fixed4(0, 0, 0, 0);
break;
}
if(surf.w < 0.03)
{
float3 n = calcNormal(position);
col = fixed4(surf.rgb * dot(-_LightDir.xyz, n).rrr, 1);
break;
}
travelled += surf.w;
}
return col;
}
//Vertex
v2f vert(appdata v)
{
v2f o;
half index = v.vertex.z;
v.vertex.z = 0;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv.xy;
// #if UNITY_UV_STARTS_AT_TOP
// if(_TexelSize.y < 0)
// o.uv.y = 1 - o.uv.y;
// #endif
o.ray = _FrustrumCornersES[(int)index].xyz;
//Normalize on the Z axis - viewspace position
o.ray /= abs(o.ray.z);
o.ray = mul(_CameraInvViewMatrix, o.ray);
return o;
}
//Fragment
fixed4 frag(v2f i): SV_Target
{
float3 rayDir = normalize(i.ray.xyz);
float3 rayOrigin = _WorldSpaceCameraPos;
float2 duv = i.uv;
if (_TexelSize.y < 0)
duv.y = 1 - duv.y;
float depth = LinearEyeDepth(tex2D(_CameraDepthTexture, duv).r);
depth *= length(i.ray.xyz);
fixed3 col = tex2D(_MainTex, i.uv);
fixed4 add = raymarch(rayOrigin, rayDir, depth);
return (fixed4(col * (1.0 - add.w) + add.xyz * add.w, 1.0) + 0.2);
}
ENDCG
}
}
} 吨