ThreeJS—自定義着色器

ThreeJS自定義着色器

說起着色器的學習,強烈推薦康玉之編寫的《GPU編程與CG語言之陽春白雪下里巴人》,尤其是此書的序言部分針砭時弊毫無隱晦的指出了當今學術現狀的問題,更是發出了“開天闢地,日月重光”的憤慨。着色器的編程語言的根是CG(C For Graphics),語言風格類似C語言或者說就是;在ThreeJS當中,着色器的編程風格也是類似C語言的,引擎最終會通過字符串解析將着色器解析成正常的GLSL語法從而去編譯連接。

1.引擎爲着色器提供的內置變量和屬性

所謂的內置變量和屬性就是說這些東西不用開發人員傳入到着色器當中,引擎生成最終的shader代碼的時候自動創建並且傳入,因爲這些變量和屬性都是渲染管線所使用的最最重要的數據,交給開發者的話肯定會有人算錯或者傳錯,危險性太高了。內置變量和屬性如下:

uniform mat4 modelMatrix;// M矩陣
uniform mat4 modelViewMatrix;// MV矩陣
uniform mat4 projectionMatrix;// P矩陣
uniform mat4 viewMatrix;// V矩陣
uniform mat3 normalMatrix;// 法線矩陣(注:渲染對象的頂點數據需要矩陣轉換,對象的法線也需要矩陣轉換)
uniform vec3 cameraPosition;// 相機在世界座標系下的位置
attribute vec3 position;// 頂點數據
attribute vec3 normal;// 法線數據
attribute vec2 uv;// UV數據
// 注:這些變量屬性是內置的,自動創建傳入相關值,無需干預;但是還有一些變量屬性是需要聲明“宏”來開啓,如:
#ifdef USE_COLOR
attribute vec3 color;
#endif

在編寫着色器代碼的時候,以上變量屬性就可以直接使用,並不會因爲在shader代碼中沒有聲明而出錯;經常編寫shader代碼的人肯定會發現,以上變量屬性基本都是在“頂點着色器”中使用的,所以不要用錯了位置;在“片元着色器”中也有內置變量屬性如下:

uniform mat4 viewMatrix;// V矩陣
uniform vec3 cameraPosition;// 相機在世界座標系下的位置

以下是一段自定義的頂點着色器代碼,uv、projectionMatrix 、modelViewMatrix 和position都是沒有聲明直接使用的

"varying vec2 vUv;",

"void main() {",

"	vUv = uv;",
"	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

"}"

2.仿照C語言的include寫法

寫過C語言代碼的應該都知道include是幹什麼用的—“引用(頭)文件”,在ThreeJS中自定義着色器代碼的時候,也可以使用"#include “的寫法,它會將引擎中內置的着色器代碼(或者着色器代碼片段,這個片段可能是一個光照計算函數或者其他的)引用進來,雖然是字符串層面的寫法,但是我們可以把它理解爲引用了一個“庫”類似的。
在ThreeJS源碼當中有兩個有關着色器的文件:ShaderLib和ShaderChunk,其中ShaderLib中包含了引擎內置材質的着色器對象;而ShaderChunk中包含了着色器代碼集合,其中從凡是”./ShaderChunk"中引入的都是可以在我們自定義着色代碼時使用"#include "直接引用的,引入的庫(姑且這麼叫)內的變量、宏、方法或者代碼片段等都可以在引入後使用。

var BokehShader = {
	// 自定義宏
	defines: {
		"DEPTH_PACKING": 1,
		"PERSPECTIVE_CAMERA": 1,
	},

	uniforms: {
		// 此處省略一些變量
	},

	vertexShader: [
		"varying vec2 vUv;",
		"void main() {",
		"	vUv = uv;",
		"	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
		"}"
	].join( "\n" ),

	fragmentShader: [
		"#include <common>",
		"#include <packing>",
		// 此處省略一些邏輯代碼
		"void main() {",
		// 此處省略一些邏輯代碼
		"	gl_FragColor = col / 41.0;",
		"	gl_FragColor.a = 1.0;",
		"}"
	].join( "\n" )
};

在上邊這個完整的自定義着色器的代碼框架中,"#include “和”#include "就引入了ShaderChunk中的common和packing代碼段,這兩個文件中有什麼代碼可以自行查看一下,代碼太多在此不展示了就。需要說明一點:有些引入的代碼是函數可直接調用,有些是功能比如光照等引入就可以了。

3.include引入“庫”之後組織Uniform變量

我們知道了"#include "可以引入“庫”來使用,但是某些庫中有Uniform變量該如何組裝呢?就像上面的自定義着色器代碼框架中的uniforms,它的裏面該怎麼寫變量呢?

UniformsLib.line = {
	linewidth: { value: 1 },
	resolution: { value: new Vector2( 1, 1 ) },
	dashScale: { value: 1 },
	dashSize: { value: 1 },
	gapSize: { value: 1 }
};
ShaderLib[ 'line' ] = {
	// Uniforms變量是通過UniformsLib來組裝的
	uniforms: UniformsUtils.merge( [
		UniformsLib.common,// 庫Uniforms
		UniformsLib.fog,// 庫Uniforms
		UniformsLib.line// 自定義的Uniforms
	] ),

	vertexShader:
		`
		#include <common>
		#include <color_pars_vertex>
		#include <fog_pars_vertex>
		#include <logdepthbuf_pars_vertex>
		#include <clipping_planes_pars_vertex>

		uniform float linewidth;
		uniform vec2 resolution;

		void main() {
		// 此處省略邏輯代碼
		}
		`,

	fragmentShader:
		`
		#ifdef USE_DASH
			uniform float dashSize;
			uniform float gapSize;
		#endif

		varying float vLineDistance;
		// 引入的庫
		#include <common>
		#include <color_pars_fragment>
		#include <fog_pars_fragment>
		#include <logdepthbuf_pars_fragment>
		#include <clipping_planes_pars_fragment>

		void main() {
		// 此處省略邏輯代碼
		}
		`
};

上面的着色器框架中,引入庫的Uniform變量通過UniformsLib來引入,通過UniformsUtils.merge()來合併“庫Uniforms變量”和“自定義的Uniforms變量”。那麼“宏”該如何使用呢?想想C語言中宏是如何使用的,基本就能猜個差不多了吧?

注:如文章中有任何錯誤,請一定批評勘正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章