Codea
是 iPad
上的一款很方便的開發軟件, 尤爲是它支持 OpenGL ES 2.0/3.0
, 支持着色器 shader
, 能夠直接寫代碼操縱 GPU
. 不過也有不太方便的地方, 那就是在 Codea
上寫 OpenGL ES 2.0 Shader
代碼的時候發現跟蹤 shader
內部使用的變量特別困難, 由於 GPU
就像一個黑洞, 程序員能夠經過程序向 vertex shader
和 fragment shader
傳遞數據進去, 可是卻沒辦法把 shader
的變量值傳回來, 這樣就致使在調試 shader
代碼時看不到內部的變化, 有時候出了問題就得左右推測, 以往 打印/輸出
變量值的調試方法也失效了, 結果使得調試 shader
代碼比較困難.html
可是 shader
仍是要輸出信息的, 只不過它輸出的信息是 gl_Position
和 gl_FragColor
, 前者是一個四維向量用於設定屏幕圖像像素點座標, 後者也是一個四維向量用於設定顏色值, 而這兩個信息是沒法直接爲咱們輸出變量值的. 那麼是否能夠作一點文章, 經過一種間接的方式來傳遞咱們須要知道的信息呢?程序員
昨天晚上, 在調試一段簡單可是比較有趣的 shader
代碼時, 突然產生了一個靈感:爲何不改變一下對 shader
提問的方式? 咱們以前在調試普通程序時使用的 打印/輸出
技巧實際上等價於向計算機提出一個問題: 請告訴我那個變量的值是多少? 很顯然, shader
程序沒有辦法直接告訴咱們那個變量是多少, 那麼換一個思惟, 改爲問計算機: 請告訴我那個變量的值是否是大於100? 這下就簡單了, 由於 shader
是很容易回答這個問題的, 由於這個問題的答案要麼是 true
, 要麼是 false
.算法
咱們很容易設計一段 shader
繪圖代碼, 若是答案是 true
, 那麼咱們在指定區域揮着紅色, 若是答案是 false
, 那麼咱們在指定區域繪製綠色, 這樣 GPU
就能夠經過屏幕間接地把咱們須要的信息傳遞出來了.函數
假設要觀察的變量爲 myVar
, 想要觀察它是否大於100, shader
實現代碼以下:測試
//這段代碼放在 fragment shader 的 main 函數的最後面 void main() ... // 取得座標 float x = vTexCoord.x; float y = vTexCoord.y; // 設定調試區顯示範圍爲右上角 if(x > 0.9 && y > 0.9) { if(myVar > 100){ // 答案爲 true 則設置調試區顏色爲紅色 gl_FragColor = vec4(1,0,0,.5); }else{ // 答案爲 false 則設置調試區顏色爲紅色 gl_FragColor = vec4(1,0,0,.5); } } }
完整的 shader
代碼爲:網站
myShader = { vsBase = [[ // vertex shader 代碼 uniform mat4 modelViewProjection; uniform vec2 uResolution; attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { vColor=color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; } ]], fsBase = [[ // fragment shader 代碼 precision highp float; uniform lowp sampler2D texture; varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { lowp vec4 col = texture2D( texture, vTexCoord ) * vColor; // 默認所有設置爲白色 gl_FragColor = vec4(1,1,1,1); // 測試變量 myVar, 可分別設置爲 >100 和 <=100 兩個區間的值 int myVar = 1; // 取得座標 float x = vTexCoord.x; float y = vTexCoord.y; // 設定調試區顯示範圍爲右上角 if(x > 0.9 && x < 1.0 && y > 0.9 && y < 1.0) { if(myVar > 100){ // 答案爲 true 則設置調試區顏色爲紅色 gl_FragColor = vec4(1,0,0,1); }else { // 答案爲 false 則設置調試區顏色爲紅色 gl_FragColor = vec4(0,1,0,1); } } } ]] }
配套的 Codea
代碼爲:debug
-- Shader debug displayMode(OVERLAY) function setup() m = mesh() -- 首先得有頂點,後續要把頂點數據傳給 vertex Shader 處理程序 m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT) -- 設置 shader m.shader = shader(myShader.vsBase,myShader.fsBase) -- 要有 texture 才能設置顏色 -- m.texture = "Documents:univer" m:setColors(color(220,200,200,255)) -- 觀察 parameter.watch("m.shader.modelViewProjection") parameter.watch("m.shader.uResolution") parameter.watch("m.vertices[1]") end function draw() background(0) m:draw() end function touched(touch) end
沒有Codea
的用戶能夠在XCode
下編譯該項目, 而後在模擬器查看執行結果, XCode
項目文件下載地址XCode項目文件連接:設計
運行截圖以下:調試
myVar
大於 100
code
myVar
小於等於 100
很是好, 經過上面的試驗, 咱們終於能夠大體瞭解 shader
中變量的狀況了, 好比說它是否是大於某個數, 是否是小於某個數, 是否是正數, 是否是負數, 等等. 可是這種調試信息仍是太粗糙, 並且用起來也比較繁瑣. 那麼更進一步, 咱們仍是但願能看到變量的具體的值, 前面說過 shader
沒辦法像 printf
同樣, 直接把變量值打印到屏幕, 可是咱們知道咱們實際上能夠經過 shader
徹底控制屏幕輸出, 因此理論上咱們能夠在屏幕上繪製出任何內容, 包括數字.
如今先簡化一下問題, 假設 myVar
的取值範圍是整數 0~9
, 那麼咱們能夠設計一種對應算法, 處理邏輯是這樣的:
若是 myVar 是 1, 那麼咱們在指定的區域內把指定的像素點用指定的顏色繪製(繪製出1); 若是 myVar 是 2, 那麼咱們在指定的區域內把指定的像素點用指定的顏色繪製(繪製出2); ... 若是 myVar 是 9, 那麼咱們在指定的區域內把指定的像素點用指定的顏色繪製(繪製出9); 若是 myVar 是 0, 那麼咱們在指定的區域內把指定的像素點用指定的顏色繪製(繪製出0);
聽起來不錯, 這樣咱們就可讓 shader
輸出 1~0
10個數字了, 繼續簡化問題, 先從最簡單的地方入手, 咱們試着處理一下 1
, 暫時無論 myVar
的值, 咱們只是簡單地在屏幕上繪製一個 1
, 那麼代碼以下:
// 取得座標 float x = vTexCoord.x; float y = vTexCoord.y; // 先設置好調試區的範圍 if(x > 0.9 && x < 1.0 && y > 0.9 && y < 1.0) { // 設置正方形區域的顏色爲白色做爲背景色 gl_FragColor = vec4(1,1,1,1); // 至關於在一個正方形內繪製一個 `1`, 咱們選擇最右邊 if( x > 0.99 ){ // 最右邊設置爲綠色 gl_FragColor = vec4(0,1,0,1); } }
截圖以下:
很好, 繼續, 增長一個判斷條件 myVar是否爲1
, 不然只要執行到這個區域座標一概會繪製一個白底綠色的數字1
, 以下:
// 取得座標 float x = vTexCoord.x; float y = vTexCoord.y; // 先設置好調試區的範圍 if(x > 0.9 && x < 1.0 && y > 0.9 && y < 1.0) { // 設置正方形區域的顏色爲白色做爲背景色 gl_FragColor = vec4(1,1,1,1); // 至關於在一個正方形內繪製一個 `1`, 咱們選擇最右邊 if( myVar == 1 && x > 0.99 ){ // 最右邊設置爲綠色 gl_FragColor = vec4(0,1,0,1); } }
接着咱們能夠把 2~0
的數字所有以這種方式繪製出來, 爲了簡單起見, 數字造型所有采用相似7段數碼管的那種風格, 僞碼以下:
// 取得座標 float x = vTexCoord.x; float y = vTexCoord.y; // 先設置好調試區的範圍 if(x > 0.9 && x < 1.0 && y > 0.9 && y < 1.0) { // 設置正方形區域的顏色爲白色做爲背景色 gl_FragColor = vec4(1,1,1,1); // 至關於在一個正方形內繪製一個 `1`, 咱們選擇最右邊 if( myVar == 1 && x > 0.99 ){ // 最右邊設置爲綠色 gl_FragColor = vec4(0,1,0,1); } if( myVar == 2 && (2的繪製座標範圍) ){ // 最右邊設置爲綠色 gl_FragColor = vec4(0,1,0,1); } ... if( myVar == 0 && (0的座標繪製範圍) ){ // 最右邊設置爲綠色 gl_FragColor = vec4(0,1,0,1); } }
回頭看看代碼, 發現貌似有不少重複的地方, 稍微合併一下, 僞碼以下:
// 取得座標 float x = vTexCoord.x; float y = vTexCoord.y; // 先設置好調試區的範圍 if(x > 0.9 && x < 1.0 && y > 0.9 && y < 1.0) { // 設置正方形區域的顏色爲白色做爲背景色 gl_FragColor = vec4(1,1,1,1); // 至關於在一個正方形內繪製一個 `1`, 咱們選擇最右邊 if(( myVar == 1 && x > 0.99 ) || ( myVar == 2 && (2的繪製座標範圍)) || ... ( myVar == 0 && (0的座標繪製範圍)) ) { // 最右邊設置爲綠色 gl_FragColor = vec4(0,1,0,1); } }
基本上就是這樣樣子, 把它寫成函數形式, 代碼以下:
// 構造數字 void ledChar(int n, float xa,float xb, float ya, float yb){ float x = vTexCoord.x; float y = vTexCoord.y; float x1 = xa; float x2 = xa+xb; float y1 = ya; float y2 = ya+yb; float ox = (x2+x1)/2.0; float oy = (y2+y1)/2.0; float dx = (x2-x1)/10.0; float dy = (y2-y1)/10.0; float b = (x2-x1)/20.0; int num = n; // 設定調試區顯示範圍 if(x >= x1 && x <= x2 && y >= y1 && y <= y2) { // 設置調試區背景色的半透明的藍色 gl_FragColor = vec4(0,0,1,.5); // 分別繪製出 LED 形式的數字 1~0 if((num==1 && (x > x2-dx)) || (num==2 && ((y > y2-dy) || (x > x2-dx && y > oy-dy/2.0) || (y > oy-dy/2.0 && y < oy+dy/2.0) || (x < x1+dx && y < oy+dy/2.0) || (y < y1+dy))) || (num==3 && ((y > y2-dy) || (x > x2-dx) || (y > oy-dy/2.0 && y < oy+dy/2.0) || (y < y1+dy))) || (num==4 && ((x < x1+dx && y > oy-dy/2.0) ||(x > x2-dx) || (y > oy-dy/2.0 && y < oy+dy/2.0))) || (num==5 && ((y > y2-dy) || (x < x1+dx && y > oy-dy/2.0)|| (y > oy-dy/2.0 && y < oy+dy/2.0) || (x>x2-dx && y <oy-dy/2.0) || (y<y1+dy))) || (num==6 && ((y > y2-dy) || (x < x1+dx)|| (y > oy-dy/2.0 && y < oy+dy/2.0) || (x>x2-dx && y <oy-dy/2.0) || (y<y1+dy))) || (num==7 && ((y > y2-dy) || (x > x2-dx))) || (num==8 && ((y > y2-dy) || (x < x1+dx)|| (y > oy-dy/2.0 && y < oy+dy/2.0) || (x>x2-dx) || (y<y1+dy))) || (num==9 && ((y > y2-dy) || (x < x1+dx && y > oy-dy/2.0)||(y > oy-dy/2.0 && y < oy+dy/2.0)|| (x>x2-dx) || (y<y1+dy))) || (num==0 && ((y > y2-dy) || (x < x1+dx) || (x>x2-dx) || (y<y1+dy))) ) { // 設置數字顏色爲綠色 gl_FragColor = vec4(0,1,0,1); } } }
完整的 shader
代碼以下:
myShader = { vsBase = [[ // vertex shader 代碼 uniform mat4 modelViewProjection; uniform vec2 uResolution; attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { vColor=color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; } ]], fsBase = [[ // fragment shader 代碼 precision highp float; uniform lowp sampler2D texture; varying lowp vec4 vColor; varying highp vec2 vTexCoord; void ledChar(int,float,float,float,float); void main() { lowp vec4 col = texture2D( texture, vTexCoord ) * vColor; // 默認所有設置爲黑色 gl_FragColor = vec4(.1,.1,.1,1); // 在右上角顯示1 ledChar(1, 0.9, 0.1, 0.9, 0.1); } // 構造數字 void ledChar(int n, float xa,float xb, float ya, float yb){ float x = vTexCoord.x; float y = vTexCoord.y; float x1 = xa; float x2 = xa+xb; float y1 = ya; float y2 = ya+yb; float ox = (x2+x1)/2.0; float oy = (y2+y1)/2.0; float dx = (x2-x1)/10.0; float dy = (y2-y1)/10.0; float b = (x2-x1)/20.0; int num = n; // 設定調試區顯示範圍 if(x >= x1 && x <= x2 && y >= y1 && y <= y2) { // 設置調試區背景色爲半透明的藍色 gl_FragColor = vec4(0.2,0.8,0.2,.5); // 分別繪製出 LED 形式的數字 1~0 if((num==1 && (x > x2-dx)) || (num==2 && ((y > y2-dy) || (x > x2-dx && y > oy-dy/2.0) || (y > oy-dy/2.0 && y < oy+dy/2.0) || (x < x1+dx && y < oy+dy/2.0) || (y < y1+dy))) || (num==3 && ((y > y2-dy) || (x > x2-dx) || (y > oy-dy/2.0 && y < oy+dy/2.0) || (y < y1+dy))) || (num==4 && ((x < x1+dx && y > oy-dy/2.0) ||(x > x2-dx) || (y > oy-dy/2.0 && y < oy+dy/2.0))) || (num==5 && ((y > y2-dy) || (x < x1+dx && y > oy-dy/2.0)|| (y > oy-dy/2.0 && y < oy+dy/2.0) || (x>x2-dx && y <oy-dy/2.0) || (y<y1+dy))) || (num==6 && ((y > y2-dy) || (x < x1+dx)|| (y > oy-dy/2.0 && y < oy+dy/2.0) || (x>x2-dx && y <oy-dy/2.0) || (y<y1+dy))) || (num==7 && ((y > y2-dy) || (x > x2-dx))) || (num==8 && ((y > y2-dy) || (x < x1+dx)|| (y > oy-dy/2.0 && y < oy+dy/2.0) || (x>x2-dx) || (y<y1+dy))) || (num==9 && ((y > y2-dy) || (x < x1+dx && y > oy-dy/2.0)||(y > oy-dy/2.0 && y < oy+dy/2.0)|| (x>x2-dx) || (y<y1+dy))) || (num==0 && ((y > y2-dy) || (x < x1+dx) || (x>x2-dx) || (y<y1+dy))) ) { // 設置數字顏色爲綠色 gl_FragColor = vec4(0,1,0,1); } } } ]] }
運行截圖以下:
4
調試區設置爲右上角
2
調試區設置爲全屏
5
調試區設置爲全屏
理論上來講, 有了上面這些基礎, 咱們就能夠自由地經過 shader
輸出要觀察變量的值了, 不過貌似有一個 bug
: 重複運行 ledChar
函數會致使花屏, 暫時還沒搞清楚問題緣由.
在顯示數字的方向上, 今天還想到幾種得到數字字型的辦法, 一種是經過多個 vec4
或 mat4
來傳, 另外一種是經過 texture
來傳, 大體考慮了下, 感受仍是經過 texture
傳比較簡單, 由於它是直接傳圖, 不須要再本身算像素點座標了, 這個想法也準備後續試驗一下.
另外, 今天在有了這個思路以後, 去搜索了一下 shader debug
, 結果在 StackOverflow
網站發現也有人問一樣的問題, 而後有人給出了本文提到的第一種簡單方法, 還有人提出一種頗有想象力的方案, 那就是把要觀察的數據可視化, 直接用圖像來表達數字, 看着挺有趣, 點擊網頁連接, 準備後面也試試.
通過一番調試, 終於把剛剛能用的原型搞出來了:
OpenGL ES 2.0 Shader 調試新思路(二): 作一個可用的原型