文档章节

如何把一个标准 GLSL 例程改写为 Codea shader

FreeBlues
 FreeBlues
发布于 2016/07/17 11:38
字数 1752
阅读 26
收藏 1

如何把一个标准 GLSL 例程改写为 Codea shader

概述

这里所说的标准 GLSL 例程指的是 OpenGL ES 2.0/3.0 中使用 GLSL 编写, 由 顶点着色器片段着色器 组成的 shader. 在 <OpegGL ES 3.0 编程指南> 中有很多优秀的 shader 例程, 它们一般通过 C 程序 来调用, 我们现在希望把它们改写为 Codea shader, 也就是准备用 Lua 来调用, 以一个 法线贴图 的例程为例说明改写过程细节.

法线贴图例程

顶点着色器代码

下面是顶点着色器原始代码:

uniform mat4 u_matViewInverse;
uniform mat4 u_matViewProjection;
uniform vec3 u_lightPosition;
uniform vec3 u_eyePosition;

varying vec2 v_texcoord
varying vec3 v_viewDirection
varying vec3 v_lightDirection

attribute vec4 a_vertex; 
attribute vec2 a_texcoord0; 
attribute vec3 a_normal; 
attribute vec3 a_binormal; 
attribute vec3 a_tangent;

void main(void) {
// Transform eye vector into world space 
vec3 eyePositionWorld =
(u_matViewInverse * vec4(u_eyePosition, 1.0)).xyz;

// Compute world space direction vector
vec3 viewDirectionWorld = eyePositionWorld - a_vertex.xyz;

// Transform light position into world space vec3 lightPositionWorld =
(u_matViewInverse * vec4(u_lightPosition, 1.0)).xyz;

// Compute world space light direction vector
vec3 lightDirectionWorld = lightPositionWorld - a_vertex.xyz;

// Create the tangent matrix 
mat3 tangentMat = mat3(a_tangent,
                          a_binormal,
                          a_normal);
                          
// Transform the view and light vectors into tangent space 
v_viewDirection = viewDirectionWorld * tangentMat; 
v_lightDirection = lightDirectionWorld * tangentMat;

// Transform output position
gl_Position = u_matViewProjection * a_vertex;
// Pass through texture coordinate 
v_texcoord = a_texcoord0.xy;
}

我们按照代码顺序来一行行地进行转换, 首先变量声明, 有 4 个统一变量 uniform, 有 3 传给片段着色器的变量 varying, 有 5 个来自主程序的顶点属性 attribute.

我们需要处理的就是找到 uniformattributeCodea 中的对应设置, 依次进行:

统一变量的对应设置

4个统一变量如下:

uniform mat4 u_matViewInverse;
uniform mat4 u_matViewProjection;
uniform vec3 u_lightPosition;
uniform vec3 u_eyePosition;

按照变量名的含义:

  • u_matViewInverse 应该是 viewMatrix 的逆矩阵;
  • u_matViewProjection 应该是 viewMatrixprojectionMatrix 的相乘;
  • u_lightPosition 应该是光源位置, 由我们自行设置;
  • u_eyePosition 应该是摄像机位置, 可以通过 camera 来设置.

视图矩阵的逆矩阵, 在书中描述是为了把 u_lightPositionu_eyePosition 从视图空间转换到世界空间, 那么视图矩阵就是从世界空间转换到视图空间了?

属性的对应设置

Codea 中, 有一些约定俗成的设置, 比如顶点的属性 attribute, 就有一些预先定义好的:

  • position 顶点坐标
  • texCoord 顶点的纹理坐标
  • color 顶点的颜色
  • normal 顶点的法线

在上面的顶点着色器代码中, 我们看到用了这么几个属性:

attribute vec4 a_vertex; 
attribute vec2 a_texcoord0; 
attribute vec3 a_normal; 
attribute vec3 a_binormal; 
attribute vec3 a_tangent;

很明显, 对应关系如下:

  • a_vertexposition 一样;
  • a_texcoord0texCoord 一样;
  • a_normalnormal 一样.

还有两个属性 a_binormal 次法线a_tangent 切线, 在 Codea 中没有对应的属性, 就需要我们自己计算了, 这部分可以在 Lua 主程序中计算, 也可以在 shader 中计算. 如果在 Lua 中计算, 那么它们的声明可以保持不变, 如果在 shader 中计算, 就不能声明为 attribute 了.

原本计划为减少调试难度, 我们决定先在 Lua 中计算, 确认调试通过了, 再改写为 shader 计算.

后来看到这篇教程 Mesh Deformers with the GLSL, 给出了在 shader 中根据 normal 属性计算 binormaltangent 的算法, 所以我们就直接引用一下:

	vec3 tangent; 
	vec3 binormal; 
	
	vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0)); 
	vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0)); 
	
	if(length(c1)>length(c2))
	{
		tangent = c1;	
	}
	else
	{
		tangent = c2;	
	}
	
	// 归一化切线
	tangent = normalize(tangent);
	
	binormal = cross(gl_Normal, tangent); 
	binormal = normalize(binormal);

所以, 最终得到的顶点着色器代码为:

uniform mat4 u_matViewInverse;
uniform mat4 u_matViewProjection;
uniform vec3 u_lightPosition;
uniform vec3 u_eyePosition;

varying vec2 v_texcoord
varying vec3 v_viewDirection
varying vec3 v_lightDirection

attribute vec4 a_vertex; 
attribute vec2 a_texcoord0; 
attribute vec3 a_normal; 
// attribute vec3 a_binormal; 
// attribute vec3 a_tangent;

void main(void) {

	// 先根据 normal 计算 binormal 和 tangent
	vec3 tangent; 
	vec3 binormal; 
	
	vec3 c1 = cross(normal, vec3(0.0, 0.0, 1.0)); 
	vec3 c2 = cross(normal, vec3(0.0, 1.0, 0.0)); 
	
	if(length(c1)>length(c2))
	{
		tangent = c1;	
	}
	else
	{
		tangent = c2;	
	}
	
	// 归一化切线和次法线
	tangent = normalize(tangent);
	binormal = cross(normal, tangent); 
	binormal = normalize(binormal);

	// 坐标空间转换: 摄像机坐标 Transform eye vector into world space 
	vec3 eyePositionWorld =
		(u_matViewInverse * vec4(u_eyePosition, 1.0)).xyz;

	// Compute world space direction vector
	vec3 viewDirectionWorld = eyePositionWorld - a_vertex.xyz;

	// Transform light position into world space 
	vec3 lightPositionWorld =
		(u_matViewInverse * vec4(u_lightPosition, 1.0)).xyz;

	// Compute world space light direction vector
	vec3 lightDirectionWorld = lightPositionWorld - a_vertex.xyz;

	// Create the tangent matrix 
	mat3 tangentMat = mat3(a_tangent,
                          a_binormal,
                          a_normal);
                          
	// Transform the view and light vectors into tangent space 
	v_viewDirection = viewDirectionWorld * tangentMat; 
	v_lightDirection = lightDirectionWorld * tangentMat;

	// Transform output position
	gl_Position = u_matViewProjection * a_vertex;
	// Pass through texture coordinate 
	v_texcoord = a_texcoord0.xy;
}

片段着色器代码

下面是片段着色器原始代码:

precision mediump float;
uniform vec4 u_ambient; 
uniform vec4 u_specular; 
uniform vec4 u_diffuse; 
uniform float u_specularPower;

uniform sampler2D s_baseMap; 
uniform sampler2D s_bumpMap;

varying vec2 v_texcoord; 
varying vec3 v_viewDirection; 
varying vec3 v_lightDirection;

void main(void) {
// Fetch basemap color
vec4 baseColor = texture2D(s_baseMap, v_texcoord);

// Fetch the tangent-space normal from normal map 
vec3 normal = texture2D(s_bumpMap, v_texcoord).xyz;

// Scale and bias from [0, 1] to [-1, 1] and normalize 
normal = normalize(normal * 2.0 - 1.0);

// Normalize the light direction and view direction 
vec3 lightDirection = normalize(v_lightDirection); 
vec3 viewDirection = normalize(v_viewDirection);

// Compute N.L
float nDotL = dot(normal, lightDirection);

// Compute reflection vector
vec3 reflection = (2.0 * normal * nDotL) - lightDirection;

// Compute R.V
float rDotV = max(0.0, dot(reflection, viewDirection));

// Compute Ambient term
vec4 ambient = u_ambient * baseColor;

// Compute Diffuse term
vec4 diffuse = u_diffuse * nDotL * baseColor;

// Compute Specular term
vec4 specular = u_specular * pow(rDotV, u_specularPower);

// Output final color
gl_FragColor = ambient + diffuse + specular; 
}

这段代码不需要任何修改, 直接使用就行

Lua 主程序

我们用来加载 shaderLua 主程序如下:

function setup()
    print("normal 3D")
    tchx=0
    tchy=0
    
    createMesh()
    cam = vec3(0, 0, 1000)
    obj = vec3(0, 0, 0)
    light = vec3(tchx, tchy, 0.075)
end

function draw()
    perspective(50, WIDTH/HEIGHT)
    light = vec3(tchx, tchy, 0.0075)
    setShaderParam()
    camera(cam.x, cam.y, cam.z, obj.x, obj.y, obj.z)
    m:draw()
end

function createMesh()
    m = mesh()
    local w,h = WIDTH,HEIGHT
    local img1, img2 = readImage("Dropbox:n1"), readImage("Dropbox:n2")
    m:addRect(w/2,h/2,w,h)
    -- m:addRect(0,0,w,h)
    
    ---[[
    m.shader = shader(shader2.vs,shader2.fs)
    m.shader.u_matViewInverse = viewMatrix():inverse()
    m.shader.u_matViewProjection = viewMatrix() * projectionMatrix()
    
    m.shader.s_baseMap = img1
    m.shader.s_bumpMap = img2
    --]]
    
    --[[
    m.shader.u_lightPosition = light or vec3(100, 100, 100)
    m.shader.u_eyePosition = cam or vec3(100, 100, 100)
    -- ambient/specular/diffuse 环境光,反射光,散射
    m.shader.u_ambient = vec4(0.15, 0.15, 0.15, 0.8)
    m.shader.u_specular = vec4(0.05, 0.05, 0.05, 0.8)
    m.shader.u_diffuse = vec4(0.105, 0.005, 0.005, 0.8)
    m.shader.u_specularPower = 0.09
    --]]
    
    -- m.texture = img1       
end

function setShaderParam()
    m.shader.u_lightPosition = light or vec3(100, 100, 100)
    m.shader.u_eyePosition = cam or vec3(100, 100, 100)
    -- ambient/specular/diffuse 环境光,反射光,散射
    m.shader.u_ambient = vec4(0.37,0.37,0.37,1.0)
    m.shader.u_specular = vec4(0.5,0.5,0.5,1.0)
    m.shader.u_diffuse = vec4(0.88,0.88,0.88,1.0)
    m.shader.u_specularPower = .05
end

function touched(touch)
    if touch.state == BEGAN or touch.state == MOVING then
        tchx=touch.x+50
        tchy=touch.y+50
    end
end

问题

1 无法改变距离

发现无法通过设置 camera 函数的参数来改变视角, 找了半天, 发现是顶点着色器中这条语句的原因;

gl_Position = u_matViewProjection * a_vertex;

它把视图投影矩阵应用于顶点, 变换后得到视图投影, 而不是常用的模型视图投影, 可以将其修改为:

uniform mat4 modelViewProjection;

gl_Position = modelViewProjection * a_vertex;

这样就可以调节摄像机和物体之间的距离了.

2 上下坐标错位

具体来说就是点击上方, 本来应该把光源放在上方, 结果下方出现高光, 说明上下坐标错位, 在我们的屏幕上也就是 y 轴坐标错位, 这一点可能是因为 Codea 使用了不同手系的坐标导致, 可以在顶点着色器中通过乘一个如下的矩阵来调整:

mat4 mm = mat4( 1.0, 0.0, 0.0, 0.0,
				0.0, -1.0, 0.0, 0.0,
				0.0, 0.0, 1.0, 0.0.
				0.0, 0.0, 0.0, 1.0);
					
gl_Position = mm * modelViewProjection * a_vertex;

3 光线没有设置衰减

不设置衰减的光线会导致光源越远, 物体越亮的错误情况, 增加一个衰减系数就可以了

参考

Mesh Deformers with the GLSL

© 著作权归作者所有

FreeBlues
粉丝 98
博文 280
码字总数 493678
作品 0
其它
程序员
私信 提问
从零开始写一个武侠冒险游戏-0-开发框架Codea简介

从零开始写一个武侠冒险游戏-0-开发框架Codea简介 概述 本游戏全程使用一款运行于 上的开发工具类 -- 来开发, 是一款 + 的开发工具, 它既是一个: - 也是一个: - 还是一个: - 更是一个: - , 可...

FreeBlues
2016/06/21
69
0
如何创建一个 Lua 模块

如何创建一个 Lua 模块 翻译自: How to Create a Lua Module - 译者: FreeBlues 正文 中的一个 ()是一个包含函数和变量的代码片段: 它是一个用户库. 它是把你的代码切分为多个文件的一种有力...

FreeBlues
2016/07/23
22
0
从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)

从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) 概述 我们之前所有的绘图工作都是直接使用基本绘图函数来绘制的, 这样写出来的代码容易理解, 不过这些代码基本都是由 来执行的, 没怎么发挥出...

FreeBlues
2016/06/19
136
0
为新手准备的 Codea 着色器(Shader)教程

为新手准备的 Codea 着色器(Shader) 教程 原文标题:《Shaders for dummies》作者:Ignatz译者:FreeBlues译文链接:http://my.oschina.net/freeblues/blog/336055PDF链接:http://pan.ba...

FreeBlues
2014/10/22
0
5
用 Love2D 实现法线贴图的例程(到最新版本 0.10.1)

用 Love2D 实现法线贴图的例程(到最新版本 0.10.1) 概述 一般来说, 复杂的光照模型会被用在 游戏中, 以产生逼真的效果, 不过也有些开发者研究出一些代码可以在 游戏中使用这些光照模型, 这里...

FreeBlues
2016/07/05
108
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周二乱弹 —— 吾不好梦中插人

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @鱼豆腐233 :#今日歌曲分享# 分享My Chemical Romance的单曲《I Don't Love You》: 《I Don't Love You》- My Chemical Romance 手机党少年们...

小小编辑
今天
115
8
ss5 vpn 安装(linux版本)

1. 创建一个文件夹 /ss5 你也可以自定义,不过后续的地方需要注意自己的地址 2. 下载ss5文件(如果你的服务器没有安装wget请使用 yum -y install wget 命令安装 如果连yum都没安装自己查去)(下...

太黑_thj
今天
2
0
八、RabbitMQ的集群原理

集群架构 写在前面 RabbitMQ集群是按照低延迟环境设计的,千万不要跨越WAN或者互联网来搭建RabbitMQ集群。如果一定要在高延迟环境下使用RabbitMQ集群,可以参考使用Shovel和Federation工具。...

XuePeng77
今天
5
0
mac系统下,brew 安装mysql,用终端可以连接,navicat却连接不上?

问题: 1.报错? 2059 - Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(../Frameworks/caching_sha2_password.so, 2): image not found 2.自己通过设置,已经把密......

写bug的攻城狮
昨天
3
0
老生常谈,HashMap的死循环

问题 最近的几次面试中,我都问了是否了解HashMap在并发使用时可能发生死循环,导致cpu100%,结果让我很意外,都表示不知道有这样的问题,让我意外的是面试者的工作年限都不短。 由于HashMap...

群星纪元
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部