2013-11-28 103 views
2

我想写一个GLSL着色器程序为每个人脸着色。我第一次尝试使用flat插值限定符激发顶点。我使用普通和位置顶点属性的平面插值,这给了我想要的固体表面的老派效果。如何使用在质心处计算的光线实现平面着色?

虽然看起来渲染正确,着色器程序实际上并没有做正确的工作:

  1. 光计算仍然是每个片段的基础上进行(在片段着色器),
  2. 位置矢量是从引发顶点,而不是三角形的质心(右?)。

是否可以应用照明式一次,到三角形的重心,然后用整个原始计算的颜色值?怎么做?

回答

2

使用一个几何着色器,其输入是一个三角形,其输出是一个三角形。从顶点着色器传递法线和位置,自己计算质心(通过平均位置),然后执行光照,将输出颜色作为输出变量传递给片段着色器,片段着色器将其读出并写入。

+0

这是个好主意!但引入额外阴影阶段对性能的影响是什么?从理论上讲,您的方法应该更快一些,因为它会根据基元计算一次照明方程。另一方面,在浏览相关的问题和答案的同时,我发现人们将几何着色视为非常缓慢的技术。也许这些只是偏见,但总是值得去问。 –

+1

扩展几何着色器(即输出比输入更多顶点的几何着色器)在整个板上都很慢。非扩展几何着色器(比如你用来做这件事的)通常至少可以接受性能,我认为在新硬件上它们总体上非常好。 请注意,除非您的照明计算非常复杂,或者您使用的是动力非常低的硬件,否则将流水线上的照明计算向上移动可能不会节省太多:成本像是像素着色器线程的实际启动。 – MikeMx7f

1

另一个简单的方法是使用屏幕空间位置的导数来计算片段着色器中的(screenspace)face normal。实施起来非常简单,甚至表现良好。

我写的这here一个例子(需要WebGL的浏览器能力):

顶点:

attribute vec3 vertex; 

uniform mat4 _mvProj; 
uniform mat4 _mv; 

varying vec3 fragVertexEc; 

void main(void) { 
    gl_Position = _mvProj * vec4(vertex, 1.0); 
    fragVertexEc = (_mv * vec4(vertex, 1.0)).xyz; 
} 

片段:

#ifdef GL_ES 
precision highp float; 
#endif 

#extension GL_OES_standard_derivatives : enable 

varying vec3 fragVertexEc; 

const vec3 lightPosEc = vec3(0,0,10); 
const vec3 lightColor = vec3(1.0,1.0,1.0); 

void main() 
{ 
    vec3 X = dFdx(fragVertexEc); 
    vec3 Y = dFdy(fragVertexEc); 
    vec3 normal=normalize(cross(X,Y)); 

    vec3 lightDirection = normalize(lightPosEc - fragVertexEc); 

    float light = max(0.0, dot(lightDirection, normal)); 

    gl_FragColor = vec4(normal, 1.0); 
    gl_FragColor = vec4(lightColor * light, 1.0); 
} 
+0

请注意,在这种情况下,位置是按照片段计算的,而不是在质心处计算的。这意味着对于非定向灯或投影灯来说,三角形的照明不会相同。 – MikeMx7f

+0

感谢您的示例!但是'dFdx'和'dFdy'的含义是什么?我应该如何解释他们的输出值?根据我最初的研究,他们在四个(右)?片段着色器线程之间运行,并对属于两个不同坐标系(3D _view space_和2D _viewport space_,对吗?)的向量进行操作。但他们是什么意思?如何“可视化”背后的概念? –

+0

在这种用法中,您可以将dFdx和dFdy视为斜率或当前三角形的切线。在更抽象的术语中,dFdx和dYdy告诉你当一个像素水平或垂直移动时给定变量会发生什么。或者用更多的数学术语来描述变量相对于x或y方向的导数。 – Mortennobel