水面渲染

    Avatar 引擎中的水面渲染,是用一种比较高效并且真实感强的方法实现的。水面的波纹模拟没有采用顶点波动方式,而是使用法向贴图来模拟。其效果可参见之前的几篇文章的图片。

    首先需要实现的是水面倒影,水面倒影的渲染需要把摄像机置于水面的另一面对称位置,然后渲染整个场景至纹理(render to texture)。一般需要使用平面裁剪将水面以下的物体裁剪掉,避免错误的倒影,但在本方法中使用了一种特殊的方法,通过修改投影矩阵将视截体的裁剪近平面与水面重合,从而将水面下的物体裁剪掉,修改后的投影矩阵也叫近斜平面裁剪投影矩阵。计算方法如下

CMatrix4& CMatrix4::ObliqueNearPlaneClipping(const CMatrix4& proj, const CPlane& clip) {
	float x = ((clip.m_fNormal[0] > 0? 1: (clip.m_fNormal[0] < 0? -1: 0)) + proj.m_fValue[8]) / proj.m_fValue[0];
	float y = ((clip.m_fNormal[1] > 0? 1: (clip.m_fNormal[1] < 0? -1: 0)) + proj.m_fValue[9]) / proj.m_fValue[5];
	float z = -1.0f;
	float w = (1.0f + proj.m_fValue[10]) / proj.m_fValue[14];
	float scale = 2.0f / (clip.m_fNormal[0] * x + clip.m_fNormal[1] * y + clip.m_fNormal[2] * z + clip.m_fDistance * w);

	// 修改投影矩阵的第三行
	m_fValue[0] = proj.m_fValue[0];
	m_fValue[1] = proj.m_fValue[1];
	m_fValue[2] = clip.m_fNormal[0] * scale;
	m_fValue[3] = proj.m_fValue[3];
	m_fValue[4] = proj.m_fValue[4];
	m_fValue[5] = proj.m_fValue[5];
	m_fValue[6] = clip.m_fNormal[1] * scale;
	m_fValue[7] = proj.m_fValue[7];
	m_fValue[8] = proj.m_fValue[8];
	m_fValue[9] = proj.m_fValue[9];
	m_fValue[10] = clip.m_fNormal[2] * scale + 1.0f;
	m_fValue[11] = proj.m_fValue[11];
	m_fValue[12] = proj.m_fValue[12];
	m_fValue[13] = proj.m_fValue[13];
	m_fValue[14] = clip.m_fDistance * scale;
	m_fValue[15] = proj.m_fValue[15];
	return *this;
}
CMatrix4 内部为列优先排列,同OpenGL


    水面的几何结构是一个平面,四个顶点两个三角形,至于如何产生波浪,秘密全在 shader 中

attribute vec4 aPosition;
attribute vec4 aNormal;
attribute vec2 aTexCoord;
attribute vec4 aColor;

uniform mat4 uProjMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
uniform float uWaveLength;
uniform vec2 uWaveMovement;
uniform vec4 uLightPos;

varying vec2 vWavesTexCoord;
varying vec3 vTexCoord;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec4 vLightPos;

void main()
{
	mat4 modelView = uViewMatrix * uModelMatrix;
	vec4 position = modelView * aPosition;
	vec4 normal = normalize(modelView * aNormal);
	vec4 finalPos = uProjMatrix * position;
	vWavesTexCoord = (aPosition.xy / uWaveLength) + uWaveMovement;
	vTexCoord.x = 0.5 * (finalPos.w + finalPos.x);
	vTexCoord.y = 0.5 * (finalPos.w + finalPos.y);
	vTexCoord.z = finalPos.w;
	vPosition = position.xyz;
	vNormal = normal.xyz;
	vLightPos = uViewMatrix * uLightPos;

	gl_Position = finalPos;
}
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif

uniform sampler2D uTexture[2];
uniform vec4 uLightColor;
uniform float uWaveHeight;
uniform vec4 uWaterColor;

varying vec2 vWavesTexCoord;
varying vec3 vTexCoord;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec4 vLightPos;

void main()
{
	vec4 normalColor = texture2D(uTexture[0], vWavesTexCoord.xy);
	vec2 waveMovement = uWaveHeight * (normalColor.xy - 0.5);

	vec2 projTexCoord = clamp((vTexCoord.xy / vTexCoord.z) + waveMovement, 0.0, 1.0);
	vec4 reflectiveColor = texture2D(uTexture[1], vec2(projTexCoord.x, -projTexCoord.y));

	vec3 vPos = normalize(vPosition);

	// 光照计算
	float specular = 0.0;
	vec3 lightVec = vLightPos.xyz;
	if (vLightPos.w != 0.0)
	{
		lightVec = normalize(lightVec - vPosition);
	}
	vec3 r = reflect(lightVec, vNormal);
	specular = pow(max(0.0, dot(r, vPos)), 64.0) * 0.5;
	vec4 color = uWaterColor * reflectiveColor + vec4(uLightColor.rgb, 1.0) * specular;

	// 反射系数
	float fresnel = 1.0 - abs(dot(vNormal, -vPos));
	color.a = uWaterColor.a * fresnel + specular;

	gl_FragColor = color;
}
在 fragment shader 中计算 fresnel 是为了实现远处的水面反射光强而透射光弱,近处的水面看上去透光强而反射光弱,这也就是菲涅尔效应。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值