我正在尝试模仿进行逐多边形裁剪的旧 3D 游戏。
使用简并三角形,除非视线移开相机,否则它会起作用。
这是一个动画 GIF,一个应用了该材质的球体:
绿色的面正是我所期待的,三角形已完全被剪掉。
红脸显然是错误的,它们都粘在屏幕中心了。
这是有问题的着色器:
Shader"PerPolygonClipping"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Distance ("Distance", Float) = 1.0
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
fixed4 color : COLOR;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Distance;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
const float3 world_position = mul(unity_ObjectToWorld, v.vertex).xyz;
const float distance = length(world_position - _WorldSpaceCameraPos);
float difference = _Distance - distance;
if (difference < 0)
{
o.color = fixed4(1, 0, 0, 1);
}
else if (difference > 0)
{
o.color = fixed4(0, 1, 0, 1);
}
else
{
o.color = fixed4(0, 0, 1, 1);
}
// https://gamedev.net/forums/topic/421211-clip-in-vertex-shader-in-hlsl/3805467/
o.vertex *= step(0, difference);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col *= i.color;
return col;
}
ENDCG
}
}
}
欢迎就如何解决此问题提出建议。
问题在于您尝试在每个顶点级别剪切三角形。每个顶点都会被独立检查,因此可能只有组成三角形的三个顶点之一移动到屏幕中心,从而导致您在红色三角形中看到的拉伸。为了解决这个问题,您需要裁剪整个三角形而不仅仅是顶点。
在着色器中解决此问题的最简单方法是在几何着色器中执行裁剪,因为几何着色器可以一次对整个三角形进行操作,但需要注意的是,Metal 中不支持几何着色器,并且出于性能原因,通常不推荐。
// Culling triangles in the geometry shader
// This is a pass-through geometry shader which will only output triangles
// which have a center point nearer to the camera than _Distance.
[maxvertexcount(3)]
void geom(triangle v2f v[3], inout TriangleStream<v2f> triStream)
{
// Find the center of the triangle by averaging together the 3 vertices.
const float3 center = (v[0].worldPos + v[1].worldPos + v[2].worldPos) / 3.0;
// Calculate the distance from the triangle to the camera.
const float distance = length(world_position - _WorldSpaceCameraPos);
// If the distance is below the cutoff, output all three vertices.
// Otherwise, no vertices will be output, and the triangle will not be drawn.
if (distance < _Distance)
{
triStream.Append(v[0]);
triStream.Append(v[1]);
triStream.Append(v[2]);
}
triStream.RestartStrip();
}
一个不依赖几何着色器的稍微复杂的解决方案是为网格创建另一组纹理坐标,其中每个顶点的 UVW 坐标是其连接的三角形的中心。然后,您的顶点着色器可以检查该三角形中心坐标而不是顶点位置。由于三角形的每个顶点都是相同的,因此整个三角形将被立即剪切。
这有点烦人,因为它涉及使用附加数据生成新的网格,但它允许您仅使用顶点和片段着色器来实现裁剪效果。
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
// This is an editor wizard to create new meshes with additional data.
// Put this in an "Editor" folder in your project.
public class CustomMeshWizard : ScriptableWizard
{
public Mesh mesh;
[MenuItem("Assets/Create/Custom Mesh")]
static void CreateWizard()
{
DisplayWizard< CustomMeshWizard >("Create Custom Mesh", "Create");
}
void OnWizardCreate()
{
// Note that there are much more efficient ways of doing this,
// but many of them require a lot more code.
// For simplicity's sake, use the old clunky Mesh api.
// This example script only works on meshes with one submesh.
Mesh newMesh = new Mesh();
// Vertices must be split so they don't share UVs.
// To do this, make new vertices for each triangle.
var newVerts = new List<Vector3>();
var newNorms = new List<Vector3>();
var newUVs = new List<Vector2>();
var newTriCenters = new List<Vector3>();
var newTriIndices = new List<int>();
// Iterate over existing triangles, and copy their vertices.
var oldTriIndices = mesh.triangles;
var oldVerts = mesh.vertices;
var oldNorms = mesh.normals;
var oldUVs = mesh.uv;
for (int i = 0; i < oldTriIndices.Length; i += 3)
{
var i0 = oldTriIndices[i + 0];
var i1 = oldTriIndices[i + 1];
var i2 = oldTriIndices[i + 2];
var v0 = oldVerts[i0];
var v1 = oldVerts[i1];
var v2 = oldVerts[i2];
var triCenter = (v0 + v1 + v2) / 3.0f;
newVerts.Add(v0);
newNorms.Add(oldNorms[i0]);
newUVs.Add(oldUVs[i0]);
newTriCenters.Add(triCenter);
newVerts.Add(v1);
newNorms.Add(oldNorms[i1]);
newUVs.Add(oldUVs[i1]);
newTriCenters.Add(triCenter);
newVerts.Add(v2);
newNorms.Add(oldNorms[i2]);
newUVs.Add(oldUVs[i2]);
newTriCenters.Add(triCenter);
newTriIndices.Add(i + 0);
newTriIndices.Add(i + 1);
newTriIndices.Add(i + 2);
}
newMesh.SetVertices(newVerts);
newMesh.SetNormals(newNorms);
newMesh.SetUVs(0, newUVs);
newMesh.SetUVs(1, newTriCenters);
newMesh.SetTriangles(newTriIndices, 0);
AssetDatabase.CreateAsset(newMesh, "Assets/custom mesh.asset");
Selection.activeObject = newMesh;
}
}