Relief Mapping (ピクセルシェーダによる単純なレイトレーシング)
数年前, 学生の頃にOpenGL2.0とNVidia Cgで実装したRelief Mappingの画像です.
- ピクセルシェーダで単純なレイトレーシングを行って, ポリゴン上に幾何形状をリアルタイムに描画します.
- 今回のリリーフマッピング用に使用したテクスチャデータは ディフューズアルベドテクスチャ(RGB), 法線マップテクスチャ(RGB), ハイトマップテクスチャ(R)です.
- 実装の際に参考にした記事は, ShaderX4の"Rendering Surface Detials in Games with Relief Mapping Using a Minimally Invasive Approach"です.
一応,ソースコードも貼り付けておきます.
// 頂点シェーダ (NVidia Cg) struct VertexInput { float4 position : POSITION; float2 texcoord : TEXCOORD0; }; struct VertexOutput { float4 position : POSITION; float2 texcoord : TEXCOORD0; float3 object_position : TEXCOORD1; float4 color : COLOR; }; VertexOutput main(VertexInput vertex_input, uniform float4 color) { VertexOutput vertex_output; // position vertex_output.position = mul(glstate.matrix.mvp, vertex_input.position); // object_position vertex_output.object_position = vertex_input.position.xyz; // texcoord vertex_output.texcoord = vertex_input.texcoord; return vertex_output; }
フラグメントシェーダ (NVidia Cg) struct FragmentInput { float2 texcoord : TEXCOORD0; float3 object_position : TEXCOORD1; float4 color : COLOR; }; struct FragmentOutput { float4 color : COLOR; }; #define RELIEF_LINEAR_SEARCH_STEP 10 #define RELIEF_BINARY_SEARCH_STEP 6 void rayIntersectLinear(sampler2D relief_texture, inout float3 tex_vec, inout float3 eye_vec) { eye_vec /= RELIEF_LINEAR_SEARCH_STEP; for (int i = 0; i < RELIEF_LINEAR_SEARCH_STEP - 1; i++) { float4 relief_vec4 = tex2D(relief_texture, tex_vec.xy); if ( tex_vec.z < relief_vec4.w) tex_vec += eye_vec; } } void rayIntersectBinary(sampler2D relief_texture, inout float3 tex_vec, inout float3 eye_vec) { for (int i = 0; i < RELIEF_BINARY_SEARCH_STEP; i++) { eye_vec *= 0.5; float4 relief_vec4 = tex2D(relief_texture, tex_vec.xy); if ( tex_vec.z < relief_vec4.w) tex_vec += 2 * eye_vec; tex_vec -= eye_vec; } } FragmentOutput main(FragmentInput fragment_input, uniform float3 eye_position, uniform float3 tbn_tangent, uniform float3 tbn_binormal, uniform float3 tbn_normal, uniform float object_x_unit, uniform float object_y_unit, uniform float relief_depth, uniform float tile_scale, uniform float use_texture, uniform sampler2D texture, uniform float use_relief_texture, uniform sampler2D relief_texture) { FragmentOutput fragment_output; fragment_output.color = float4(1, 1, 1, 1); float3 object_position_eye_vec = fragment_input.object_position - eye_position; float3 n_object_position_eye_vec = normalize(object_position_eye_vec); float cos_z_object_position_eye = dot(- n_object_position_eye_vec, tbn_normal); // eye_vec in TBN space float3 n_eye_tbn_vec = normalize( float3( dot(n_object_position_eye_vec, tbn_tangent), dot(n_object_position_eye_vec, tbn_binormal), cos_z_object_position_eye) ); // scale depth float3 scale = float3(object_x_unit, object_y_unit, relief_depth) / tile_scale; n_eye_tbn_vec *= scale.z / (scale * n_eye_tbn_vec.z); // search tex_vec by raycast float3 tex_vec = float3(fragment_input.texcoord * tile_scale, 0); rayIntersectLinear(relief_texture, tex_vec, n_eye_tbn_vec); rayIntersectBinary(relief_texture, tex_vec, n_eye_tbn_vec); // expand normal to object_space float3 object_normal = tex2D(relief_texture, tex_vec.xy).xyz - 0.5; object_normal = normalize(object_normal.x * tbn_tangent.xyz + object_normal.y * tbn_binormal.xyz + object_normal.z * tbn_normal.xyz); float3 new_object_position = fragment_input.object_position + n_object_position_eye_vec * tex_vec.z * relief_depth / cos_z_object_position_eye; // color fragment_output.color = tex2D(texture, tex_vec.xy); // diffuse, specular float4 diffuse_sum = float4(0, 0, 0, 0); float4 specular_sum = float4(0, 0, 0, 0); for (int i = 0; i < LIGHT_NUM; i++) { float4 tmp_diffuse; float4 tmp_specular; computeLight(glstate.light[i].position, glstate.light[i].diffuse, glstate.light[i].specular, new_object_position, object_normal, eye_position, 1.0, tmp_diffuse, tmp_specular); diffuse_sum += tmp_diffuse; specular_sum += tmp_specular; diffuse_sum = saturate(diffuse_sum); specular_sum = saturate(specular_sum); } fragment_output.color *= (diffuse_sum + specular_sum); return fragment_output; }