如何用高效的用 shader 實現柱狀圖?

我想把他作為一個材質,參數可以控制每個柱體的高低。


既然參數有上百個, 那直接用Texture進行數據的輸入吧, Shader里根據紋理坐標轉化成柱狀圖就行了. 不過問題是數據太多的話每個柱子的寬度就太窄了, 這裡用了個常量先湊合著看:

Shader "Hidden/BarChartShader"
{
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"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}

sampler2D _MainTex;
#define bars 32.0 // How many buckets to divide spectrum into
#define barSize 1.0 / bars // Constant to avoid division in main loop
#define barGap 0.1 * barSize // 0.1 represents gap on both sides, so a bar is
// shown to be 80% as wide as the spectrum it samples
#define sampleSize 0.02 // How accurately to sample spectrum, must be a factor of 1.0
// Setting this too low may crash your browser!

// Helper for intensityToColour
float h2rgb(float h)
{
if (h &< 0.0) h += 1.0; if (h &< 0.166666) return 0.1 + 4.8 * h; if (h &< 0.5) return 0.9; if (h &< 0.666666) return 0.1 + 4.8 * (0.666666 - h); return 0.1; } // Map [0, 1] to rgb using hues from [240, 0] - ie blue to red float3 intensityToColour(float i) { // Algorithm rearranged from http://www.w3.org/TR/css3-color/#hsl-color // with s = 0.8, l = 0.5 float h = 0.666666 - (i * 0.666666); return float3(h2rgb(h + 0.333333), h2rgb(h), h2rgb(h - 0.333333)); } float4 frag (v2f i) : SV_Target { float4 fragColor = tex2D(_MainTex, i.uv); // Map input coordinate to [0, 1) float2 uv = float2(i.uv.x, 1 - i.uv.y); // Get the starting x for this bar by rounding down float barStart = floor(uv.x * bars) / bars; // Discard pixels in the "gap" between bars if (uv.x - barStart &< barGap || uv.x &> barStart + barSize - barGap)
{
fragColor = (float4)0.0;
}
else
{
// Sample spectrum in bar area, keep cumulative total
float intensity = 0.0;
for (float s = 0.0; s &< barSize; s += barSize * sampleSize) { // Shader toy shows loudness at a given frequency at (f, 0) with the same value in all channels intensity += tex2D(_MainTex, float2(barStart + s, 0.0)).r; } intensity *= sampleSize; // Divide total by number of samples taken (which is 1 / sampleSize) intensity = clamp(intensity, 0.005, 1.0); // Show silent spectrum to be just poking out of the bottom // Only want to keep this pixel if it is lower (screenwise) than this bar is loud float i = float(intensity &> uv.y); // Casting a comparison to float sets i to 0.0 or 1.0

//fragColor = vec4(intensityToColour(uv.x), 1.0); // Demo of HSL function
//fragColor = vec4(i); // Black and white output
fragColor = float4(intensityToColour(intensity) * i, i); // Final output
}
// Note that i2c output is multiplied by i even though i is sent in the alpha channel
// This is because alpha is not "pre-multiplied" for fragment shaders, you need to do it yourself
return fragColor;
}
ENDCG
}
}
}

這是Unity的腳本, 用的隨機數填充:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RandomDataTexture : MonoBehaviour {

const int NumData = 256;

public Texture2D texture;

// Use this for initialization
void Start () {
texture = new Texture2D(NumData, 1, TextureFormat.RGBA32, false);
Color32[] pixelData = new Color32[NumData];
for (int i = 0; i &< pixelData.Length; ++i) { byte value = (byte)(255 * Random.Range(0.0f, 1.0f)); pixelData[i] = new Color32(value, value, value, value); } texture.SetPixels32(pixelData); texture.Apply(); GetComponent&().material.mainTexture = texture;
}
}

效果圖如下:

參考的演算法來自

Zero to GLSL Spectrum Analyser in One Hour

Shadertoy


個人認為,一般這種需求都是在ui上的。所以應該基於ui系統來進行處理,而不是基於shader。如果是ugui的話,所需的知識點就轉化為如何通過腳本控制柱狀圖頂點並通過onpopulatemesh函數傳遞給底層進行繪製。這樣無論是執行效率還是特殊情況的支持(比如裁剪區域,滑動)都會好很多。

如果確定要用shader來實現的話已經有答主闡明了。


我也來提供一種方法。通過腳本給shader傳遞數組的方式。當然也可以傳遞顏色數組來控制每個柱子的顏色,這裡編寫了一個基本的控制模板以供修改。(色控方面可以根據需求自己修改)

Shader "QQ/Histogram"
{
Properties
{
_BarColor("Bar Color",Color) = (0.0,0.8,0.0,1.0)
_BackColor("Back Color",Color) = (0.4,0.4,0.0,0.2)
_BarSpace("Bar Space",Range(0,1)) = 0.8
}
SubShader
{
Tags {
"RenderType" = "Transparent"
"Queue" = "Transparent"
}
LOD 100

Pass
{
Zwrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

#define Length 20 //定義你需要多少個數據

uniform fixed4 _BarColor;
uniform fixed4 _BackColor;
uniform float _BarSpace;
uniform float _Values[Length];
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};

v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}

fixed4 frag(v2f i) : SV_Target
{
float center = 1.0 / Length;
int index = i.uv.x / center;
fixed4 col = _BackColor;
if (abs(i.uv.x % center / center - 0.5) / 0.5 &< _BarSpace) if (i.uv.y &< _Values[index]) { //柱顏色的控制 col = _BarColor; col.rgb *= _Values[index]*2; } return col; } ENDCG } } }

下面是腳本控制代碼

public class HistogramControl : MonoBehaviour
{
//數組長度要符合shader中定義的
public float[] values;
Material mat;
void Start()
{
mat = GetComponent&().material;
}
void Update()
{
SetData(values);
}
public void SetData(float[] values)
{
mat.SetFloatArray("_Values", values);
}
}

btw:shader中只支持固定數組,請保證傳遞的數組長度和你shader中定義的數組長度相同


推薦閱讀:

哪裡有Unity3D遊戲開發的教程?
C#初學者問一個問題?
會Unity 3D的技術人才現在是否緊俏?做此類培訓的市場前景如何?
Unity 高級程序員應該具備怎樣的能力?要怎樣成長為 Unity 高級程序員?
暢遊引擎Genesis的現狀是如何?

TAG:遊戲開發 | Unity遊戲引擎 | 虛幻引擎 | gpu渲染 |