Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KHR material anisotropy #2029

Merged
merged 8 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions addons/io_scene_gltf2/blender/com/gltf2_blender_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,11 @@ def get_gltf_interpolation(interpolation):
"LINEAR": "LINEAR",
"CONSTANT": "STEP"
}.get(interpolation, "LINEAR")

def get_anisotropy_rotation_gltf_to_blender(rotation):
# glTF rotation is in randian, Blender in 0 to 1
return rotation / (2 * np.pi)

def get_anisotropy_rotation_blender_to_gltf(rotation):
# glTF rotation is in randian, Blender in 0 to 1
return rotation * (2 * np.pi)
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Copyright 2018-2022 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import bpy
from .gltf2_blender_image import TmpImageGuard, make_temp_image_copy, StoreImage, StoreData
import numpy as np
from .....io.com.gltf2_io_extensions import Extension
from ....com.gltf2_blender_conversion import get_anisotropy_rotation_blender_to_gltf
from ...material import gltf2_blender_gather_texture_info
from ..gltf2_blender_search_node_tree import detect_anisotropy_nodes, get_socket, has_image_node_from_socket, get_factor_from_socket

def export_anisotropy(blender_material, export_settings):

anisotropy_extension = {}
uvmap_infos = {}

anisotropy_socket = get_socket(blender_material, 'Anisotropic')
anisotropic_rotation_socket = get_socket(blender_material, 'Anisotropic Rotation')
anisotropy_tangent_socket = get_socket(blender_material, 'Tangent')

if anisotropy_socket.socket is None or anisotropic_rotation_socket.socket is None or anisotropy_tangent_socket.socket is None:
return None, {}

if anisotropy_socket.socket.is_linked is False and anisotropic_rotation_socket.socket.is_linked is False:
# We don't need the complex node setup, just export the value
anisotropyStrength = anisotropy_socket.socket.default_value
if anisotropyStrength != 0.0:
anisotropy_extension['anisotropyStrength'] = anisotropyStrength
anisotropyRotation = get_anisotropy_rotation_blender_to_gltf(anisotropic_rotation_socket.socket.default_value)
if anisotropyRotation != 0.0:
anisotropy_extension['anisotropyRotation'] = anisotropyRotation

if len(anisotropy_extension) == 0:
return None, {}

return Extension('KHR_materials_anisotropy', anisotropy_extension, False), uvmap_infos

# Get complex node setup

is_anisotropy, anisotropy_data = detect_anisotropy_nodes(
anisotropy_socket,
anisotropic_rotation_socket,
anisotropy_tangent_socket,
export_settings
)

if not is_anisotropy:
# Trying to export from grayscale textures
anisotropy_texture, uvmap_info = export_anisotropy_from_grayscale_textures(blender_material, export_settings)
if anisotropy_texture is None:
return None, {}

fac = get_factor_from_socket(anisotropy_socket, kind='VALUE')
if fac is None and anisotropy_texture is not None:
anisotropy_extension['anisotropyStrength'] = 1.0
elif fac != 0.0 and anisotropy_texture is not None:
anisotropy_extension['anisotropyStrength'] = fac

fac = get_factor_from_socket(anisotropic_rotation_socket, kind='VALUE')
if fac is None and anisotropy_texture is not None:
pass # Rotation 0 is default
elif fac != 0.0 and anisotropy_texture is not None:
anisotropy_extension['anisotropyRotation'] = get_anisotropy_rotation_blender_to_gltf(fac)

anisotropy_extension['anisotropyTexture'] = anisotropy_texture
uvmap_infos.update({'anisotropyTexture': uvmap_info})

return Extension('KHR_materials_anisotropy', anisotropy_extension, False), uvmap_infos


# Export from complex node setup

if anisotropy_data['anisotropyStrength'] != 0.0:
anisotropy_extension['anisotropyStrength'] = anisotropy_data['anisotropyStrength']
if anisotropy_data['anisotropyRotation'] != 0.0:
anisotropy_extension['anisotropyRotation'] = anisotropy_data['anisotropyRotation']

# Get texture data
# No need to check here that we have a texture, this check is already done insode detect_anisotropy_nodes
anisotropy_texture, uvmap_info , _ = gltf2_blender_gather_texture_info.gather_texture_info(
anisotropy_data['tex_socket'],
(anisotropy_data['tex_socket'],),
(),
export_settings,
)
anisotropy_extension['anisotropyTexture'] = anisotropy_texture
uvmap_infos.update({'anisotropyTexture': uvmap_info})


return Extension('KHR_materials_anisotropy', anisotropy_extension, False), uvmap_infos

def export_anisotropy_from_grayscale_textures(blender_material, export_settings):
# There will be a texture, with a complex calculation (no direct channel mapping)

anisotropy_socket = get_socket(blender_material, 'Anisotropic')
anisotropic_rotation_socket = get_socket(blender_material, 'Anisotropic Rotation')
anisotropy_tangent_socket = get_socket(blender_material, 'Tangent')

sockets = (anisotropy_socket, anisotropic_rotation_socket, anisotropy_tangent_socket)

# Set primary socket having a texture
primary_socket = anisotropy_socket
if not has_image_node_from_socket(primary_socket, export_settings):
primary_socket = anisotropic_rotation_socket

anisotropyTexture, uvmap_info, _ = gltf2_blender_gather_texture_info.gather_texture_info(
primary_socket,
sockets,
(),
export_settings,
filter_type='ANY')

if anisotropyTexture is None:
return None, {}

return anisotropyTexture, uvmap_info


def grayscale_anisotropy_calculation(stored):

# Find all Blender images used
images = []
for fill in stored.values():
if isinstance(fill, StoreImage):
if fill.image not in images:
images.append(fill.image)

if not images:
# No ImageFills; use a 1x1 white pixel
pixels = np.array([1.0, 1.0, 1.0, 1.0], np.float32)
return pixels, 1, 1

width = max(image.size[0] for image in images)
height = max(image.size[1] for image in images)

buffers = {}

def rgb2gray(rgb):
r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]
gray = 0.2989 * r + 0.5870 * g + 0.1140 * b
return gray

for identifier, image in [(ident, store.image) for (ident, store) in stored.items() if isinstance(store, StoreImage)]:
tmp_buf = np.empty(width * height * 4, np.float32)

if image.size[0] == width and image.size[1] == height:
image.pixels.foreach_get(tmp_buf)
else:
# Image is the wrong size; make a temp copy and scale it.
with TmpImageGuard() as guard:
make_temp_image_copy(guard, src_image=image)
tmp_image = guard.image
tmp_image.scale(width, height)
tmp_image.pixels.foreach_get(tmp_buf)

buffers[identifier] = np.reshape(tmp_buf, [width, height, 4])
buffers[identifier] = rgb2gray(buffers[identifier])

for identifier, data in [(ident, data) for (ident, data) in stored.items() if isinstance(data, StoreData)]:
buffers[identifier] = np.full((width, height), 1) # Set to white / 1.0, as value is set as factor

# Combine the image
out_buf = np.zeros((width, height, 4), np.float32)
out_buf[:,:,3] = 1.0 # A : Alpha
out_buf[:,:,2] = buffers['anisotropy'] # B : Strength (Anisotropic socket)

# Rotation needs to be converted from 0-1 to 0-2pi, and then vectorized it, normalized, and apply to R & G channels
# with mapping

buffers['anisotropic_rotation'] = buffers['anisotropic_rotation'] * 2 * np.pi
buffers['anisotropic_rotation'] = np.stack((np.cos(buffers['anisotropic_rotation']), np.sin(buffers['anisotropic_rotation'])), axis=-1)
buffers['anisotropic_rotation'] = buffers['anisotropic_rotation'] / np.linalg.norm(buffers['anisotropic_rotation'], axis=-1, keepdims=True)
buffers['anisotropic_rotation'] = (buffers['anisotropic_rotation'] + 1.0) / 2.0

out_buf[:,:,0] = buffers['anisotropic_rotation'][:,:,0] # R : Rotation X
out_buf[:,:,1] = buffers['anisotropic_rotation'][:,:,1] # G : Rotation Y

out_buf = np.reshape(out_buf, (width * height * 4))


return np.float32(out_buf), width, height, None
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
from ..gltf2_blender_gather_cache import cached
from .extensions.gltf2_blender_image import Channel, ExportImage, FillImage
from .gltf2_blender_search_node_tree import get_texture_node_from_socket, NodeSocket
from .gltf2_blender_search_node_tree import get_texture_node_from_socket, detect_anisotropy_nodes

@cached
def gather_image(
Expand Down Expand Up @@ -204,7 +204,33 @@ def __get_image_data(sockets, default_sockets, export_settings) -> ExportImage:
results = [get_texture_node_from_socket(socket, export_settings) for socket in sockets]

# Check if we need a simple mapping or more complex calculation
# There is currently no complex calculation for any textures

# Case of Anisotropy : It can be a complex node setup, or simple grayscale textures
# In case of complex node setup, this will be a direct mapping of channels
# But in case of grayscale textures, we need to combine them, we numpy calculations
# So we need to check if we have a complex node setup or not

need_to_check_anisotropy = is_anisotropy = False
try:
anisotropy_socket = [s for s in sockets if s.socket.name == 'Anisotropic'][0]
anisotropy_rotation_socket = [s for s in sockets if s.socket.name == 'Anisotropic Rotation'][0]
anisotropy_tangent_socket = [s for s in sockets if s.socket.name == 'Tangent'][0]
need_to_check_anisotropy = True
except:
need_to_check_anisotropy = False

if need_to_check_anisotropy is True:
is_anisotropy, anisotropy_data = detect_anisotropy_nodes(
anisotropy_socket,
anisotropy_rotation_socket,
anisotropy_tangent_socket,
export_settings
)

if need_to_check_anisotropy is True and is_anisotropy is False:
# We are not in complex node setup, so we can try to get the image data from grayscale textures
return __get_image_data_grayscale_anisotropy(sockets, results, export_settings)

return __get_image_data_mapping(sockets, default_sockets, results, export_settings)

def __get_image_data_mapping(sockets, default_sockets, results, export_settings) -> ExportImage:
Expand Down Expand Up @@ -325,6 +351,29 @@ def __get_image_data_mapping(sockets, default_sockets, results, export_settings)
return composed_image


def __get_image_data_grayscale_anisotropy(sockets, results, export_settings) -> ExportImage:
"""
calculating Anisotropy Texture from grayscale textures, settings needed data
"""
from .extensions.gltf2_blender_gather_materials_anisotropy import grayscale_anisotropy_calculation
composed_image = ExportImage()
composed_image.set_calc(grayscale_anisotropy_calculation)

results = [get_texture_node_from_socket(socket, export_settings) for socket in sockets[:-1]] #No texture from tangent

mapping = {
0: "anisotropy",
1: "anisotropic_rotation",
}

for idx, result in enumerate(results):
if get_texture_node_from_socket(sockets[idx], export_settings):
composed_image.store_data(mapping[idx], result.shader_node.image, type="Image")
else:
composed_image.store_data(mapping[idx], sockets[idx].socket.default_value, type="Data")

return composed_image

def __is_blender_image_a_jpeg(image: bpy.types.Image) -> bool:
if image.source != 'FILE':
return False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .extensions.gltf2_blender_gather_materials_specular import export_specular
from .extensions.gltf2_blender_gather_materials_transmission import export_transmission
from .extensions.gltf2_blender_gather_materials_clearcoat import export_clearcoat
from .extensions.gltf2_blender_gather_materials_anisotropy import export_anisotropy
from .extensions.gltf2_blender_gather_materials_ior import export_ior
from .gltf2_blender_search_node_tree import \
has_image_node_from_socket, \
Expand Down Expand Up @@ -211,6 +212,12 @@ def __gather_extensions(blender_material, emissive_factor, export_settings):
extensions["KHR_materials_sheen"] = sheen_extension
uvmap_infos.update(uvmap_info)

# KHR_materials_anisotropy
anisotropy_extension, uvmap_info = export_anisotropy(blender_material, export_settings)
if anisotropy_extension:
extensions["KHR_materials_anisotropy"] = anisotropy_extension
uvmap_infos.update(uvmap_info)

# KHR_materials_ior
# Keep this extension at the end, because we export it only if some others are exported
ior_extension = export_ior(blender_material, extensions, export_settings)
Expand Down Expand Up @@ -443,6 +450,8 @@ def __get_final_material_with_indices(blender_material, base_material, caching_i
material.extensions["KHR_materials_sheen"].extension['sheenRoughnessTexture'].tex_coord = ind
elif tex == "thicknessTexture":
material.extensions["KHR_materials_volume"].extension['thicknessTexture'].tex_ccord = ind
elif tex == "anisotropyTexture":
material.extensions["KHR_materials_anisotropy"].extension['anisotropyTexture'].tex_coord = ind
else:
print_console("ERROR", "some Textures tex coord are not managed")

Expand Down Expand Up @@ -488,5 +497,6 @@ def get_all_textures():
tab.append("sheenColorTexture")
tab.append("sheenRoughnessTexture")
tab.append("thicknessTexture")
tab.append("anisotropyTexture")

return tab
Loading