I have a simple class to generate a series of quads facing the camera to test volumetric rendering shaders (see (1)).
With a normal pixel shader, this works fine.
However, with a surface shader, it doesn’t seem to work at all; it only seems to find the pixels from existing geometry in the scene, not from the procedurally created meshes.
I’ve had a good read of the #pragma directives in Unity - Manual: Writing Surface Shaders, but I have no idea what I’m doing wrong here.
Anyone know what might be causing this?
It seems specifically to be related to the alpha somehow; removing the alpha pragma makes the slices render correctly, but obviously, without the alpha to make them transparent.
Here’s what the normal shader (see below (2)) renders:
And here’s what the surface shader (see below(3)) renders (notice that it render correctly in the scene view, just not the camera view):
Here are the two shaders, (2) and (3):
Shader "Shaders/Volume/SphereVolume" {
Properties {
_center ("Center", Vector) = (0, 0, 0, 0)
_size ("Size", Float) = 0
}
SubShader {
Tags {"Queue"="Transparent"}
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
uniform float3 _center;
uniform float _size;
struct vertexInput {
float4 world : POSITION;
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
};
struct fragmentInput{
float4 position : SV_POSITION;
float4 world : TEXCOORD1;
};
fragmentInput vert(vertexInput i){
fragmentInput o;
o.position = mul(UNITY_MATRIX_MVP, i.vertex);
o.world = mul(_Object2World, i.vertex);
return o;
}
float4 frag(fragmentInput i) : COLOR {
// Calculate the relative distance from the center point
float x = (i.world[0] - _center[0]) * (i.world[0] - _center[0]);
float y = (i.world[1] - _center[1]) * (i.world[1] - _center[1]);
float z = (i.world[2] - _center[2]) * (i.world[2] - _center[1]);
float delta = sqrt(x + y + z);
float inner = 0f;
if (delta < _size) {
inner = 1f - delta / _size;
}
return float4(1f, 0f, 0f, inner);
}
ENDCG
}
}
}
and:
Shader "Shaders/Volume/SphereVolumeMaterial" {
Properties {
_center ("Center", Vector) = (0, 0, 0, 0)
_size ("Size", Float) = 0
}
SubShader {
Cull Off
CGPROGRAM
#pragma surface surf Lambert alpha keepalpha
uniform float3 _center;
uniform float _size;
struct Input {
float3 worldPos;
};
// Distance between two points
float distance(float3 a, float3 b) {
float x = (a[0] - b[0]) * (a[0] - b[0]);
float y = (a[1] - b[1]) * (a[1] - b[1]);
float z = (a[2] - b[2]) * (a[2] - b[2]);
return sqrt(x + y + z);
}
// See if a value is bounded inside 'magic volume'
// In this case; a sphere.
bool bounded(float3 a) {
return distance(a, _center) < _size;
}
// Calculate the relative distance from the center point
void surf (Input IN, inout SurfaceOutput o) {
if (bounded(IN.worldPos)) {
float partial = (_size - distance(IN.worldPos, _center)) / _size;
o.Albedo = fixed3(partial, partial, partial);
}
}
ENDCG
}
Fallback "Diffuse"
}
and finally, here’s the code generating the mesh, which seems fine, but hey, maybe there’s something weird up with it… (1):
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Shaders.Volume {
/// Generate a series of parallel planes relative to the camera
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class SimpleVolumeRenderer : MonoBehaviour {
[Tooltip("The camera to make this volume viewable from")]
public new UnityEngine.Camera camera;
[Tooltip("The size of the frame to generate for this object")]
public float size = 1f;
[Tooltip("The number of slices to mesh")]
public int slices = 10;
private int slices_;
[Tooltip("The slice interval")]
public float sliceGap = 0.1f;
/// Internal vertex buffer
private Vector3[] points;
public void Start() {
Build();
}
public void Update() {
if (slices_ != slices) {
Build();
}
Rebuild();
}
/// Generate a quad facing the camera at the given offset
private void BuildQuad(Vector3 origin, Vector3 up, Vector3 right, int offset) {
points[offset * 4 + 0] = origin + right - up;
points[offset * 4 + 1] = origin + right + up;
points[offset * 4 + 2] = origin - right + up;
points[offset * 4 + 3] = origin - right - up;
}
/// Generate new points for each quad
public Vector3[] MeshPoints() {
if ((points == null) || (points.Length != 4 * slices)) {
points = new Vector3[4 * slices];
}
var offset = size / 2f;
var origin = gameObject.transform.position;
var normal = (-1f * camera.transform.forward).normalized;
var up = camera.transform.up.normalized * offset;
var right = Vector3.Cross(normal, up).normalized * offset;
for (var i = 0; i < slices; ++i) {
var o = i - slices / 2;
var src = origin - sliceGap * o * normal;
BuildQuad(src, up, right, i);
}
return points;
}
/// Rebuild vertex points only
public void Rebuild() {
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
meshFilter.mesh.vertices = MeshPoints();
}
/// Build all details of the mesh
public void Build() {
if (camera == null) {
return;
}
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
// Generate a set of meshes
var mesh = new Mesh();
mesh.Clear();
var verts = MeshPoints();
mesh.vertices = verts;
// Full quads for the shader to use
var uvs = new Vector2[4 * slices];
for (var i = 0; i < slices; ++i) {
uvs[i * 4 + 0] = new Vector2(0f, 0f);
uvs[i * 4 + 1] = new Vector2(1f, 0f);
uvs[i * 4 + 2] = new Vector2(1f, 1f);
uvs[i * 4 + 3] = new Vector2(0f, 1f);
}
mesh.uv = uvs;
// Always aim at camera
var normal = (-1f * camera.transform.forward).normalized;
var normals = new Vector3[4 * slices];
for (var i = 0; i < slices; ++i) {
normals[i * 4 + 0] = normal;
normals[i * 4 + 1] = normal;
normals[i * 4 + 2] = normal;
normals[i * 4 + 3] = normal;
}
//mesh.normals = normals;
// A series of quads
var triangles = new int[6 * slices];
for (var i = 0; i < slices; ++i) {
triangles[i * 6 + 0] = i * 4 + 2;
triangles[i * 6 + 1] = i * 4 + 1;
triangles[i * 6 + 2] = i * 4 + 0;
triangles[i * 6 + 3] = i * 4 + 0;
triangles[i * 6 + 4] = i * 4 + 3;
triangles[i * 6 + 5] = i * 4 + 2;
}
mesh.triangles = triangles;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
mesh.Optimize();
meshFilter.mesh = mesh;
slices_ = slices;
}
}
}